blob: 5947a4ffbb12a94b4e8e271148c1b0f32e3c8792 [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/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/inspector/MainThreadDebugger.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/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(
currentDOMWindow(isolate), frame,
BindingSecurity::ErrorReportOption::Report);
}
ScriptController::ScriptController(LocalFrame* frame)
: m_windowProxyManager(WindowProxyManager::create(*frame)) {}
DEFINE_TRACE(ScriptController) {
visitor->trace(m_windowProxyManager);
}
void ScriptController::clearForClose() {
m_windowProxyManager->clearForClose();
MainThreadDebugger::instance()->didClearContextsForFrame(frame());
}
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::Local<v8::Value> ScriptController::executeScriptAndReturnValue(
v8::Local<v8::Context> context,
const ScriptSourceCode& source,
AccessControlStatus accessControlStatus) {
TRACE_EVENT1("devtools.timeline", "EvaluateScript", "data",
InspectorEvaluateScriptEvent::data(
frame(), source.url().getString(), source.startPosition()));
InspectorInstrumentation::NativeBreakpoint nativeBreakpoint(
frame()->document(), "scriptFirstStatement", false);
v8::Local<v8::Value> result;
{
V8CacheOptions v8CacheOptions(V8CacheOptionsDefault);
if (frame()->settings())
v8CacheOptions = frame()->settings()->v8CacheOptions();
if (source.resource() &&
!source.resource()->response().cacheStorageCacheName().isNull()) {
switch (frame()->settings()->v8CacheStrategiesForCacheStorage()) {
case V8CacheStrategiesForCacheStorage::None:
v8CacheOptions = V8CacheOptionsNone;
break;
case V8CacheStrategiesForCacheStorage::Normal:
v8CacheOptions = V8CacheOptionsCode;
break;
case V8CacheStrategiesForCacheStorage::Default:
case V8CacheStrategiesForCacheStorage::Aggressive:
v8CacheOptions = V8CacheOptionsAlways;
break;
}
}
// 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 (!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.
m_windowProxyManager->clearForNavigation();
MainThreadDebugger::instance()->didClearContextsForFrame(frame());
}
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();
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);
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) {
v8::HandleScope handleScope(isolate());
evaluateScriptInMainWorld(sourceCode, accessControlStatus,
DoNotExecuteScriptWhenScriptsDisabled);
}
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) {
if (policy == DoNotExecuteScriptWhenScriptsDisabled &&
!canExecuteScripts(AboutToExecuteScript))
return v8::Local<v8::Value>();
ScriptState* scriptState = ScriptState::forMainWorld(frame());
if (!scriptState)
return v8::Local<v8::Value>();
v8::EscapableHandleScope handleScope(isolate());
ScriptState::Scope scope(scriptState);
if (frame()->loader().stateMachine()->isDisplayingInitialEmptyDocument())
frame()->loader().didAccessInitialDocument();
v8::Local<v8::Value> object = executeScriptAndReturnValue(
scriptState->context(), sourceCode, accessControlStatus);
if (object.IsEmpty())
return v8::Local<v8::Value>();
return handleScope.Escape(object);
}
void ScriptController::executeScriptInIsolatedWorld(
int worldID,
const HeapVector<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->CreateDataProperty(scriptState->context(),
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