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