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