bf30e93c906616c5f2b47f8e89ef2fda292a4b60
[WebKit-https.git] / Source / WebCore / bindings / js / ScriptController.cpp
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