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