ScriptController.cpp [plain text]
#include "config.h"
#include "ScriptController.h"
#include "BridgeJSC.h"
#include "CachedScriptFetcher.h"
#include "CommonVM.h"
#include "ContentSecurityPolicy.h"
#include "DocumentLoader.h"
#include "Event.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "HTMLPlugInElement.h"
#include "InspectorInstrumentation.h"
#include "JSDOMBindingSecurity.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMWindow.h"
#include "JSDocument.h"
#include "JSExecState.h"
#include "LoadableModuleScript.h"
#include "ModuleFetchFailureKind.h"
#include "ModuleFetchParameters.h"
#include "NP_jsobject.h"
#include "Page.h"
#include "PageConsoleClient.h"
#include "PageGroup.h"
#include "PluginViewBase.h"
#include "RuntimeApplicationChecks.h"
#include "ScriptDisallowedScope.h"
#include "ScriptSourceCode.h"
#include "ScriptableDocumentParser.h"
#include "Settings.h"
#include "UserGestureIndicator.h"
#include "WebCoreJSClientData.h"
#include "npruntime_impl.h"
#include "runtime_root.h"
#include <JavaScriptCore/Debugger.h>
#include <JavaScriptCore/InitializeThreading.h>
#include <JavaScriptCore/JSFunction.h>
#include <JavaScriptCore/JSInternalPromise.h>
#include <JavaScriptCore/JSLock.h>
#include <JavaScriptCore/JSModuleRecord.h>
#include <JavaScriptCore/JSNativeStdFunction.h>
#include <JavaScriptCore/JSScriptFetchParameters.h>
#include <JavaScriptCore/JSScriptFetcher.h>
#include <JavaScriptCore/ScriptCallStack.h>
#include <JavaScriptCore/StrongInlines.h>
#include <wtf/SetForScope.h>
#include <wtf/Threading.h>
#include <wtf/text/TextPosition.h>
namespace WebCore {
using namespace JSC;
void ScriptController::initializeThreading()
{
#if !PLATFORM(IOS_FAMILY)
JSC::initializeThreading();
WTF::initializeMainThread();
#endif
}
ScriptController::ScriptController(Frame& frame)
: m_frame(frame)
, m_sourceURL(0)
, m_paused(false)
#if ENABLE(NETSCAPE_PLUGIN_API)
, m_windowScriptNPObject(0)
#endif
#if PLATFORM(COCOA)
, m_windowScriptObject(0)
#endif
{
}
ScriptController::~ScriptController()
{
disconnectPlatformScriptObjects();
if (m_cacheableBindingRootObject) {
JSLockHolder lock(commonVM());
m_cacheableBindingRootObject->invalidate();
m_cacheableBindingRootObject = nullptr;
}
}
JSValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld& world, ExceptionDetails* exceptionDetails)
{
JSLockHolder lock(world.vm());
const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
String sourceURL = jsSourceCode.provider()->url();
auto& proxy = jsWindowProxy(world);
auto& exec = *proxy.window()->globalExec();
const String* savedSourceURL = m_sourceURL;
m_sourceURL = &sourceURL;
Ref<Frame> protector(m_frame);
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
NakedPtr<JSC::Exception> evaluationException;
JSValue returnValue = JSExecState::profiledEvaluate(&exec, JSC::ProfilingReason::Other, jsSourceCode, &proxy, evaluationException);
InspectorInstrumentation::didEvaluateScript(cookie, m_frame);
if (evaluationException) {
reportException(&exec, evaluationException, sourceCode.cachedScript(), exceptionDetails);
m_sourceURL = savedSourceURL;
return { };
}
m_sourceURL = savedSourceURL;
return returnValue;
}
JSValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails)
{
return evaluateInWorld(sourceCode, mainThreadNormalWorld(), exceptionDetails);
}
void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters, DOMWrapperWorld& world)
{
JSLockHolder lock(world.vm());
auto& proxy = jsWindowProxy(world);
auto& state = *proxy.window()->globalExec();
auto& promise = JSExecState::loadModule(state, moduleName, JSC::JSScriptFetchParameters::create(state.vm(), WTFMove(topLevelFetchParameters)), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript }));
setupModuleScriptHandlers(moduleScript, promise, world);
}
void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const String& moduleName, Ref<ModuleFetchParameters>&& topLevelFetchParameters)
{
loadModuleScriptInWorld(moduleScript, moduleName, WTFMove(topLevelFetchParameters), mainThreadNormalWorld());
}
void ScriptController::loadModuleScriptInWorld(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode, DOMWrapperWorld& world)
{
JSLockHolder lock(world.vm());
auto& proxy = jsWindowProxy(world);
auto& state = *proxy.window()->globalExec();
auto& promise = JSExecState::loadModule(state, sourceCode.jsSourceCode(), JSC::JSScriptFetcher::create(state.vm(), { &moduleScript }));
setupModuleScriptHandlers(moduleScript, promise, world);
}
void ScriptController::loadModuleScript(LoadableModuleScript& moduleScript, const ScriptSourceCode& sourceCode)
{
loadModuleScriptInWorld(moduleScript, sourceCode, mainThreadNormalWorld());
}
JSC::JSValue ScriptController::linkAndEvaluateModuleScriptInWorld(LoadableModuleScript& moduleScript, DOMWrapperWorld& world)
{
JSLockHolder lock(world.vm());
auto& proxy = jsWindowProxy(world);
auto& state = *proxy.window()->globalExec();
Ref<Frame> protector(m_frame);
NakedPtr<JSC::Exception> evaluationException;
auto returnValue = JSExecState::linkAndEvaluateModule(state, Identifier::fromUid(&state.vm(), moduleScript.moduleKey()), jsUndefined(), evaluationException);
if (evaluationException) {
reportException(&state, evaluationException, nullptr);
return jsUndefined();
}
return returnValue;
}
JSC::JSValue ScriptController::linkAndEvaluateModuleScript(LoadableModuleScript& moduleScript)
{
return linkAndEvaluateModuleScriptInWorld(moduleScript, mainThreadNormalWorld());
}
JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord, DOMWrapperWorld& world)
{
JSLockHolder lock(world.vm());
const auto& jsSourceCode = moduleRecord.sourceCode();
auto& proxy = jsWindowProxy(world);
auto& state = *proxy.window()->globalExec();
SetForScope<const String*> sourceURLScope(m_sourceURL, &sourceURL.string());
Ref<Frame> protector(m_frame);
auto cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, jsSourceCode.firstLine().oneBasedInt());
auto returnValue = moduleRecord.evaluate(&state);
InspectorInstrumentation::didEvaluateScript(cookie, m_frame);
return returnValue;
}
JSC::JSValue ScriptController::evaluateModule(const URL& sourceURL, JSModuleRecord& moduleRecord)
{
return evaluateModule(sourceURL, moduleRecord, mainThreadNormalWorld());
}
Ref<DOMWrapperWorld> ScriptController::createWorld()
{
return DOMWrapperWorld::create(commonVM());
}
void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds)
{
static_cast<JSVMClientData*>(commonVM().clientData)->getAllWorlds(worlds);
}
void ScriptController::initScriptForWindowProxy(JSWindowProxy& windowProxy)
{
auto& world = windowProxy.world();
jsCast<JSDOMWindow*>(windowProxy.window())->updateDocument();
if (Document* document = m_frame.document())
document->contentSecurityPolicy()->didCreateWindowProxy(windowProxy);
if (Page* page = m_frame.page()) {
windowProxy.attachDebugger(page->debugger());
windowProxy.window()->setProfileGroup(page->group().identifier());
windowProxy.window()->setConsoleClient(&page->console());
}
m_frame.loader().dispatchDidClearWindowObjectInWorld(world);
}
static Identifier jsValueToModuleKey(ExecState* exec, JSValue value)
{
if (value.isSymbol())
return Identifier::fromUid(jsCast<Symbol*>(value)->privateName());
ASSERT(value.isString());
return asString(value)->toIdentifier(exec);
}
void ScriptController::setupModuleScriptHandlers(LoadableModuleScript& moduleScriptRef, JSInternalPromise& promise, DOMWrapperWorld& world)
{
auto& proxy = jsWindowProxy(world);
auto& state = *proxy.window()->globalExec();
RefPtr<LoadableModuleScript> moduleScript(&moduleScriptRef);
auto& fulfillHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) {
Identifier moduleKey = jsValueToModuleKey(exec, exec->argument(0));
moduleScript->notifyLoadCompleted(*moduleKey.impl());
return JSValue::encode(jsUndefined());
});
auto& rejectHandler = *JSNativeStdFunction::create(state.vm(), proxy.window(), 1, String(), [moduleScript](ExecState* exec) {
VM& vm = exec->vm();
JSValue errorValue = exec->argument(0);
if (errorValue.isObject()) {
auto* object = JSC::asObject(errorValue);
if (JSValue failureKindValue = object->getDirect(vm, static_cast<JSVMClientData&>(*vm.clientData).builtinNames().failureKindPrivateName())) {
switch (static_cast<ModuleFetchFailureKind>(failureKindValue.asInt32())) {
case ModuleFetchFailureKind::WasErrored:
moduleScript->notifyLoadFailed(LoadableScript::Error {
LoadableScript::ErrorType::CachedScript,
WTF::nullopt
});
break;
case ModuleFetchFailureKind::WasCanceled:
moduleScript->notifyLoadWasCanceled();
break;
}
return JSValue::encode(jsUndefined());
}
}
auto scope = DECLARE_CATCH_SCOPE(vm);
moduleScript->notifyLoadFailed(LoadableScript::Error {
LoadableScript::ErrorType::CachedScript,
LoadableScript::ConsoleMessage {
MessageSource::JS,
MessageLevel::Error,
retrieveErrorMessage(*exec, vm, errorValue, scope),
}
});
return JSValue::encode(jsUndefined());
});
promise.then(&state, &fulfillHandler, &rejectHandler);
}
WindowProxy& ScriptController::windowProxy()
{
return m_frame.windowProxy();
}
JSWindowProxy& ScriptController::jsWindowProxy(DOMWrapperWorld& world)
{
auto* jsWindowProxy = m_frame.windowProxy().jsWindowProxy(world);
ASSERT_WITH_MESSAGE(jsWindowProxy, "The JSWindowProxy can only be null if the frame has been destroyed");
return *jsWindowProxy;
}
TextPosition ScriptController::eventHandlerPosition() const
{
auto* parser = m_frame.document()->scriptableDocumentParser();
if (parser)
return parser->textPosition();
return TextPosition();
}
void ScriptController::enableEval()
{
auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
if (!jsWindowProxy)
return;
jsWindowProxy->window()->setEvalEnabled(true);
}
void ScriptController::enableWebAssembly()
{
auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
if (!jsWindowProxy)
return;
jsWindowProxy->window()->setWebAssemblyEnabled(true);
}
void ScriptController::disableEval(const String& errorMessage)
{
auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
if (!jsWindowProxy)
return;
jsWindowProxy->window()->setEvalEnabled(false, errorMessage);
}
void ScriptController::disableWebAssembly(const String& errorMessage)
{
auto* jsWindowProxy = windowProxy().existingJSWindowProxy(mainThreadNormalWorld());
if (!jsWindowProxy)
return;
jsWindowProxy->window()->setWebAssemblyEnabled(false, errorMessage);
}
bool ScriptController::canAccessFromCurrentOrigin(Frame* frame)
{
auto* state = JSExecState::currentState();
if (!state)
return true;
return BindingSecurity::shouldAllowAccessToFrame(state, frame);
}
void ScriptController::updateDocument()
{
for (auto& jsWindowProxy : windowProxy().jsWindowProxiesAsVector()) {
JSLockHolder lock(jsWindowProxy->world().vm());
jsCast<JSDOMWindow*>(jsWindowProxy->window())->updateDocument();
}
}
Bindings::RootObject* ScriptController::cacheableBindingRootObject()
{
if (!canExecuteScripts(NotAboutToExecuteScript))
return nullptr;
if (!m_cacheableBindingRootObject) {
JSLockHolder lock(commonVM());
m_cacheableBindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld()));
}
return m_cacheableBindingRootObject.get();
}
Bindings::RootObject* ScriptController::bindingRootObject()
{
if (!canExecuteScripts(NotAboutToExecuteScript))
return nullptr;
if (!m_bindingRootObject) {
JSLockHolder lock(commonVM());
m_bindingRootObject = Bindings::RootObject::create(nullptr, globalObject(pluginWorld()));
}
return m_bindingRootObject.get();
}
Ref<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
{
auto it = m_rootObjects.find(nativeHandle);
if (it != m_rootObjects.end())
return it->value.copyRef();
auto rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
m_rootObjects.set(nativeHandle, rootObject.copyRef());
return rootObject;
}
void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*>>& result)
{
for (auto& jsWindowProxy : windowProxy().jsWindowProxiesAsVector()) {
auto* exec = jsWindowProxy->window()->globalExec();
auto* origin = &downcast<DOMWindow>(jsWindowProxy->wrapped()).document()->securityOrigin();
result.append(std::make_pair(exec, origin));
}
}
#if ENABLE(NETSCAPE_PLUGIN_API)
NPObject* ScriptController::windowScriptNPObject()
{
if (!m_windowScriptNPObject) {
JSLockHolder lock(commonVM());
if (canExecuteScripts(NotAboutToExecuteScript)) {
auto* window = jsWindowProxy(pluginWorld()).window();
ASSERT(window);
Bindings::RootObject* root = bindingRootObject();
m_windowScriptNPObject = _NPN_CreateScriptObject(0, window, root);
} else {
m_windowScriptNPObject = _NPN_CreateNoScriptObject();
}
}
return m_windowScriptNPObject;
}
#endif
#if !PLATFORM(COCOA)
RefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget)
{
if (!is<PluginViewBase>(*widget))
return nullptr;
return downcast<PluginViewBase>(*widget).bindingInstance();
}
#endif
JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
{
if (!canExecuteScripts(NotAboutToExecuteScript))
return nullptr;
JSLockHolder lock(commonVM());
auto* globalObj = globalObject(pluginWorld());
JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
if (!jsElementValue || !jsElementValue.isObject())
return nullptr;
return jsElementValue.getObject();
}
#if !PLATFORM(COCOA)
void ScriptController::updatePlatformScriptObjects()
{
}
void ScriptController::disconnectPlatformScriptObjects()
{
}
#endif
void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
{
auto it = m_rootObjects.find(nativeHandle);
if (it == m_rootObjects.end())
return;
it->value->invalidate();
m_rootObjects.remove(it);
}
void ScriptController::clearScriptObjects()
{
JSLockHolder lock(commonVM());
for (auto& rootObject : m_rootObjects.values())
rootObject->invalidate();
m_rootObjects.clear();
if (m_bindingRootObject) {
m_bindingRootObject->invalidate();
m_bindingRootObject = nullptr;
}
#if ENABLE(NETSCAPE_PLUGIN_API)
if (m_windowScriptNPObject) {
_NPN_DeallocateObject(m_windowScriptNPObject);
m_windowScriptNPObject = nullptr;
}
#endif
}
JSValue ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails)
{
UserGestureIndicator gestureIndicator(forceUserGesture ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt);
ScriptSourceCode sourceCode(script, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset()));
if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
return { };
return evaluateInWorld(sourceCode, world, exceptionDetails);
}
bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason)
{
if (reason == AboutToExecuteScript)
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed() || !isInWebProcess());
if (m_frame.document() && m_frame.document()->isSandboxed(SandboxScripts)) {
if (reason == AboutToExecuteScript || reason == AboutToCreateEventListener)
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.");
return false;
}
if (!m_frame.page())
return false;
return m_frame.loader().client().allowScript(m_frame.settings().isScriptEnabled());
}
JSValue ScriptController::executeScript(const String& script, bool forceUserGesture, ExceptionDetails* exceptionDetails)
{
UserGestureIndicator gestureIndicator(forceUserGesture ? Optional<ProcessingUserGestureState>(ProcessingUserGesture) : WTF::nullopt);
return executeScript(ScriptSourceCode(script, URL(m_frame.document()->url()), TextPosition(), JSC::SourceProviderSourceType::Program, CachedScriptFetcher::create(m_frame.document()->charset())), exceptionDetails);
}
JSValue ScriptController::executeScript(const ScriptSourceCode& sourceCode, ExceptionDetails* exceptionDetails)
{
if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
return { };
Ref<Frame> protector(m_frame);
return evaluate(sourceCode, exceptionDetails);
}
bool ScriptController::executeIfJavaScriptURL(const URL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL)
{
if (!WTF::protocolIsJavaScript(url))
return false;
if (!m_frame.page() || !m_frame.document()->contentSecurityPolicy()->allowJavaScriptURLs(m_frame.document()->url(), eventHandlerPosition().m_line))
return true;
Ref<Frame> protector(m_frame);
RefPtr<Document> ownerDocument(m_frame.document());
const int javascriptSchemeLength = sizeof("javascript:") - 1;
String decodedURL = decodeURLEscapeSequences(url.string());
auto result = executeScript(decodedURL.substring(javascriptSchemeLength));
if (!m_frame.page())
return true;
String scriptResult;
if (!result || !result.getString(jsWindowProxy(mainThreadNormalWorld()).window()->globalExec(), scriptResult))
return true;
if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) {
ASSERT(m_frame.document()->loader());
if (RefPtr<DocumentLoader> loader = m_frame.document()->loader())
loader->writer().replaceDocument(scriptResult, ownerDocument.get());
}
return true;
}
}