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