1 | /* |
2 | * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
3 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
4 | * Copyright (C) 2006-2019 Apple Inc. All rights reserved. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | #include "config.h" |
22 | #include "ScriptController.h" |
23 | |
24 | #include "BridgeJSC.h" |
25 | #include "CachedScriptFetcher.h" |
26 | #include "CommonVM.h" |
27 | #include "ContentSecurityPolicy.h" |
28 | #include "DocumentLoader.h" |
29 | #include "Event.h" |
30 | #include "Frame.h" |
31 | #include "FrameLoader.h" |
32 | #include "FrameLoaderClient.h" |
33 | #include "HTMLPlugInElement.h" |
34 | #include "InspectorInstrumentation.h" |
35 | #include "JSDOMBindingSecurity.h" |
36 | #include "JSDOMExceptionHandling.h" |
37 | #include "JSDOMWindow.h" |
38 | #include "JSDocument.h" |
39 | #include "JSExecState.h" |
40 | #include "LoadableModuleScript.h" |
41 | #include "ModuleFetchFailureKind.h" |
42 | #include "ModuleFetchParameters.h" |
43 | #include "NP_jsobject.h" |
44 | #include "Page.h" |
45 | #include "PageConsoleClient.h" |
46 | #include "PageGroup.h" |
47 | #include "PaymentCoordinator.h" |
48 | #include "PluginViewBase.h" |
49 | #include "RuntimeApplicationChecks.h" |
50 | #include "ScriptDisallowedScope.h" |
51 | #include "ScriptSourceCode.h" |
52 | #include "ScriptableDocumentParser.h" |
53 | #include "Settings.h" |
54 | #include "UserGestureIndicator.h" |
55 | #include "WebCoreJSClientData.h" |
56 | #include "npruntime_impl.h" |
57 | #include "runtime_root.h" |
58 | #include <JavaScriptCore/Debugger.h> |
59 | #include <JavaScriptCore/InitializeThreading.h> |
60 | #include <JavaScriptCore/JSFunction.h> |
61 | #include <JavaScriptCore/JSInternalPromise.h> |
62 | #include <JavaScriptCore/JSLock.h> |
63 | #include <JavaScriptCore/JSModuleRecord.h> |
64 | #include <JavaScriptCore/JSNativeStdFunction.h> |
65 | #include <JavaScriptCore/JSScriptFetchParameters.h> |
66 | #include <JavaScriptCore/JSScriptFetcher.h> |
67 | #include <JavaScriptCore/ScriptCallStack.h> |
68 | #include <JavaScriptCore/StrongInlines.h> |
69 | #include <wtf/SetForScope.h> |
70 | #include <wtf/Threading.h> |
71 | #include <wtf/text/TextPosition.h> |
72 | |
73 | namespace WebCore { |
74 | using namespace JSC; |
75 | |
76 | void ScriptController::initializeThreading() |
77 | { |
78 | #if !PLATFORM(IOS_FAMILY) |
79 | JSC::initializeThreading(); |
80 | WTF::initializeMainThread(); |
81 | #endif |
82 | } |
83 | |
84 | ScriptController::ScriptController(Frame& frame) |
85 | : m_frame(frame) |
86 | , m_sourceURL(0) |
87 | , m_paused(false) |
88 | #if ENABLE(NETSCAPE_PLUGIN_API) |
89 | , m_windowScriptNPObject(0) |
90 | #endif |
91 | #if PLATFORM(COCOA) |
92 | , m_windowScriptObject(0) |
93 | #endif |
94 | { |
95 | } |
96 | |
97 | ScriptController::~ScriptController() |
98 | { |
99 | disconnectPlatformScriptObjects(); |
100 | |
101 | if (m_cacheableBindingRootObject) { |
102 | JSLockHolder lock(commonVM()); |
103 | m_cacheableBindingRootObject->invalidate(); |
104 | m_cacheableBindingRootObject = nullptr; |
105 | } |
106 | } |
107 | |
108 | JSValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld& world, ExceptionDetails* exceptionDetails) |
109 | { |
110 | JSLockHolder lock(world.vm()); |
111 | |
112 | const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); |
113 | String sourceURL = jsSourceCode.provider()->url(); |
114 | |
115 | // evaluate code. Returns the JS return value or 0 |
116 | // if there was none, an error occurred or the type couldn't be converted. |
117 | |
118 | // inlineCode is true for <a href="javascript:doSomething()"> |
119 | // and false for <script>doSomething()</script>. Check if it has the |
120 | // expected value in all cases. |
121 | // See smart window.open policy for where this is used. |
122 | auto& proxy = jsWindowProxy(world); |
123 | auto& exec = *proxy.window()->globalExec(); |
124 | const String* savedSourceURL = m_sourceURL; |
125 | m_sourceURL = &sourceURL; |
126 | |
127 | Ref<Frame> protector(m_frame); |
128 | |
129 | InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine(), sourceCode.startColumn()); |
130 | |
131 | NakedPtr<JSC::Exception> evaluationException; |
132 | JSValue returnValue = JSExecState::profiledEvaluate(&exec, JSC::ProfilingReason::Other, jsSourceCode, &proxy, evaluationException); |
133 | |
134 | InspectorInstrumentation::didEvaluateScript(cookie, m_frame); |
135 | |
136 | if (evaluationException) { |
137 | reportException(&exec, evaluationException, sourceCode.cachedScript(), exceptionDetails); |
138 | m_sourceURL = savedSourceURL; |
139 | return { }; |
140 | } |
141 | |
142 | m_sourceURL = savedSourceURL; |
143 | return returnValue; |
144 | } |
145 | |
146 | JSValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails) |
147 | { |
148 | return evaluateInWorld(sourceCode, mainThreadNormalWorld(), exceptionDetails); |
149 | } |
150 | |
151 | void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters, DOMWrapperWorld& world) |
152 | { |
153 | JSLockHolder lock(world.vm()); |
154 | |
155 | auto& proxy = jsWindowProxy(world); |
156 | auto& state = *proxy.window()->globalExec(); |
157 | |
158 | auto& promise = JSExecState::loadModule(state, moduleName, JSC::JSScriptFetchParameters::create(state.vm(), WTFMove(topLevelFetchParameters)), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript })); |
159 | setupModuleScriptHandlers(moduleScript, promise, world); |
160 | } |
161 | |
162 | void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters) |
163 | { |
164 | loadModuleScriptInWorld(moduleScript, moduleName, WTFMove(topLevelFetchParameters), mainThreadNormalWorld()); |
165 | } |
166 | |
167 | void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode, DOMWrapperWorld& world) |
168 | { |
169 | JSLockHolder lock(world.vm()); |
170 | |
171 | auto& proxy = jsWindowProxy(world); |
172 | auto& state = *proxy.window()->globalExec(); |
173 | |
174 | auto& promise = JSExecState::loadModule(state, sourceCode.jsSourceCode(), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript })); |
175 | setupModuleScriptHandlers(moduleScript, promise, world); |
176 | } |
177 | |
178 | void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode) |
179 | { |
180 | loadModuleScriptInWorld(moduleScript, sourceCode, mainThreadNormalWorld()); |
181 | } |
182 | |
183 | JSC::JSValue ScriptController::linkAndEvaluateModuleScriptInWorld(LoadableModuleScript& moduleScript, DOMWrapperWorld& world) |
184 | { |
185 | JSLockHolder lock(world.vm()); |
186 | |
187 | auto& proxy = jsWindowProxy(world); |
188 | auto& state = *proxy.window()->globalExec(); |
189 | |
190 | // FIXME: Preventing Frame from being destroyed is essentially unnecessary. |
191 | // https://bugs.webkit.org/show_bug.cgi?id=164763 |
192 | Ref<Frame> protector(m_frame); |
193 | |
194 | NakedPtr<JSC::Exception> evaluationException; |
195 | auto returnValue = JSExecState::linkAndEvaluateModule(state, Identifier::fromUid(&state.vm(), moduleScript.moduleKey()), jsUndefined(), evaluationException); |
196 | if (evaluationException) { |
197 | // FIXME: Give a chance to dump the stack trace if the "crossorigin" attribute allows. |
198 | // https://bugs.webkit.org/show_bug.cgi?id=164539 |
199 | reportException(&state, evaluationException, nullptr); |
200 | return jsUndefined(); |
201 | } |
202 | return returnValue; |
203 | } |
204 | |
205 | JSC::JSValue ScriptController::linkAndEvaluateModuleScript(LoadableModuleScript& moduleScript) |
206 | { |
207 | return linkAndEvaluateModuleScriptInWorld(moduleScript, mainThreadNormalWorld()); |
208 | } |
209 | |
210 | JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord, DOMWrapperWorld& world) |
211 | { |
212 | JSLockHolder lock(world.vm()); |
213 | |
214 | const auto& jsSourceCode = moduleRecord.sourceCode(); |
215 | |
216 | auto& proxy = jsWindowProxy(world); |
217 | auto& state = *proxy.window()->globalExec(); |
218 | SetForScope<const String*> sourceURLScope(m_sourceURL, &sourceURL.string()); |
219 | |
220 | Ref<Frame> protector(m_frame); |
221 | |
222 | auto cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, jsSourceCode.firstLine().oneBasedInt(), jsSourceCode.startColumn().oneBasedInt()); |
223 | |
224 | auto returnValue = moduleRecord.evaluate(&state); |
225 | InspectorInstrumentation::didEvaluateScript(cookie, m_frame); |
226 | |
227 | return returnValue; |
228 | } |
229 | |
230 | JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord) |
231 | { |
232 | return evaluateModule(sourceURL, moduleRecord, mainThreadNormalWorld()); |
233 | } |
234 | |
235 | Ref<DOMWrapperWorld> ScriptController::createWorld() |
236 | { |
237 | return DOMWrapperWorld::create(commonVM()); |
238 | } |
239 | |
240 | void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds) |
241 | { |
242 | static_cast<JSVMClientData*>(commonVM().clientData)->getAllWorlds(worlds); |
243 | } |
244 | |
245 | void ScriptController::initScriptForWindowProxy(JSWindowProxy& windowProxy) |
246 | { |
247 | auto& world = windowProxy.world(); |
248 | |
249 | jsCast<JSDOMWindow*>(windowProxy.window())->updateDocument(); |
250 | |
251 | if (Document* document = m_frame.document()) |
252 | document->contentSecurityPolicy()->didCreateWindowProxy(windowProxy); |
253 | |
254 | if (Page* page = m_frame.page()) { |
255 | windowProxy.attachDebugger(page->debugger()); |
256 | windowProxy.window()->setProfileGroup(page->group().identifier()); |
257 | windowProxy.window()->setConsoleClient(&page->console()); |
258 | } |
259 | |
260 | m_frame.loader().dispatchDidClearWindowObjectInWorld(world); |
261 | } |
262 | |
263 | static Identifier jsValueToModuleKey(ExecState* exec, JSValue value) |
264 | { |
265 | if (value.isSymbol()) |
266 | return Identifier::fromUid(jsCast<Symbol*>(value)->privateName()); |
267 | ASSERT(value.isString()); |
268 | return asString(value)->toIdentifier(exec); |
269 | } |
270 | |
271 | void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScriptRef, JSInternalPromise& promise, DOMWrapperWorld& world) |
272 | { |
273 | auto& proxy = jsWindowProxy(world); |
274 | auto& state = *proxy.window()->globalExec(); |
275 | |
276 | // It is not guaranteed that either fulfillHandler or rejectHandler is eventually called. |
277 | // For example, if the page load is canceled, the DeferredPromise used in the module loader pipeline will stop executing JS code. |
278 | // Thus the promise returned from this function could remain unresolved. |
279 | |
280 | RefPtr<LoadableModuleScript> moduleScript(&moduleScriptRef); |
281 | |
282 | auto& fulfillHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) -> JSC::EncodedJSValue { |
283 | VM& vm = exec->vm(); |
284 | auto scope = DECLARE_THROW_SCOPE(vm); |
285 | Identifier moduleKey = jsValueToModuleKey(exec, exec->argument(0)); |
286 | RETURN_IF_EXCEPTION(scope, { }); |
287 | moduleScript->notifyLoadCompleted(*moduleKey.impl()); |
288 | return JSValue::encode(jsUndefined()); |
289 | }); |
290 | |
291 | auto& rejectHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) { |
292 | VM& vm = exec->vm(); |
293 | JSValue errorValue = exec->argument(0); |
294 | if (errorValue.isObject()) { |
295 | auto* object = JSC::asObject(errorValue); |
296 | if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) { |
297 | // This is host propagated error in the module loader pipeline. |
298 | switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) { |
299 | case ModuleFetchFailureKind::WasErrored: |
300 | moduleScript->notifyLoadFailed(LoadableScript::Error { |
301 | LoadableScript::ErrorType::CachedScript, |
302 | WTF::nullopt |
303 | }); |
304 | break; |
305 | case ModuleFetchFailureKind::WasCanceled: |
306 | moduleScript->notifyLoadWasCanceled(); |
307 | break; |
308 | } |
309 | return JSValue::encode(jsUndefined()); |
310 | } |
311 | } |
312 | |
313 | auto scope = DECLARE_CATCH_SCOPE(vm); |
314 | moduleScript->notifyLoadFailed(LoadableScript::Error { |
315 | LoadableScript::ErrorType::CachedScript, |
316 | LoadableScript::ConsoleMessage { |
317 | MessageSource::JS, |
318 | MessageLevel::Error, |
319 | retrieveErrorMessage(*exec, vm, errorValue, scope), |
320 | } |
321 | }); |
322 | return JSValue::encode(jsUndefined()); |
323 | }); |
324 | |
325 | promise.then(&state, &fulfillHandler, &rejectHandler); |
326 | } |
327 | |
328 | WindowProxy& ScriptController::windowProxy() |
329 | { |
330 | return m_frame.windowProxy(); |
331 | } |
332 | |
333 | JSWindowProxy& ScriptController::jsWindowProxy(DOMWrapperWorld& world) |
334 | { |
335 | auto* jsWindowProxy = m_frame.windowProxy().jsWindowProxy(world); |
336 | ASSERT_WITH_MESSAGE(jsWindowProxy, "The JSWindowProxy can only be null if the frame has been destroyed" ); |
337 | return *jsWindowProxy; |
338 | } |
339 | |
340 | TextPosition ScriptController::eventHandlerPosition() const |
341 | { |
342 | // FIXME: If we are not currently parsing, we should use our current location |
343 | // in JavaScript, to cover cases like "element.setAttribute('click', ...)". |
344 | |
345 | // FIXME: This location maps to the end of the HTML tag, and not to the |
346 | // exact column number belonging to the event handler attribute. |
347 | auto* parser = m_frame.document()->scriptableDocumentParser(); |
348 | if (parser) |
349 | return parser->textPosition(); |
350 | return TextPosition(); |
351 | } |
352 | |
353 | void ScriptController::enableEval() |
354 | { |
355 | auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld()); |
356 | if (!jsWindowProxy) |
357 | return; |
358 | jsWindowProxy->window()->setEvalEnabled(true); |
359 | } |
360 | |
361 | void ScriptController::enableWebAssembly() |
362 | { |
363 | auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld()); |
364 | if (!jsWindowProxy) |
365 | return; |
366 | jsWindowProxy->window()->setWebAssemblyEnabled(true); |
367 | } |
368 | |
369 | void ScriptController::disableEval(const String& errorMessage) |
370 | { |
371 | auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld()); |
372 | if (!jsWindowProxy) |
373 | return; |
374 | jsWindowProxy->window()->setEvalEnabled(false, errorMessage); |
375 | } |
376 | |
377 | void ScriptController::disableWebAssembly(const String& errorMessage) |
378 | { |
379 | auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld()); |
380 | if (!jsWindowProxy) |
381 | return; |
382 | jsWindowProxy->window()->setWebAssemblyEnabled(false, errorMessage); |
383 | } |
384 | |
385 | bool ScriptController::canAccessFromCurrentOrigin(Frame* frame) |
386 | { |
387 | auto* state = JSExecState::currentState(); |
388 | |
389 | // If the current state is null we're in a call path where the DOM security check doesn't apply (eg. parser). |
390 | if (!state) |
391 | return true; |
392 | |
393 | return BindingSecurity::shouldAllowAccessToFrame(state, frame); |
394 | } |
395 | |
396 | void ScriptController::updateDocument() |
397 | { |
398 | for (auto& jsWindowProxy : windowProxy().jsWindowProxiesAsVector()) { |
399 | JSLockHolder lock(jsWindowProxy->world().vm()); |
400 | jsCast<JSDOMWindow*>(jsWindowProxy->window())->updateDocument(); |
401 | } |
402 | } |
403 | |
404 | Bindings::RootObject* ScriptController::cacheableBindingRootObject() |
405 | { |
406 | if (!canExecuteScripts(NotAboutToExecuteScript)) |
407 | return nullptr; |
408 | |
409 | if (!m_cacheableBindingRootObject) { |
410 | JSLockHolder lock(commonVM()); |
411 | m_cacheableBindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld())); |
412 | } |
413 | return m_cacheableBindingRootObject.get(); |
414 | } |
415 | |
416 | Bindings::RootObject* ScriptController::bindingRootObject() |
417 | { |
418 | if (!canExecuteScripts(NotAboutToExecuteScript)) |
419 | return nullptr; |
420 | |
421 | if (!m_bindingRootObject) { |
422 | JSLockHolder lock(commonVM()); |
423 | m_bindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld())); |
424 | } |
425 | return m_bindingRootObject.get(); |
426 | } |
427 | |
428 | Ref<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle) |
429 | { |
430 | auto it = m_rootObjects.find(nativeHandle); |
431 | if (it != m_rootObjects.end()) |
432 | return it->value.copyRef(); |
433 | |
434 | auto rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld())); |
435 | |
436 | m_rootObjects.set(nativeHandle, rootObject.copyRef()); |
437 | return rootObject; |
438 | } |
439 | |
440 | void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*>>& result) |
441 | { |
442 | for (auto& jsWindowProxy : windowProxy().jsWindowProxiesAsVector()) { |
443 | auto* exec = jsWindowProxy->window()->globalExec(); |
444 | auto* origin = &downcast<DOMWindow>(jsWindowProxy->wrapped()).document()->securityOrigin(); |
445 | result.append(std::make_pair(exec, origin)); |
446 | } |
447 | } |
448 | |
449 | #if ENABLE(NETSCAPE_PLUGIN_API) |
450 | NPObject* ScriptController::windowScriptNPObject() |
451 | { |
452 | if (!m_windowScriptNPObject) { |
453 | JSLockHolder lock(commonVM()); |
454 | if (canExecuteScripts(NotAboutToExecuteScript)) { |
455 | // JavaScript is enabled, so there is a JavaScript window object. |
456 | // Return an NPObject bound to the window object. |
457 | auto* window = jsWindowProxy(pluginWorld()).window(); |
458 | ASSERT(window); |
459 | Bindings::RootObject* root = bindingRootObject(); |
460 | m_windowScriptNPObject = _NPN_CreateScriptObject(0, window, root); |
461 | } else { |
462 | // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. |
463 | // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. |
464 | m_windowScriptNPObject = _NPN_CreateNoScriptObject(); |
465 | } |
466 | } |
467 | |
468 | return m_windowScriptNPObject; |
469 | } |
470 | #endif |
471 | |
472 | #if !PLATFORM(COCOA) |
473 | RefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget) |
474 | { |
475 | if (!is<PluginViewBase>(*widget)) |
476 | return nullptr; |
477 | |
478 | return downcast<PluginViewBase>(*widget).bindingInstance(); |
479 | } |
480 | #endif |
481 | |
482 | JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin) |
483 | { |
484 | // Can't create JSObjects when JavaScript is disabled |
485 | if (!canExecuteScripts(NotAboutToExecuteScript)) |
486 | return nullptr; |
487 | |
488 | JSLockHolder lock(commonVM()); |
489 | |
490 | // Create a JSObject bound to this element |
491 | auto* globalObj = globalObject(pluginWorld()); |
492 | // FIXME: is normal okay? - used for NP plugins? |
493 | JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin); |
494 | if (!jsElementValue || !jsElementValue.isObject()) |
495 | return nullptr; |
496 | |
497 | return jsElementValue.getObject(); |
498 | } |
499 | |
500 | #if !PLATFORM(COCOA) |
501 | |
502 | void ScriptController::updatePlatformScriptObjects() |
503 | { |
504 | } |
505 | |
506 | void ScriptController::disconnectPlatformScriptObjects() |
507 | { |
508 | } |
509 | |
510 | #endif |
511 | |
512 | void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle) |
513 | { |
514 | auto it = m_rootObjects.find(nativeHandle); |
515 | if (it == m_rootObjects.end()) |
516 | return; |
517 | |
518 | it->value->invalidate(); |
519 | m_rootObjects.remove(it); |
520 | } |
521 | |
522 | void ScriptController::clearScriptObjects() |
523 | { |
524 | JSLockHolder lock(commonVM()); |
525 | |
526 | for (auto& rootObject : m_rootObjects.values()) |
527 | rootObject->invalidate(); |
528 | |
529 | m_rootObjects.clear(); |
530 | |
531 | if (m_bindingRootObject) { |
532 | m_bindingRootObject->invalidate(); |
533 | m_bindingRootObject = nullptr; |
534 | } |
535 | |
536 | #if ENABLE(NETSCAPE_PLUGIN_API) |
537 | if (m_windowScriptNPObject) { |
538 | // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window |
539 | // script object properly. |
540 | // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. |
541 | _NPN_DeallocateObject(m_windowScriptNPObject); |
542 | m_windowScriptNPObject = nullptr; |
543 | } |
544 | #endif |
545 | } |
546 | |
547 | JSValue ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails) |
548 | { |
549 | UserGestureIndicator gestureIndicator(forceUserGesture ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt); |
550 | ScriptSourceCode sourceCode(script, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset())); |
551 | |
552 | if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) |
553 | return { }; |
554 | |
555 | return evaluateInWorld(sourceCode, world, exceptionDetails); |
556 | } |
557 | |
558 | JSValue ScriptController::executeUserAgentScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails) |
559 | { |
560 | auto& document = *m_frame.document(); |
561 | if (!shouldAllowUserAgentScripts(document)) |
562 | return { }; |
563 | |
564 | document.setHasEvaluatedUserAgentScripts(); |
565 | return executeScriptInWorld(world, script, forceUserGesture, exceptionDetails); |
566 | } |
567 | |
568 | bool ScriptController::shouldAllowUserAgentScripts(Document& document) const |
569 | { |
570 | #if ENABLE(APPLE_PAY) |
571 | if (auto page = m_frame.page()) |
572 | return page->paymentCoordinator().shouldAllowUserAgentScripts(document); |
573 | #else |
574 | UNUSED_PARAM(document); |
575 | #endif |
576 | return true; |
577 | } |
578 | |
579 | bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason) |
580 | { |
581 | if (reason == AboutToExecuteScript) |
582 | RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed() || !isInWebProcess()); |
583 | |
584 | if (m_frame.document() && m_frame.document()->isSandboxed(SandboxScripts)) { |
585 | // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. |
586 | if (reason == AboutToExecuteScript || reason == AboutToCreateEventListener) |
587 | m_frame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked script execution in '" + m_frame.document()->url().stringCenterEllipsizedToLength() + "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set." ); |
588 | return false; |
589 | } |
590 | |
591 | if (!m_frame.page()) |
592 | return false; |
593 | |
594 | return m_frame.loader().client().allowScript(m_frame.settings().isScriptEnabled()); |
595 | } |
596 | |
597 | JSValue ScriptController::executeScript(const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails) |
598 | { |
599 | UserGestureIndicator gestureIndicator(forceUserGesture ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt); |
600 | return executeScript(ScriptSourceCode(script, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset())), exceptionDetails); |
601 | } |
602 | |
603 | JSValue ScriptController::executeScript(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails) |
604 | { |
605 | if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) |
606 | return { }; // FIXME: Would jsNull be better? |
607 | |
608 | // FIXME: Preventing Frame from being destroyed is essentially unnecessary. |
609 | // https://bugs.webkit.org/show_bug.cgi?id=164763 |
610 | Ref<Frame> protector(m_frame); // Script execution can destroy the frame, and thus the ScriptController. |
611 | |
612 | return evaluate(sourceCode, exceptionDetails); |
613 | } |
614 | |
615 | bool ScriptController::executeIfJavaScriptURL(const URL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL) |
616 | { |
617 | if (!WTF::protocolIsJavaScript(url)) |
618 | return false; |
619 | |
620 | if (!m_frame.page() || !m_frame.document()->contentSecurityPolicy()->allowJavaScriptURLs(m_frame.document()->url(), eventHandlerPosition().m_line)) |
621 | return true; |
622 | |
623 | // We need to hold onto the Frame here because executing script can |
624 | // destroy the frame. |
625 | Ref<Frame> protector(m_frame); |
626 | RefPtr<Document> ownerDocument(m_frame.document()); |
627 | |
628 | const int javascriptSchemeLength = sizeof("javascript:" ) - 1; |
629 | |
630 | String decodedURL = decodeURLEscapeSequences(url.string()); |
631 | auto result = executeScript(decodedURL.substring(javascriptSchemeLength)); |
632 | |
633 | // If executing script caused this frame to be removed from the page, we |
634 | // don't want to try to replace its document! |
635 | if (!m_frame.page()) |
636 | return true; |
637 | |
638 | String scriptResult; |
639 | if (!result || !result.getString(jsWindowProxy(mainThreadNormalWorld()).window()->globalExec(), scriptResult)) |
640 | return true; |
641 | |
642 | // FIXME: We should always replace the document, but doing so |
643 | // synchronously can cause crashes: |
644 | // http://bugs.webkit.org/show_bug.cgi?id=16782 |
645 | if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) { |
646 | // We're still in a frame, so there should be a DocumentLoader. |
647 | ASSERT(m_frame.document()->loader()); |
648 | |
649 | // DocumentWriter::replaceDocument can cause the DocumentLoader to get deref'ed and possible destroyed, |
650 | // so protect it with a RefPtr. |
651 | if (RefPtr<DocumentLoader> loader = m_frame.document()->loader()) |
652 | loader->writer().replaceDocument(scriptResult, ownerDocument.get()); |
653 | } |
654 | return true; |
655 | } |
656 | |
657 | } // namespace WebCore |
658 | |