blob: c88c738763d0ec4d0b22652449e35c5d085017de [file] [log] [blame]
/*
* Copyright (C) 2008, 2009 Google Inc. All rights reserved.
* Copyright (C) 2009 Apple Inc. All rights reserved.
* Copyright (C) 2014 Opera Software ASA. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/BindingSecurity.h"
#include "bindings/core/v8/ScriptCallStack.h"
#include "bindings/core/v8/ScriptSourceCode.h"
#include "bindings/core/v8/ScriptValue.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8Event.h"
#include "bindings/core/v8/V8GCController.h"
#include "bindings/core/v8/V8HTMLElement.h"
#include "bindings/core/v8/V8PerContextData.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "bindings/core/v8/V8Window.h"
#include "bindings/core/v8/WindowProxy.h"
#include "core/dom/Document.h"
#include "core/dom/Node.h"
#include "core/dom/ScriptableDocumentParser.h"
#include "core/events/Event.h"
#include "core/events/EventListener.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/loader/NavigationScheduler.h"
#include "core/loader/ProgressTracker.h"
#include "core/plugins/PluginView.h"
#include "platform/Histogram.h"
#include "platform/TraceEvent.h"
#include "platform/UserGestureIndicator.h"
#include "platform/Widget.h"
#include "platform/v8_inspector/public/V8StackTrace.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/Platform.h"
#include "wtf/CurrentTime.h"
#include "wtf/StdLibExtras.h"
#include "wtf/StringExtras.h"
#include "wtf/text/CString.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/TextPosition.h"
namespace blink {
bool ScriptController::canAccessFromCurrentOrigin(v8::Isolate* isolate, Frame* frame)
{
if (!frame)
return false;
return !isolate->InContext() || BindingSecurity::shouldAllowAccessToFrame(isolate, callingDOMWindow(isolate), frame, ReportSecurityError);
}
ScriptController::ScriptController(LocalFrame* frame)
: m_windowProxyManager(WindowProxyManager::create(*frame))
, m_sourceURL(0)
{
}
ScriptController::~ScriptController()
{
}
DEFINE_TRACE(ScriptController)
{
#if ENABLE(OILPAN)
visitor->trace(m_windowProxyManager);
#endif
}
void ScriptController::clearForClose()
{
double start = currentTime();
m_windowProxyManager->clearForClose();
double end = currentTime();
DEFINE_STATIC_LOCAL(CustomCountHistogram, clearForCloseHistogram, ("WebCore.ScriptController.clearForClose", 0, 10000, 50));
clearForCloseHistogram.count((end - start) * 1000);
}
void ScriptController::updateSecurityOrigin(SecurityOrigin* origin)
{
m_windowProxyManager->mainWorldProxy()->updateSecurityOrigin(origin);
Vector<std::pair<ScriptState*, SecurityOrigin*>> isolatedContexts;
m_windowProxyManager->collectIsolatedContexts(isolatedContexts);
for (auto isolatedContext : isolatedContexts)
m_windowProxyManager->windowProxy(isolatedContext.first->world())->updateSecurityOrigin(isolatedContext.second);
}
v8::MaybeLocal<v8::Value> ScriptController::callFunction(v8::Local<v8::Function> function, v8::Local<v8::Value> receiver, int argc, v8::Local<v8::Value> info[])
{
// Keep LocalFrame (and therefore ScriptController) alive.
RefPtrWillBeRawPtr<LocalFrame> protect(frame());
return ScriptController::callFunction(frame()->document(), function, receiver, argc, info, isolate());
}
v8::MaybeLocal<v8::Value> ScriptController::callFunction(ExecutionContext* context, v8::Local<v8::Function> function, v8::Local<v8::Value> receiver, int argc, v8::Local<v8::Value> info[], v8::Isolate* isolate)
{
v8::MaybeLocal<v8::Value> result = V8ScriptRunner::callFunction(function, context, receiver, argc, info, isolate);
return result;
}
v8::Local<v8::Value> ScriptController::executeScriptAndReturnValue(v8::Local<v8::Context> context, const ScriptSourceCode& source, AccessControlStatus accessControlStatus, double* compilationFinishTime)
{
TRACE_EVENT1("devtools.timeline", "EvaluateScript", "data", InspectorEvaluateScriptEvent::data(frame(), source.url().getString(), source.startPosition()));
InspectorInstrumentation::willEvaluateScript(frame()->document());
v8::Local<v8::Value> result;
{
V8CacheOptions v8CacheOptions(V8CacheOptionsDefault);
if (frame()->settings())
v8CacheOptions = frame()->settings()->v8CacheOptions();
// Isolate exceptions that occur when compiling and executing
// the code. These exceptions should not interfere with
// javascript code we might evaluate from C++ when returning
// from here.
v8::TryCatch tryCatch(isolate());
tryCatch.SetVerbose(true);
v8::Local<v8::Script> script;
if (!v8Call(V8ScriptRunner::compileScript(source, isolate(), accessControlStatus, v8CacheOptions), script, tryCatch))
return result;
if (compilationFinishTime) {
*compilationFinishTime = WTF::monotonicallyIncreasingTime();
}
// Keep LocalFrame (and therefore ScriptController) alive.
RefPtrWillBeRawPtr<LocalFrame> protect(frame());
if (!v8Call(V8ScriptRunner::runCompiledScript(isolate(), script, frame()->document()), result, tryCatch))
return result;
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", InspectorUpdateCountersEvent::data());
return result;
}
bool ScriptController::initializeMainWorld()
{
if (m_windowProxyManager->mainWorldProxy()->isContextInitialized())
return false;
return windowProxy(DOMWrapperWorld::mainWorld())->isContextInitialized();
}
WindowProxy* ScriptController::existingWindowProxy(DOMWrapperWorld& world)
{
return m_windowProxyManager->existingWindowProxy(world);
}
WindowProxy* ScriptController::windowProxy(DOMWrapperWorld& world)
{
WindowProxy* windowProxy = m_windowProxyManager->windowProxy(world);
if (!windowProxy->isContextInitialized() && windowProxy->initializeIfNeeded() && world.isMainWorld())
frame()->loader().dispatchDidClearWindowObjectInMainWorld();
// FIXME: There are some situations where we can return an uninitialized
// context. This is broken.
return windowProxy;
}
bool ScriptController::shouldBypassMainWorldCSP()
{
v8::HandleScope handleScope(isolate());
v8::Local<v8::Context> context = isolate()->GetCurrentContext();
if (context.IsEmpty() || !toDOMWindow(context))
return false;
DOMWrapperWorld& world = DOMWrapperWorld::current(isolate());
return world.isIsolatedWorld() ? world.isolatedWorldHasContentSecurityPolicy() : false;
}
TextPosition ScriptController::eventHandlerPosition() const
{
ScriptableDocumentParser* parser = frame()->document()->scriptableDocumentParser();
if (parser)
return parser->textPosition();
return TextPosition::minimumPosition();
}
void ScriptController::enableEval()
{
v8::HandleScope handleScope(isolate());
v8::Local<v8::Context> v8Context = m_windowProxyManager->mainWorldProxy()->contextIfInitialized();
if (v8Context.IsEmpty())
return;
v8Context->AllowCodeGenerationFromStrings(true);
}
void ScriptController::disableEval(const String& errorMessage)
{
v8::HandleScope handleScope(isolate());
v8::Local<v8::Context> v8Context = m_windowProxyManager->mainWorldProxy()->contextIfInitialized();
if (v8Context.IsEmpty())
return;
v8Context->AllowCodeGenerationFromStrings(false);
v8Context->SetErrorMessageForCodeGenerationFromStrings(v8String(isolate(), errorMessage));
}
PassRefPtr<SharedPersistent<v8::Object>> ScriptController::createPluginWrapper(Widget* widget)
{
ASSERT(widget);
if (!widget->isPluginView())
return nullptr;
v8::HandleScope handleScope(isolate());
v8::Local<v8::Object> scriptableObject = toPluginView(widget)->scriptableObject(isolate());
if (scriptableObject.IsEmpty())
return nullptr;
return SharedPersistent<v8::Object>::create(scriptableObject, isolate());
}
V8Extensions& ScriptController::registeredExtensions()
{
DEFINE_STATIC_LOCAL(V8Extensions, extensions, ());
return extensions;
}
void ScriptController::registerExtensionIfNeeded(v8::Extension* extension)
{
const V8Extensions& extensions = registeredExtensions();
for (size_t i = 0; i < extensions.size(); ++i) {
if (extensions[i] == extension)
return;
}
v8::RegisterExtension(extension);
registeredExtensions().append(extension);
}
void ScriptController::clearWindowProxy()
{
// V8 binding expects ScriptController::clearWindowProxy only be called
// when a frame is loading a new page. This creates a new context for the new page.
double start = currentTime();
m_windowProxyManager->clearForNavigation();
double end = currentTime();
DEFINE_STATIC_LOCAL(CustomCountHistogram, clearWindowProxyHistogram, ("WebCore.ScriptController.clearWindowProxy", 0, 10000, 50));
clearWindowProxyHistogram.count((end - start) * 1000);
}
void ScriptController::setCaptureCallStackForUncaughtExceptions(v8::Isolate* isolate, bool value)
{
isolate->SetCaptureStackTraceForUncaughtExceptions(value, V8StackTrace::maxCallStackSizeToCapture, stackTraceOptions);
}
void ScriptController::collectIsolatedContexts(Vector<std::pair<ScriptState*, SecurityOrigin*>>& result)
{
m_windowProxyManager->collectIsolatedContexts(result);
}
void ScriptController::updateDocument()
{
// For an uninitialized main window windowProxy, do not incur the cost of context initialization.
if (!m_windowProxyManager->mainWorldProxy()->isGlobalInitialized())
return;
if (!initializeMainWorld())
windowProxy(DOMWrapperWorld::mainWorld())->updateDocument();
}
void ScriptController::namedItemAdded(HTMLDocument* doc, const AtomicString& name)
{
windowProxy(DOMWrapperWorld::mainWorld())->namedItemAdded(doc, name);
}
void ScriptController::namedItemRemoved(HTMLDocument* doc, const AtomicString& name)
{
windowProxy(DOMWrapperWorld::mainWorld())->namedItemRemoved(doc, name);
}
static bool isInPrivateScriptIsolateWorld(v8::Isolate* isolate)
{
v8::Local<v8::Context> context = isolate->GetCurrentContext();
return !context.IsEmpty() && toDOMWindow(context) && DOMWrapperWorld::current(isolate).isPrivateScriptIsolatedWorld();
}
bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason)
{
// For performance reasons, we check isInPrivateScriptIsolateWorld() only if
// canExecuteScripts is going to return false.
if (frame()->document() && frame()->document()->isSandboxed(SandboxScripts)) {
if (isInPrivateScriptIsolateWorld(isolate()))
return true;
// FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
if (reason == AboutToExecuteScript)
frame()->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Blocked script execution in '" + frame()->document()->url().elidedString() + "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set."));
return false;
}
if (frame()->document() && frame()->document()->isViewSource()) {
ASSERT(frame()->document()->getSecurityOrigin()->isUnique());
return true;
}
FrameLoaderClient* client = frame()->loader().client();
if (!client)
return false;
Settings* settings = frame()->settings();
const bool allowed = client->allowScript(settings && settings->scriptEnabled())
|| isInPrivateScriptIsolateWorld(isolate());
if (!allowed && reason == AboutToExecuteScript)
client->didNotAllowScript();
return allowed;
}
bool ScriptController::executeScriptIfJavaScriptURL(const KURL& url)
{
if (!protocolIsJavaScript(url))
return false;
bool shouldBypassMainWorldContentSecurityPolicy = ContentSecurityPolicy::shouldBypassMainWorld(frame()->document());
if (!frame()->page()
|| (!shouldBypassMainWorldContentSecurityPolicy && !frame()->document()->contentSecurityPolicy()->allowJavaScriptURLs(frame()->document()->url(), eventHandlerPosition().m_line)))
return true;
bool progressNotificationsNeeded = frame()->loader().stateMachine()->isDisplayingInitialEmptyDocument() && !frame()->isLoading();
if (progressNotificationsNeeded)
frame()->loader().progress().progressStarted();
// We need to hold onto the LocalFrame here because executing script can
// destroy the frame.
RefPtrWillBeRawPtr<LocalFrame> protect(frame());
RefPtrWillBeRawPtr<Document> ownerDocument(frame()->document());
const int javascriptSchemeLength = sizeof("javascript:") - 1;
bool locationChangeBefore = frame()->navigationScheduler().locationChangePending();
String decodedURL = decodeURLEscapeSequences(url.getString());
v8::HandleScope handleScope(isolate());
v8::Local<v8::Value> result = evaluateScriptInMainWorld(ScriptSourceCode(decodedURL.substring(javascriptSchemeLength)), NotSharableCrossOrigin, DoNotExecuteScriptWhenScriptsDisabled);
// If executing script caused this frame to be removed from the page, we
// don't want to try to replace its document!
if (!frame()->page())
return true;
if (result.IsEmpty() || !result->IsString()) {
if (progressNotificationsNeeded)
frame()->loader().progress().progressCompleted();
return true;
}
String scriptResult = toCoreString(v8::Local<v8::String>::Cast(result));
// We're still in a frame, so there should be a DocumentLoader.
ASSERT(frame()->document()->loader());
if (!locationChangeBefore && frame()->navigationScheduler().locationChangePending())
return true;
frame()->loader().replaceDocumentWhileExecutingJavaScriptURL(scriptResult, ownerDocument.get());
return true;
}
void ScriptController::executeScriptInMainWorld(const String& script, ExecuteScriptPolicy policy)
{
v8::HandleScope handleScope(isolate());
evaluateScriptInMainWorld(ScriptSourceCode(script), NotSharableCrossOrigin, policy);
}
void ScriptController::executeScriptInMainWorld(const ScriptSourceCode& sourceCode, AccessControlStatus accessControlStatus, double* compilationFinishTime)
{
v8::HandleScope handleScope(isolate());
evaluateScriptInMainWorld(sourceCode, accessControlStatus, DoNotExecuteScriptWhenScriptsDisabled, compilationFinishTime);
}
v8::Local<v8::Value> ScriptController::executeScriptInMainWorldAndReturnValue(const ScriptSourceCode& sourceCode, ExecuteScriptPolicy policy)
{
return evaluateScriptInMainWorld(sourceCode, NotSharableCrossOrigin, policy);
}
v8::Local<v8::Value> ScriptController::evaluateScriptInMainWorld(const ScriptSourceCode& sourceCode, AccessControlStatus accessControlStatus, ExecuteScriptPolicy policy, double* compilationFinishTime)
{
if (policy == DoNotExecuteScriptWhenScriptsDisabled && !canExecuteScripts(AboutToExecuteScript))
return v8::Local<v8::Value>();
String sourceURL = sourceCode.url();
const String* savedSourceURL = m_sourceURL;
m_sourceURL = &sourceURL;
ScriptState* scriptState = ScriptState::forMainWorld(frame());
if (!scriptState)
return v8::Local<v8::Value>();
v8::EscapableHandleScope handleScope(isolate());
ScriptState::Scope scope(scriptState);
RefPtrWillBeRawPtr<LocalFrame> protect(frame());
if (frame()->loader().stateMachine()->isDisplayingInitialEmptyDocument())
frame()->loader().didAccessInitialDocument();
v8::Local<v8::Value> object = executeScriptAndReturnValue(scriptState->context(), sourceCode, accessControlStatus, compilationFinishTime);
m_sourceURL = savedSourceURL;
if (object.IsEmpty())
return v8::Local<v8::Value>();
return handleScope.Escape(object);
}
void ScriptController::executeScriptInIsolatedWorld(int worldID, const WillBeHeapVector<ScriptSourceCode>& sources, int extensionGroup, Vector<v8::Local<v8::Value>>* results)
{
ASSERT(worldID > 0);
RefPtr<DOMWrapperWorld> world = DOMWrapperWorld::ensureIsolatedWorld(isolate(), worldID, extensionGroup);
WindowProxy* isolatedWorldWindowProxy = windowProxy(*world);
if (!isolatedWorldWindowProxy->isContextInitialized())
return;
ScriptState* scriptState = isolatedWorldWindowProxy->getScriptState();
v8::Context::Scope scope(scriptState->context());
v8::Local<v8::Array> resultArray = v8::Array::New(isolate(), sources.size());
for (size_t i = 0; i < sources.size(); ++i) {
v8::Local<v8::Value> evaluationResult = executeScriptAndReturnValue(scriptState->context(), sources[i]);
if (evaluationResult.IsEmpty())
evaluationResult = v8::Local<v8::Value>::New(isolate(), v8::Undefined(isolate()));
if (!v8CallBoolean(resultArray->Set(scriptState->context(), v8::Integer::New(scriptState->isolate(), i), evaluationResult)))
return;
}
if (results) {
for (size_t i = 0; i < resultArray->Length(); ++i) {
v8::Local<v8::Value> value;
if (!resultArray->Get(scriptState->context(), i).ToLocal(&value))
return;
results->append(value);
}
}
}
} // namespace blink