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
73namespace WebCore {
74using namespace JSC;
75
76void ScriptController::initializeThreading()
77{
78#if !PLATFORM(IOS_FAMILY)
79 JSC::initializeThreading();
80 WTF::initializeMainThread();
81#endif
82}
83
84ScriptController::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
97ScriptController::~ScriptController()
98{
99 disconnectPlatformScriptObjects();
100
101 if (m_cacheableBindingRootObject) {
102 JSLockHolder lock(commonVM());
103 m_cacheableBindingRootObject->invalidate();
104 m_cacheableBindingRootObject = nullptr;
105 }
106}
107
108JSValue 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
146JSValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails)
147{
148 return evaluateInWorld(sourceCode, mainThreadNormalWorld(), exceptionDetails);
149}
150
151void 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
162void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters)
163{
164 loadModuleScriptInWorld(moduleScript, moduleName, WTFMove(topLevelFetchParameters), mainThreadNormalWorld());
165}
166
167void 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
178void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode)
179{
180 loadModuleScriptInWorld(moduleScript, sourceCode, mainThreadNormalWorld());
181}
182
183JSC::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
205JSC::JSValue ScriptController::linkAndEvaluateModuleScript(LoadableModuleScript& moduleScript)
206{
207 return linkAndEvaluateModuleScriptInWorld(moduleScript, mainThreadNormalWorld());
208}
209
210JSC::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
230JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord)
231{
232 return evaluateModule(sourceURL, moduleRecord, mainThreadNormalWorld());
233}
234
235Ref<DOMWrapperWorld> ScriptController::createWorld()
236{
237 return DOMWrapperWorld::create(commonVM());
238}
239
240void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds)
241{
242 static_cast<JSVMClientData*>(commonVM().clientData)->getAllWorlds(worlds);
243}
244
245void 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
263static 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
271void 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
328WindowProxy& ScriptController::windowProxy()
329{
330 return m_frame.windowProxy();
331}
332
333JSWindowProxy& 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
340TextPosition 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
353void ScriptController::enableEval()
354{
355 auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
356 if (!jsWindowProxy)
357 return;
358 jsWindowProxy->window()->setEvalEnabled(true);
359}
360
361void ScriptController::enableWebAssembly()
362{
363 auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
364 if (!jsWindowProxy)
365 return;
366 jsWindowProxy->window()->setWebAssemblyEnabled(true);
367}
368
369void 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
377void 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
385bool 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
396void ScriptController::updateDocument()
397{
398 for (auto& jsWindowProxy : windowProxy().jsWindowProxiesAsVector()) {
399 JSLockHolder lock(jsWindowProxy->world().vm());
400 jsCast<JSDOMWindow*>(jsWindowProxy->window())->updateDocument();
401 }
402}
403
404Bindings::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
416Bindings::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
428Ref<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
440void 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)
450NPObject* 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)
473RefPtr<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
482JSObject* 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
502void ScriptController::updatePlatformScriptObjects()
503{
504}
505
506void ScriptController::disconnectPlatformScriptObjects()
507{
508}
509
510#endif
511
512void 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
522void 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
547JSValue 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
558JSValue 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
568bool 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
579bool 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
597JSValue 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
603JSValue 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
615bool 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
658