| /* |
| * 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 "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/callback_helpers.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_source_code.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h" |
| #include "third_party/blink/renderer/bindings/core/v8/window_proxy.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/html/html_plugin_element.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/inspector/main_thread_debugger.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_loader.h" |
| #include "third_party/blink/renderer/core/loader/progress_tracker.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/script/classic_script.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| |
| namespace blink { |
| |
| void ScriptController::Trace(Visitor* visitor) const { |
| visitor->Trace(window_); |
| visitor->Trace(window_proxy_manager_); |
| } |
| |
| void ScriptController::UpdateSecurityOrigin( |
| const SecurityOrigin* security_origin) { |
| window_proxy_manager_->UpdateSecurityOrigin(security_origin); |
| } |
| |
| TextPosition ScriptController::EventHandlerPosition() const { |
| ScriptableDocumentParser* parser = |
| window_->document()->GetScriptableDocumentParser(); |
| if (parser) |
| return parser->GetTextPosition(); |
| return TextPosition::MinimumPosition(); |
| } |
| |
| void ScriptController::EnableEval() { |
| SetEvalForWorld(DOMWrapperWorld::MainWorld(), true /* allow_eval */, |
| g_empty_string /* error_message */); |
| } |
| |
| void ScriptController::DisableEval(const String& error_message) { |
| SetEvalForWorld(DOMWrapperWorld::MainWorld(), false /* allow_eval */, |
| error_message); |
| } |
| |
| void ScriptController::DisableEvalForIsolatedWorld( |
| int32_t world_id, |
| const String& error_message) { |
| DCHECK(DOMWrapperWorld::IsIsolatedWorldId(world_id)); |
| scoped_refptr<DOMWrapperWorld> world = |
| DOMWrapperWorld::EnsureIsolatedWorld(GetIsolate(), world_id); |
| SetEvalForWorld(*world, false /* allow_eval */, error_message); |
| } |
| |
| void ScriptController::SetEvalForWorld(DOMWrapperWorld& world, |
| bool allow_eval, |
| const String& error_message) { |
| v8::HandleScope handle_scope(GetIsolate()); |
| LocalWindowProxy* proxy = |
| world.IsMainWorld() |
| ? window_proxy_manager_->MainWorldProxyMaybeUninitialized() |
| : WindowProxy(world); |
| |
| v8::Local<v8::Context> v8_context = proxy->ContextIfInitialized(); |
| if (v8_context.IsEmpty()) |
| return; |
| |
| v8_context->AllowCodeGenerationFromStrings(allow_eval); |
| if (allow_eval) |
| return; |
| |
| v8_context->SetErrorMessageForCodeGenerationFromStrings( |
| V8String(GetIsolate(), error_message)); |
| } |
| |
| namespace { |
| |
| Vector<const char*>& RegisteredExtensionNames() { |
| DEFINE_STATIC_LOCAL(Vector<const char*>, extension_names, ()); |
| return extension_names; |
| } |
| |
| } // namespace |
| |
| void ScriptController::RegisterExtensionIfNeeded( |
| std::unique_ptr<v8::Extension> extension) { |
| for (const auto* extension_name : RegisteredExtensionNames()) { |
| if (!strcmp(extension_name, extension->name())) |
| return; |
| } |
| RegisteredExtensionNames().push_back(extension->name()); |
| v8::RegisterExtension(std::move(extension)); |
| } |
| |
| v8::ExtensionConfiguration ScriptController::ExtensionsFor( |
| const ExecutionContext* context) { |
| if (context->ShouldInstallV8Extensions()) { |
| return v8::ExtensionConfiguration(RegisteredExtensionNames().size(), |
| RegisteredExtensionNames().data()); |
| } |
| return v8::ExtensionConfiguration(); |
| } |
| |
| void ScriptController::UpdateDocument() { |
| window_proxy_manager_->UpdateDocument(); |
| } |
| |
| void ScriptController::ExecuteJavaScriptURL( |
| const KURL& url, |
| network::mojom::CSPDisposition csp_disposition, |
| const DOMWrapperWorld* world_for_csp) { |
| DCHECK(url.ProtocolIsJavaScript()); |
| |
| const int kJavascriptSchemeLength = sizeof("javascript:") - 1; |
| String script_source = DecodeURLEscapeSequences( |
| url.GetString(), DecodeURLMode::kUTF8OrIsomorphic); |
| |
| if (!window_->GetFrame()) |
| return; |
| |
| auto* policy = window_->GetContentSecurityPolicyForWorld(world_for_csp); |
| if (csp_disposition == network::mojom::CSPDisposition::CHECK && |
| !policy->AllowInline(ContentSecurityPolicy::InlineType::kNavigation, |
| nullptr, script_source, String() /* nonce */, |
| window_->Url(), EventHandlerPosition().line_)) { |
| return; |
| } |
| |
| // TODO(crbug.com/896041): Investigate how trusted type checks can be |
| // implemented for isolated worlds. |
| const bool should_bypass_trusted_type_check = |
| csp_disposition == network::mojom::CSPDisposition::DO_NOT_CHECK || |
| ContentSecurityPolicy::ShouldBypassMainWorld(world_for_csp); |
| script_source = script_source.Substring(kJavascriptSchemeLength); |
| if (!should_bypass_trusted_type_check) { |
| script_source = TrustedTypesCheckForJavascriptURLinNavigation( |
| script_source, window_.Get()); |
| if (script_source.IsEmpty()) |
| return; |
| } |
| |
| bool had_navigation_before = |
| window_->GetFrame()->Loader().HasProvisionalNavigation(); |
| |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol |
| // Step 6. "Let baseURL be settings's API base URL." [spec text] |
| const KURL base_url = window_->BaseURL(); |
| |
| // Step 7. "Let script be the result of creating a classic script given |
| // scriptSource, settings, baseURL, and the default classic script fetch |
| // options." [spec text] |
| // |
| // We pass |SanitizeScriptErrors::kDoNotSanitize| because |muted errors| is |
| // false by default. |
| ClassicScript* script = MakeGarbageCollected<ClassicScript>( |
| ScriptSourceCode(script_source, ScriptSourceLocationType::kJavascriptUrl), |
| base_url, ScriptFetchOptions(), SanitizeScriptErrors::kDoNotSanitize); |
| |
| DCHECK_EQ(&window_->GetScriptController(), this); |
| v8::HandleScope handle_scope(GetIsolate()); |
| v8::Local<v8::Value> v8_result = script->RunScriptAndReturnValue(window_); |
| UseCounter::Count(window_.Get(), WebFeature::kExecutedJavaScriptURL); |
| |
| // If executing script caused this frame to be removed from the page, we |
| // don't want to try to replace its document! |
| if (!window_->GetFrame()) |
| return; |
| // If a navigation begins during the javascript: url's execution, ignore |
| // the return value of the script. Otherwise, replacing the document with a |
| // string result would cancel the navigation. |
| // TODO(crbug.com/1085514): Consider making HasProvisionalNavigation return |
| // true when a form submission is pending instead of having a separate check |
| // for form submissions here. |
| if (!had_navigation_before && |
| (window_->GetFrame()->Loader().HasProvisionalNavigation() || |
| window_->GetFrame()->IsFormSubmissionPending())) { |
| return; |
| } |
| if (v8_result.IsEmpty() || !v8_result->IsString()) |
| return; |
| |
| UseCounter::Count(window_.Get(), |
| WebFeature::kReplaceDocumentViaJavaScriptURL); |
| |
| auto* previous_document_loader = |
| window_->GetFrame()->Loader().GetDocumentLoader(); |
| DCHECK(previous_document_loader); |
| auto params = |
| previous_document_loader->CreateWebNavigationParamsToCloneDocument(); |
| String result = ToCoreString(v8::Local<v8::String>::Cast(v8_result)); |
| WebNavigationParams::FillStaticResponse(params.get(), "text/html", "UTF-8", |
| StringUTF8Adaptor(result)); |
| window_->GetFrame()->Loader().CommitNavigation(std::move(params), nullptr, |
| CommitReason::kJavascriptUrl); |
| } |
| |
| v8::Local<v8::Value> ScriptController::EvaluateMethodInMainWorld( |
| v8::Local<v8::Function> function, |
| v8::Local<v8::Value> receiver, |
| int argc, |
| v8::Local<v8::Value> argv[]) { |
| if (!CanExecuteScript( |
| ExecuteScriptPolicy::kDoNotExecuteScriptWhenScriptsDisabled)) { |
| return v8::Local<v8::Value>(); |
| } |
| |
| // |script_state->GetContext()| should be initialized already due to the |
| // WindowProxy() call inside ToScriptStateForMainWorld(). |
| ScriptState* script_state = ToScriptStateForMainWorld(window_->GetFrame()); |
| if (!script_state) { |
| return v8::Local<v8::Value>(); |
| } |
| DCHECK_EQ(script_state->GetIsolate(), GetIsolate()); |
| |
| v8::Context::Scope scope(script_state->GetContext()); |
| v8::EscapableHandleScope handle_scope(GetIsolate()); |
| |
| v8::TryCatch try_catch(GetIsolate()); |
| try_catch.SetVerbose(true); |
| |
| ExecutionContext* executionContext = ExecutionContext::From(script_state); |
| |
| v8::MaybeLocal<v8::Value> resultObj = V8ScriptRunner::CallFunction( |
| function, executionContext, receiver, argc, |
| static_cast<v8::Local<v8::Value>*>(argv), ToIsolate(window_->GetFrame())); |
| |
| if (resultObj.IsEmpty()) |
| return v8::Local<v8::Value>(); |
| |
| return handle_scope.Escape(resultObj.ToLocalChecked()); |
| } |
| |
| bool ScriptController::CanExecuteScript(ExecuteScriptPolicy policy) { |
| if (policy == ExecuteScriptPolicy::kDoNotExecuteScriptWhenScriptsDisabled && |
| !window_->CanExecuteScripts(kAboutToExecuteScript)) |
| return false; |
| |
| if (window_->document()->IsInitialEmptyDocument()) |
| window_->GetFrame()->Loader().DidAccessInitialDocument(); |
| |
| return true; |
| } |
| |
| scoped_refptr<DOMWrapperWorld> |
| ScriptController::CreateNewInspectorIsolatedWorld(const String& world_name) { |
| scoped_refptr<DOMWrapperWorld> world = DOMWrapperWorld::Create( |
| GetIsolate(), DOMWrapperWorld::WorldType::kInspectorIsolated); |
| // Bail out if we could not create an isolated world. |
| if (!world) |
| return nullptr; |
| if (!world_name.IsEmpty()) { |
| DOMWrapperWorld::SetNonMainWorldHumanReadableName(world->GetWorldId(), |
| world_name); |
| } |
| // Make sure the execution context exists. |
| WindowProxy(*world); |
| return world; |
| } |
| |
| } // namespace blink |