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