| /* |
| * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved. |
| * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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/core/frame/local_dom_window.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/trace_event/trace_id_helper.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "build/build_config.h" |
| #include "cc/input/snap_selection_strategy.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/navigation/impression.h" |
| #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h" |
| #include "third_party/blink/public/mojom/permissions_policy/policy_disposition.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/public/web/web_picture_in_picture_window_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/binding_security.h" |
| #include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h" |
| #include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_to_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_void_function.h" |
| #include "third_party/blink/renderer/bindings/core/v8/window_proxy.h" |
| #include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_context.h" |
| #include "third_party/blink/renderer/core/aom/computed_accessible_node.h" |
| #include "third_party/blink/renderer/core/css/css_computed_style_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_rule_list.h" |
| #include "third_party/blink/renderer/core/css/dom_window_css.h" |
| #include "third_party/blink/renderer/core/css/media_query_list.h" |
| #include "third_party/blink/renderer/core/css/media_query_matcher.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver.h" |
| #include "third_party/blink/renderer/core/css/style_media.h" |
| #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h" |
| #include "third_party/blink/renderer/core/dom/document_init.h" |
| #include "third_party/blink/renderer/core/dom/events/add_event_listener_options_resolved.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" |
| #include "third_party/blink/renderer/core/dom/frame_request_callback_collection.h" |
| #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" |
| #include "third_party/blink/renderer/core/dom/scripted_idle_task_controller.h" |
| #include "third_party/blink/renderer/core/editing/editor.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/ime/input_method_controller.h" |
| #include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h" |
| #include "third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.h" |
| #include "third_party/blink/renderer/core/events/hash_change_event.h" |
| #include "third_party/blink/renderer/core/events/message_event.h" |
| #include "third_party/blink/renderer/core/events/page_transition_event.h" |
| #include "third_party/blink/renderer/core/events/pop_state_event.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h" |
| #include "third_party/blink/renderer/core/execution_context/window_agent.h" |
| #include "third_party/blink/renderer/core/frame/attribution_src_loader.h" |
| #include "third_party/blink/renderer/core/frame/bar_prop.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/document_policy_violation_report_body.h" |
| #include "third_party/blink/renderer/core/frame/dom_visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/event_handler_registry.h" |
| #include "third_party/blink/renderer/core/frame/external.h" |
| #include "third_party/blink/renderer/core/frame/frame_console.h" |
| #include "third_party/blink/renderer/core/frame/history.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/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/navigator.h" |
| #include "third_party/blink/renderer/core/frame/permissions_policy_violation_report_body.h" |
| #include "third_party/blink/renderer/core/frame/report.h" |
| #include "third_party/blink/renderer/core/frame/reporting_context.h" |
| #include "third_party/blink/renderer/core/frame/screen.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/viewport_data.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element_registry.h" |
| #include "third_party/blink/renderer/core/html/fenced_frame/fence.h" |
| #include "third_party/blink/renderer/core/html/forms/form_controller.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html/plugin_document.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_audits_issue.h" |
| #include "third_party/blink/renderer/core/inspector/inspector_issue_storage.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/layout/adjust_for_absolute_zoom.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/loader/frame_load_request.h" |
| #include "third_party/blink/renderer/core/navigation_api/navigation_api.h" |
| #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/create_window.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/core/script/modulator.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_types.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| #include "third_party/blink/renderer/core/timing/dom_window_performance.h" |
| #include "third_party/blink/renderer/core/timing/soft_navigation_heuristics.h" |
| #include "third_party/blink/renderer/core/timing/window_performance.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h" |
| #include "third_party/blink/renderer/core/view_transition/view_transition_supplement.h" |
| #include "third_party/blink/renderer/platform/back_forward_cache_buffer_limit_tracker.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_messages.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/bindings/source_location.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/network/network_state_notifier.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/dummy_schedulers.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h" |
| #include "third_party/blink/renderer/platform/storage/blink_storage_key.h" |
| #include "third_party/blink/renderer/platform/timer.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/widget/frame_widget.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/uuid.h" |
| #include "ui/display/screen_info.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| bool IsRunningMicrotasks(ScriptState* script_state) { |
| if (auto* microtask_queue = ToMicrotaskQueue(script_state)) |
| return microtask_queue->IsRunningMicrotasks(); |
| return v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate()); |
| } |
| |
| void SetCurrentTaskAsCallbackParent( |
| CallbackFunctionWithTaskAttributionBase* callback) { |
| ScriptState* script_state = callback->CallbackRelevantScriptState(); |
| auto* tracker = |
| scheduler::TaskAttributionTracker::From(script_state->GetIsolate()); |
| if (tracker && script_state->World().IsMainWorld()) { |
| callback->SetParentTask(tracker->RunningTask()); |
| } |
| } |
| |
| int RequestAnimationFrame(Document* document, |
| V8FrameRequestCallback* callback, |
| bool legacy) { |
| // TODO(crbug.com/1499981): This should be removed once synchronized scrolling |
| // impact is understood. |
| SyncScrollAttemptHeuristic::DidRequestAnimationFrame(); |
| SetCurrentTaskAsCallbackParent(callback); |
| auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback); |
| frame_callback->SetUseLegacyTimeBase(legacy); |
| return document->RequestAnimationFrame(frame_callback); |
| } |
| |
| } // namespace |
| |
| class LocalDOMWindow::NetworkStateObserver final |
| : public GarbageCollected<LocalDOMWindow::NetworkStateObserver>, |
| public NetworkStateNotifier::NetworkStateObserver, |
| public ExecutionContextLifecycleObserver { |
| public: |
| explicit NetworkStateObserver(ExecutionContext* context) |
| : ExecutionContextLifecycleObserver(context) {} |
| |
| void Initialize() { |
| online_observer_handle_ = GetNetworkStateNotifier().AddOnLineObserver( |
| this, GetExecutionContext()->GetTaskRunner(TaskType::kNetworking)); |
| } |
| |
| void OnLineStateChange(bool on_line) override { |
| AtomicString event_name = |
| on_line ? event_type_names::kOnline : event_type_names::kOffline; |
| auto* window = To<LocalDOMWindow>(GetExecutionContext()); |
| window->DispatchEvent(*Event::Create(event_name)); |
| } |
| |
| void ContextDestroyed() override { online_observer_handle_ = nullptr; } |
| |
| void Trace(Visitor* visitor) const override { |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| } |
| |
| private: |
| std::unique_ptr<NetworkStateNotifier::NetworkStateObserverHandle> |
| online_observer_handle_; |
| }; |
| |
| LocalDOMWindow::LocalDOMWindow(LocalFrame& frame, WindowAgent* agent) |
| : DOMWindow(frame), |
| ExecutionContext(agent->isolate(), |
| agent, |
| /*Same value as IsWindow(). is_window=*/true), |
| script_controller_(MakeGarbageCollected<ScriptController>( |
| *this, |
| *static_cast<LocalWindowProxyManager*>( |
| frame.GetWindowProxyManager()))), |
| visualViewport_(MakeGarbageCollected<DOMVisualViewport>(this)), |
| should_print_when_finished_loading_(false), |
| input_method_controller_( |
| MakeGarbageCollected<InputMethodController>(*this, frame)), |
| spell_checker_(MakeGarbageCollected<SpellChecker>(*this)), |
| text_suggestion_controller_( |
| MakeGarbageCollected<TextSuggestionController>(*this)), |
| isolated_world_csp_map_( |
| MakeGarbageCollected< |
| HeapHashMap<int, Member<ContentSecurityPolicy>>>()), |
| token_(frame.GetLocalFrameToken()), |
| post_message_counter_(PostMessagePartition::kSameProcess), |
| network_state_observer_(MakeGarbageCollected<NetworkStateObserver>(this)), |
| closewatcher_stack_( |
| MakeGarbageCollected<CloseWatcher::WatcherStack>(this)), |
| navigation_id_(WTF::CreateCanonicalUUIDString()) {} |
| |
| void LocalDOMWindow::BindContentSecurityPolicy() { |
| DCHECK(!GetContentSecurityPolicy()->IsBound()); |
| GetContentSecurityPolicy()->BindToDelegate( |
| GetContentSecurityPolicyDelegate()); |
| } |
| |
| void LocalDOMWindow::Initialize() { |
| GetAgent()->AttachContext(this); |
| network_state_observer_->Initialize(); |
| } |
| |
| void LocalDOMWindow::ResetWindowAgent(WindowAgent* agent) { |
| GetAgent()->DetachContext(this); |
| ResetAgent(agent); |
| if (document_) { |
| document_->ResetAgent(*agent); |
| } |
| |
| CHECK(GetFrame()); |
| GetFrame()->GetFrameScheduler()->SetAgentClusterId(GetAgentClusterID()); |
| |
| // This is only called on Android WebView, we need to reassign the microtask |
| // queue if there already is one for the associated context. There shouldn't |
| // be any other worlds with Android WebView so using the MainWorld is fine. |
| auto* microtask_queue = agent->event_loop()->microtask_queue(); |
| if (microtask_queue) { |
| v8::HandleScope handle_scope(GetIsolate()); |
| v8::Local<v8::Context> main_world_context = ToV8ContextMaybeEmpty( |
| GetFrame(), DOMWrapperWorld::MainWorld(GetIsolate())); |
| if (!main_world_context.IsEmpty()) |
| main_world_context->SetMicrotaskQueue(microtask_queue); |
| } |
| |
| GetAgent()->AttachContext(this); |
| } |
| |
| void LocalDOMWindow::AcceptLanguagesChanged() { |
| if (navigator_) |
| navigator_->SetLanguagesDirty(); |
| |
| DispatchEvent(*Event::Create(event_type_names::kLanguagechange)); |
| } |
| |
| ScriptValue LocalDOMWindow::event(ScriptState* script_state) { |
| // If current event is null, return undefined. |
| if (!current_event_) { |
| return ScriptValue(script_state->GetIsolate(), |
| v8::Undefined(script_state->GetIsolate())); |
| } |
| |
| return ScriptValue(script_state->GetIsolate(), |
| ToV8Traits<Event>::ToV8(script_state, CurrentEvent())); |
| } |
| |
| Event* LocalDOMWindow::CurrentEvent() const { |
| return current_event_.Get(); |
| } |
| |
| void LocalDOMWindow::SetCurrentEvent(Event* new_event) { |
| current_event_ = new_event; |
| } |
| |
| TrustedTypePolicyFactory* LocalDOMWindow::GetTrustedTypesForWorld( |
| const DOMWrapperWorld& world) const { |
| DCHECK(world.IsMainWorld() || world.IsIsolatedWorld()); |
| DCHECK(IsMainThread()); |
| auto iter = trusted_types_map_.find(&world); |
| if (iter != trusted_types_map_.end()) |
| return iter->value.Get(); |
| return trusted_types_map_ |
| .insert(&world, MakeGarbageCollected<TrustedTypePolicyFactory>( |
| GetExecutionContext())) |
| .stored_value->value; |
| } |
| |
| TrustedTypePolicyFactory* LocalDOMWindow::trustedTypes( |
| ScriptState* script_state) const { |
| return GetTrustedTypesForWorld(script_state->World()); |
| } |
| |
| bool LocalDOMWindow::IsCrossSiteSubframe() const { |
| if (!GetFrame()) |
| return false; |
| if (GetFrame()->IsInFencedFrameTree()) |
| return true; |
| // It'd be nice to avoid the url::Origin temporaries, but that would require |
| // exposing the net internal helper. |
| // TODO: If the helper gets exposed, we could do this without any new |
| // allocations using StringUTF8Adaptor. |
| auto* top_origin = |
| GetFrame()->Tree().Top().GetSecurityContext()->GetSecurityOrigin(); |
| return !net::registry_controlled_domains::SameDomainOrHost( |
| top_origin->ToUrlOrigin(), GetSecurityOrigin()->ToUrlOrigin(), |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| } |
| |
| bool LocalDOMWindow::IsCrossSiteSubframeIncludingScheme() const { |
| if (!GetFrame()) |
| return false; |
| if (GetFrame()->IsInFencedFrameTree()) |
| return true; |
| return top()->GetFrame() && |
| !top() |
| ->GetFrame() |
| ->GetSecurityContext() |
| ->GetSecurityOrigin() |
| ->IsSameSiteWith(GetSecurityContext().GetSecurityOrigin()); |
| } |
| |
| LocalDOMWindow* LocalDOMWindow::From(const ScriptState* script_state) { |
| return blink::ToLocalDOMWindow(script_state); |
| } |
| |
| mojom::blink::V8CacheOptions LocalDOMWindow::GetV8CacheOptions() const { |
| if (LocalFrame* frame = GetFrame()) { |
| if (const Settings* settings = frame->GetSettings()) |
| return settings->GetV8CacheOptions(); |
| } |
| |
| return mojom::blink::V8CacheOptions::kDefault; |
| } |
| |
| bool LocalDOMWindow::IsContextThread() const { |
| return IsMainThread(); |
| } |
| |
| bool LocalDOMWindow::ShouldInstallV8Extensions() const { |
| return GetFrame()->Client()->AllowScriptExtensions(); |
| } |
| |
| ContentSecurityPolicy* LocalDOMWindow::GetContentSecurityPolicyForWorld( |
| const DOMWrapperWorld* world) { |
| if (!world || !world->IsIsolatedWorld()) |
| return GetContentSecurityPolicy(); |
| |
| int32_t world_id = world->GetWorldId(); |
| auto it = isolated_world_csp_map_->find(world_id); |
| if (it != isolated_world_csp_map_->end()) |
| return it->value.Get(); |
| |
| ContentSecurityPolicy* policy = |
| IsolatedWorldCSP::Get().CreateIsolatedWorldCSP(*this, world_id); |
| if (!policy) |
| return GetContentSecurityPolicy(); |
| |
| isolated_world_csp_map_->insert(world_id, policy); |
| return policy; |
| } |
| |
| const KURL& LocalDOMWindow::Url() const { |
| return document()->Url(); |
| } |
| |
| const KURL& LocalDOMWindow::BaseURL() const { |
| return document()->BaseURL(); |
| } |
| |
| KURL LocalDOMWindow::CompleteURL(const String& url) const { |
| return document()->CompleteURL(url); |
| } |
| |
| void LocalDOMWindow::DisableEval(const String& error_message) { |
| GetScriptController().DisableEval(error_message); |
| } |
| |
| void LocalDOMWindow::SetWasmEvalErrorMessage(const String& error_message) { |
| GetScriptController().SetWasmEvalErrorMessage(error_message); |
| } |
| |
| String LocalDOMWindow::UserAgent() const { |
| if (!GetFrame()) |
| return String(); |
| |
| return GetFrame()->Loader().UserAgent(); |
| } |
| |
| UserAgentMetadata LocalDOMWindow::GetUserAgentMetadata() const { |
| return GetFrame()->Loader().UserAgentMetadata().value_or( |
| blink::UserAgentMetadata()); |
| } |
| |
| HttpsState LocalDOMWindow::GetHttpsState() const { |
| // TODO(https://crbug.com/880986): Implement Document's HTTPS state in more |
| // spec-conformant way. |
| return CalculateHttpsState(GetSecurityOrigin()); |
| } |
| |
| ResourceFetcher* LocalDOMWindow::Fetcher() { |
| return document()->Fetcher(); |
| } |
| |
| bool LocalDOMWindow::CanExecuteScripts( |
| ReasonForCallingCanExecuteScripts reason) { |
| if (!GetFrame()) |
| return false; |
| |
| // Detached frames should not be attempting to execute script. |
| DCHECK(!GetFrame()->IsDetached()); |
| |
| // Normally, scripts are not allowed in sandboxed contexts that disallow them. |
| // However, there is an exception for cases when the script should bypass the |
| // main world's CSP (such as for privileged isolated worlds). See |
| // https://crbug.com/811528. |
| if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kScripts) && |
| !ContentSecurityPolicy::ShouldBypassMainWorldDeprecated(this)) { |
| // 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 == kAboutToExecuteScript) { |
| AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kSecurity, |
| mojom::blink::ConsoleMessageLevel::kError, |
| "Blocked script execution in '" + Url().ElidedString() + |
| "' because the document's frame is sandboxed and the " |
| "'allow-scripts' permission is not set.")); |
| } |
| return false; |
| } |
| |
| bool allow_script_renderer = GetFrame()->GetSettings()->GetScriptEnabled(); |
| bool allow_script_content_setting = |
| GetFrame()->GetContentSettings()->allow_script; |
| bool script_enabled = allow_script_renderer && allow_script_content_setting; |
| if (!script_enabled && reason == kAboutToExecuteScript) { |
| WebContentSettingsClient* settings_client = |
| GetFrame()->GetContentSettingsClient(); |
| if (settings_client) { |
| settings_client->DidNotAllowScript(); |
| } |
| } |
| return script_enabled; |
| } |
| |
| String LocalDOMWindow::CheckAndGetJavascriptUrl( |
| const DOMWrapperWorld* world, |
| const KURL& url, |
| Element* element, |
| network::mojom::CSPDisposition csp_disposition) { |
| const int kJavascriptSchemeLength = sizeof("javascript:") - 1; |
| String decoded_url = DecodeURLEscapeSequences( |
| url.GetString(), DecodeURLMode::kUTF8OrIsomorphic); |
| String script_source = decoded_url.Substring(kJavascriptSchemeLength); |
| |
| if (csp_disposition == network::mojom::CSPDisposition::DO_NOT_CHECK) |
| return script_source; |
| |
| // Check the CSP of the caller (the "source browsing context") if required, |
| // as per https://html.spec.whatwg.org/C/#javascript-protocol. |
| if (!GetContentSecurityPolicyForWorld(world)->AllowInline( |
| ContentSecurityPolicy::InlineType::kNavigation, element, decoded_url, |
| String() /* nonce */, Url(), OrdinalNumber::First())) |
| return String(); |
| |
| // TODO(crbug.com/896041): Investigate how trusted type checks can be |
| // implemented for isolated worlds. |
| if (ContentSecurityPolicy::ShouldBypassMainWorldDeprecated(world)) |
| return script_source; |
| |
| // https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-pre-navigation-check |
| // 4.9.1.1. require-trusted-types-for Pre-Navigation check |
| script_source = |
| TrustedTypesCheckForJavascriptURLinNavigation(script_source, this); |
| |
| return script_source; |
| } |
| |
| void LocalDOMWindow::ExceptionThrown(ErrorEvent* event) { |
| MainThreadDebugger::Instance(GetIsolate())->ExceptionThrown(this, event); |
| } |
| |
| // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer |
| String LocalDOMWindow::OutgoingReferrer() const { |
| // Step 3.1: "If environment's global object is a Window object, then" |
| // Step 3.1.1: "Let document be the associated Document of environment's |
| // global object." |
| |
| // Step 3.1.2: "If document's origin is an opaque origin, return no referrer." |
| if (GetSecurityOrigin()->IsOpaque()) |
| return String(); |
| |
| // Step 3.1.3: "While document is an iframe srcdoc document, let document be |
| // document's browsing context's browsing context container's node document." |
| Document* referrer_document = document(); |
| if (LocalFrame* frame = GetFrame()) { |
| while (frame->GetDocument()->IsSrcdocDocument()) { |
| // Srcdoc documents must be local within the containing frame. |
| frame = To<LocalFrame>(frame->Tree().Parent()); |
| // Srcdoc documents cannot be top-level documents, by definition, |
| // because they need to be contained in iframes with the srcdoc. |
| DCHECK(frame); |
| } |
| referrer_document = frame->GetDocument(); |
| } |
| |
| // Step: 3.1.4: "Let referrerSource be document's URL." |
| return referrer_document->Url().StrippedForUseAsReferrer(); |
| } |
| |
| CoreProbeSink* LocalDOMWindow::GetProbeSink() { |
| return probe::ToCoreProbeSink(GetFrame()); |
| } |
| |
| const BrowserInterfaceBrokerProxy& LocalDOMWindow::GetBrowserInterfaceBroker() |
| const { |
| if (!GetFrame()) |
| return GetEmptyBrowserInterfaceBroker(); |
| |
| return GetFrame()->GetBrowserInterfaceBroker(); |
| } |
| |
| FrameOrWorkerScheduler* LocalDOMWindow::GetScheduler() { |
| if (GetFrame()) |
| return GetFrame()->GetFrameScheduler(); |
| if (!detached_scheduler_) |
| detached_scheduler_ = scheduler::CreateDummyFrameScheduler(GetIsolate()); |
| return detached_scheduler_.get(); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> LocalDOMWindow::GetTaskRunner( |
| TaskType type) { |
| if (GetFrame()) |
| return GetFrame()->GetTaskRunner(type); |
| TRACE_EVENT_INSTANT("blink", |
| "LocalDOMWindow::GetTaskRunner_ThreadTaskRunner"); |
| // In most cases, the ExecutionContext will get us to a relevant Frame. In |
| // some cases, though, there isn't a good candidate (most commonly when either |
| // the passed-in document or the ExecutionContext used to be attached to a |
| // Frame but has since been detached) so we will use the default task runner |
| // of the AgentGroupScheduler that created this window. |
| return To<WindowAgent>(GetAgent()) |
| ->GetAgentGroupScheduler() |
| .DefaultTaskRunner(); |
| } |
| |
| void LocalDOMWindow::ReportPermissionsPolicyViolation( |
| mojom::blink::PermissionsPolicyFeature feature, |
| mojom::blink::PolicyDisposition disposition, |
| const std::optional<String>& reporting_endpoint, |
| const String& message) const { |
| if (disposition == mojom::blink::PolicyDisposition::kEnforce) { |
| const_cast<LocalDOMWindow*>(this)->CountPermissionsPolicyUsage( |
| feature, UseCounterImpl::PermissionsPolicyUsageType::kViolation); |
| } |
| |
| if (!GetFrame()) { |
| return; |
| } |
| |
| // Construct the permissions policy violation report. |
| const String& feature_name = GetNameForFeature(feature); |
| const String& disp_str = |
| (disposition == mojom::blink::PolicyDisposition::kReport ? "report" |
| : "enforce"); |
| |
| PermissionsPolicyViolationReportBody* body = |
| MakeGarbageCollected<PermissionsPolicyViolationReportBody>( |
| feature_name, message, disp_str); |
| |
| Report* report = MakeGarbageCollected<Report>( |
| ReportType::kPermissionsPolicyViolation, Url().GetString(), body); |
| |
| // Send the permissions policy violation report to the specified endpoint, |
| // if one exists, as well as any ReportingObservers. |
| if (reporting_endpoint) { |
| ReportingContext::From(this)->QueueReport(report, {*reporting_endpoint}); |
| } else { |
| ReportingContext::From(this)->QueueReport(report); |
| } |
| |
| // TODO(iclelland): Report something different in report-only mode |
| if (disposition == mojom::blink::PolicyDisposition::kEnforce) { |
| GetFrame()->Console().AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kViolation, |
| mojom::blink::ConsoleMessageLevel::kError, body->message())); |
| } |
| } |
| |
| void LocalDOMWindow::ReportDocumentPolicyViolation( |
| mojom::blink::DocumentPolicyFeature feature, |
| mojom::blink::PolicyDisposition disposition, |
| const String& message, |
| const String& source_file) const { |
| if (!GetFrame()) |
| return; |
| |
| // Construct the document policy violation report. |
| const String& feature_name = |
| GetDocumentPolicyFeatureInfoMap().at(feature).feature_name.c_str(); |
| bool is_report_only = disposition == mojom::blink::PolicyDisposition::kReport; |
| const String& disp_str = is_report_only ? "report" : "enforce"; |
| const DocumentPolicy* relevant_document_policy = |
| is_report_only ? GetSecurityContext().GetReportOnlyDocumentPolicy() |
| : GetSecurityContext().GetDocumentPolicy(); |
| |
| DocumentPolicyViolationReportBody* body = |
| MakeGarbageCollected<DocumentPolicyViolationReportBody>( |
| feature_name, message, disp_str, source_file); |
| |
| Report* report = MakeGarbageCollected<Report>( |
| ReportType::kDocumentPolicyViolation, Url().GetString(), body); |
| |
| // Avoids sending duplicate reports, by comparing the generated MatchId. |
| // The match ids are not guaranteed to be unique. |
| // There are trade offs on storing full objects and storing match ids. Storing |
| // full objects takes more memory. Storing match id has the potential of hash |
| // collision. Since reporting is not a part critical system or have security |
| // concern, dropping a valid report due to hash collision seems a reasonable |
| // price to pay for the memory saving. |
| unsigned report_id = report->MatchId(); |
| DCHECK(report_id); |
| |
| if (document_policy_violation_reports_sent_.Contains(report_id)) |
| return; |
| document_policy_violation_reports_sent_.insert(report_id); |
| |
| // Send the document policy violation report to any ReportingObservers. |
| const std::optional<std::string> endpoint = |
| relevant_document_policy->GetFeatureEndpoint(feature); |
| |
| if (is_report_only) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.DocumentPolicy.ReportOnly", |
| feature); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.DocumentPolicy.Enforced", |
| feature); |
| } |
| |
| ReportingContext::From(this)->QueueReport( |
| report, endpoint ? Vector<String>{endpoint->c_str()} : Vector<String>{}); |
| |
| // TODO(iclelland): Report something different in report-only mode |
| if (!is_report_only) { |
| GetFrame()->Console().AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kViolation, |
| mojom::blink::ConsoleMessageLevel::kError, body->message())); |
| } |
| } |
| |
| void LocalDOMWindow::AddConsoleMessageImpl(ConsoleMessage* console_message, |
| bool discard_duplicates) { |
| CHECK(IsContextThread()); |
| |
| if (!GetFrame()) |
| return; |
| |
| if (document() && console_message->Location()->IsUnknown()) { |
| // TODO(dgozman): capture correct location at call places instead. |
| unsigned line_number = 0; |
| if (!document()->IsInDocumentWrite() && |
| document()->GetScriptableDocumentParser()) { |
| ScriptableDocumentParser* parser = |
| document()->GetScriptableDocumentParser(); |
| if (parser->IsParsingAtLineNumber()) |
| line_number = parser->LineNumber().OneBasedInt(); |
| } |
| Vector<DOMNodeId> nodes(console_message->Nodes()); |
| std::optional<mojom::blink::ConsoleMessageCategory> category = |
| console_message->Category(); |
| console_message = MakeGarbageCollected<ConsoleMessage>( |
| console_message->GetSource(), console_message->GetLevel(), |
| console_message->Message(), |
| std::make_unique<SourceLocation>(Url().GetString(), String(), |
| line_number, 0, nullptr)); |
| console_message->SetNodes(GetFrame(), std::move(nodes)); |
| if (category) |
| console_message->SetCategory(*category); |
| } |
| |
| GetFrame()->Console().AddMessage(console_message, discard_duplicates); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| LocalDOMWindow::GetAgentGroupSchedulerCompositorTaskRunner() { |
| if (!GetFrame()) |
| return nullptr; |
| auto* frame_scheduler = GetFrame()->GetFrameScheduler(); |
| return frame_scheduler->GetAgentGroupScheduler()->CompositorTaskRunner(); |
| } |
| |
| void LocalDOMWindow::AddInspectorIssue(AuditsIssue issue) { |
| if (GetFrame()) { |
| GetFrame()->GetPage()->GetInspectorIssueStorage().AddInspectorIssue( |
| this, std::move(issue)); |
| } |
| } |
| |
| void LocalDOMWindow::CountUse(mojom::WebFeature feature) { |
| if (!GetFrame()) |
| return; |
| if (auto* loader = GetFrame()->Loader().GetDocumentLoader()) |
| loader->CountUse(feature); |
| } |
| |
| void LocalDOMWindow::CountPermissionsPolicyUsage( |
| mojom::blink::PermissionsPolicyFeature feature, |
| UseCounterImpl::PermissionsPolicyUsageType type) { |
| if (!GetFrame()) |
| return; |
| if (auto* loader = GetFrame()->Loader().GetDocumentLoader()) { |
| loader->GetUseCounter().CountPermissionsPolicyUsage(feature, type, |
| *GetFrame()); |
| } |
| } |
| |
| void LocalDOMWindow::CountUseOnlyInCrossOriginIframe( |
| mojom::blink::WebFeature feature) { |
| if (GetFrame() && GetFrame()->IsCrossOriginToOutermostMainFrame()) |
| CountUse(feature); |
| } |
| |
| void LocalDOMWindow::CountUseOnlyInSameOriginIframe( |
| mojom::blink::WebFeature feature) { |
| if (GetFrame() && !GetFrame()->IsOutermostMainFrame() && |
| !GetFrame()->IsCrossOriginToOutermostMainFrame()) { |
| CountUse(feature); |
| } |
| } |
| |
| void LocalDOMWindow::CountUseOnlyInCrossSiteIframe( |
| mojom::blink::WebFeature feature) { |
| if (IsCrossSiteSubframeIncludingScheme()) |
| CountUse(feature); |
| } |
| |
| bool LocalDOMWindow::HasInsecureContextInAncestors() const { |
| for (Frame* parent = GetFrame()->Tree().Parent(); parent; |
| parent = parent->Tree().Parent()) { |
| auto* origin = parent->GetSecurityContext()->GetSecurityOrigin(); |
| if (!origin->IsPotentiallyTrustworthy()) |
| return true; |
| } |
| return false; |
| } |
| |
| Document* LocalDOMWindow::InstallNewDocument(const DocumentInit& init) { |
| // Blink should never attempt to install a new Document to a LocalDOMWindow |
| // that's not attached to a LocalFrame. |
| DCHECK(GetFrame()); |
| // Either: |
| // - `this` should be a new LocalDOMWindow, that has never had a Document |
| // associated with it or |
| // - `this` is being reused, and the previous Document has been disassociated |
| // via `ClearForReuse()`. |
| DCHECK(!document_); |
| DCHECK_EQ(init.GetWindow(), this); |
| |
| document_ = init.CreateDocument(); |
| document_->Initialize(); |
| |
| document_->GetViewportData().UpdateViewportDescription(); |
| |
| auto* frame_scheduler = GetFrame()->GetFrameScheduler(); |
| frame_scheduler->TraceUrlChange(document_->Url().GetString()); |
| frame_scheduler->SetCrossOriginToNearestMainFrame( |
| GetFrame()->IsCrossOriginToNearestMainFrame()); |
| |
| GetFrame()->GetPage()->GetChromeClient().InstallSupplements(*GetFrame()); |
| |
| return document_.Get(); |
| } |
| |
| void LocalDOMWindow::EnqueueWindowEvent(Event& event, TaskType task_type) { |
| EnqueueEvent(event, task_type); |
| } |
| |
| void LocalDOMWindow::EnqueueDocumentEvent(Event& event, TaskType task_type) { |
| if (document_) |
| document_->EnqueueEvent(event, task_type); |
| } |
| |
| void LocalDOMWindow::DispatchWindowLoadEvent() { |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| // Delay 'load' event if we are in EventQueueScope. This is a short-term |
| // workaround to avoid Editing code crashes. We should always dispatch |
| // 'load' event asynchronously. crbug.com/569511. |
| if (ScopedEventQueue::Instance()->ShouldQueueEvents() && document_) { |
| document_->GetTaskRunner(TaskType::kNetworking) |
| ->PostTask(FROM_HERE, WTF::BindOnce(&LocalDOMWindow::DispatchLoadEvent, |
| WrapPersistent(this))); |
| return; |
| } |
| DispatchLoadEvent(); |
| } |
| |
| void LocalDOMWindow::DocumentWasClosed() { |
| DispatchWindowLoadEvent(); |
| |
| // An extension to step 4.5. or a part of step 4.6.3. of |
| // https://html.spec.whatwg.org/C/#traverse-the-history . |
| // |
| // 4.5. ..., invoke the reset algorithm of each of those elements. |
| // 4.6.3. Run any session history document visibility change steps ... |
| if (document_) { |
| document_->GetFormController().RestoreImmediately(); |
| } |
| |
| // 4.6.4. Fire an event named pageshow at the Document object's relevant |
| // global object, ... |
| EnqueueNonPersistedPageshowEvent(); |
| } |
| |
| void LocalDOMWindow::EnqueueNonPersistedPageshowEvent() { |
| // FIXME: https://bugs.webkit.org/show_bug.cgi?id=36334 Pageshow event needs |
| // to fire asynchronously. As per spec pageshow must be triggered |
| // asynchronously. However to be compatible with other browsers blink fires |
| // pageshow synchronously unless we are in EventQueueScope. |
| if (ScopedEventQueue::Instance()->ShouldQueueEvents() && document_) { |
| // The task source should be kDOMManipulation, but the spec doesn't say |
| // anything about this. |
| EnqueueWindowEvent(*PageTransitionEvent::Create(event_type_names::kPageshow, |
| false /* persisted */), |
| TaskType::kMiscPlatformAPI); |
| } else { |
| DispatchEvent(*PageTransitionEvent::Create(event_type_names::kPageshow, |
| false /* persisted */), |
| document_.Get()); |
| } |
| } |
| |
| void LocalDOMWindow::DispatchPersistedPageshowEvent( |
| base::TimeTicks navigation_start) { |
| // Persisted pageshow events are dispatched for pages that are restored from |
| // the back forward cache, and the event's timestamp should reflect the |
| // |navigation_start| time of the back navigation. |
| DispatchEvent(*PageTransitionEvent::CreatePersistedPageshow(navigation_start), |
| document_.Get()); |
| } |
| |
| void LocalDOMWindow::DispatchPagehideEvent( |
| PageTransitionEventPersistence persistence) { |
| if (document_->IsPrerendering()) { |
| // Do not dispatch the event while prerendering. |
| return; |
| } |
| if (document_->UnloadStarted()) { |
| // We've already dispatched pagehide (since it's the first thing we do when |
| // starting unload) and shouldn't dispatch it again. We might get here on |
| // a document that is already unloading/has unloaded but still part of the |
| // FrameTree. |
| // TODO(crbug.com/1119291): Investigate whether this is possible or not. |
| return; |
| } |
| |
| DispatchEvent( |
| *PageTransitionEvent::Create(event_type_names::kPagehide, persistence), |
| document_.Get()); |
| } |
| |
| void LocalDOMWindow::EnqueueHashchangeEvent(const String& old_url, |
| const String& new_url) { |
| DCHECK(GetFrame()); |
| if (SoftNavigationHeuristics* heuristics = |
| SoftNavigationHeuristics::From(*this)) { |
| heuristics->SameDocumentNavigationStarted(); |
| } |
| // https://html.spec.whatwg.org/C/#history-traversal |
| EnqueueWindowEvent(*HashChangeEvent::Create(old_url, new_url), |
| TaskType::kDOMManipulation); |
| } |
| |
| void LocalDOMWindow::DispatchPopstateEvent( |
| scoped_refptr<SerializedScriptValue> state_object, |
| scheduler::TaskAttributionInfo* parent_task) { |
| DCHECK(GetFrame()); |
| std::optional<scheduler::TaskAttributionTracker::TaskScope> |
| task_attribution_scope; |
| if (parent_task) { |
| auto* tracker = scheduler::TaskAttributionTracker::From(GetIsolate()); |
| ScriptState* script_state = ToScriptStateForMainWorld(GetFrame()); |
| if (script_state && tracker) { |
| task_attribution_scope = tracker->CreateTaskScope( |
| script_state, parent_task, |
| scheduler::TaskAttributionTracker::TaskScopeType::kPopState); |
| } |
| } |
| DispatchEvent(*PopStateEvent::Create(std::move(state_object), history())); |
| } |
| |
| LocalDOMWindow::~LocalDOMWindow() = default; |
| |
| void LocalDOMWindow::Dispose() { |
| BackForwardCacheBufferLimitTracker::Get() |
| .DidRemoveFrameOrWorkerFromBackForwardCache( |
| total_bytes_buffered_while_in_back_forward_cache_); |
| total_bytes_buffered_while_in_back_forward_cache_ = 0; |
| |
| // Oilpan: should the LocalDOMWindow be GCed along with its LocalFrame without |
| // the frame having first notified its observers of imminent destruction, the |
| // LocalDOMWindow will not have had an opportunity to remove event listeners. |
| // |
| // Arrange for that removal to happen using a prefinalizer action. Making |
| // LocalDOMWindow eager finalizable is problematic as other eagerly finalized |
| // objects may well want to access their associated LocalDOMWindow from their |
| // destructors. |
| if (!GetFrame()) |
| return; |
| |
| RemoveAllEventListeners(); |
| } |
| |
| ExecutionContext* LocalDOMWindow::GetExecutionContext() const { |
| return const_cast<LocalDOMWindow*>(this); |
| } |
| |
| const LocalDOMWindow* LocalDOMWindow::ToLocalDOMWindow() const { |
| return this; |
| } |
| |
| LocalDOMWindow* LocalDOMWindow::ToLocalDOMWindow() { |
| return this; |
| } |
| |
| MediaQueryList* LocalDOMWindow::matchMedia(const String& media) { |
| return document()->GetMediaQueryMatcher().MatchMedia(media); |
| } |
| |
| void LocalDOMWindow::FrameDestroyed() { |
| BackForwardCacheBufferLimitTracker::Get() |
| .DidRemoveFrameOrWorkerFromBackForwardCache( |
| total_bytes_buffered_while_in_back_forward_cache_); |
| total_bytes_buffered_while_in_back_forward_cache_ = 0; |
| |
| // Some unit tests manually call FrameDestroyed(). Don't run it a second time. |
| if (!GetFrame()) |
| return; |
| // In the Reset() case, this Document::Shutdown() early-exits because it was |
| // already called earlier in the commit process. |
| // TODO(japhet): Can we merge this function and Reset()? At least, this |
| // function should be renamed to Detach(), since in the Reset() case the frame |
| // is not being destroyed. |
| document()->Shutdown(); |
| document()->RemoveAllEventListenersRecursively(); |
| GetAgent()->DetachContext(this); |
| NotifyContextDestroyed(); |
| RemoveAllEventListeners(); |
| MainThreadDebugger::Instance(GetIsolate()) |
| ->DidClearContextsForFrame(GetFrame()); |
| DisconnectFromFrame(); |
| } |
| |
| void LocalDOMWindow::RegisterEventListenerObserver( |
| EventListenerObserver* event_listener_observer) { |
| event_listener_observers_.insert(event_listener_observer); |
| } |
| |
| void LocalDOMWindow::Reset() { |
| DCHECK(document()); |
| FrameDestroyed(); |
| |
| screen_ = nullptr; |
| history_ = nullptr; |
| locationbar_ = nullptr; |
| menubar_ = nullptr; |
| personalbar_ = nullptr; |
| scrollbars_ = nullptr; |
| statusbar_ = nullptr; |
| toolbar_ = nullptr; |
| navigator_ = nullptr; |
| media_ = nullptr; |
| custom_elements_ = nullptr; |
| trusted_types_map_.clear(); |
| } |
| |
| void LocalDOMWindow::SendOrientationChangeEvent() { |
| DCHECK(RuntimeEnabledFeatures::OrientationEventEnabled()); |
| DCHECK(GetFrame()->IsLocalRoot()); |
| |
| // Before dispatching the event, build a list of all frames in the page |
| // to send the event to, to mitigate side effects from event handlers |
| // potentially interfering with others. |
| HeapVector<Member<LocalFrame>> frames; |
| frames.push_back(GetFrame()); |
| for (wtf_size_t i = 0; i < frames.size(); i++) { |
| for (Frame* child = frames[i]->Tree().FirstChild(); child; |
| child = child->Tree().NextSibling()) { |
| if (auto* child_local_frame = DynamicTo<LocalFrame>(child)) |
| frames.push_back(child_local_frame); |
| } |
| } |
| |
| for (LocalFrame* frame : frames) { |
| frame->DomWindow()->DispatchEvent( |
| *Event::Create(event_type_names::kOrientationchange)); |
| } |
| } |
| |
| int LocalDOMWindow::orientation() const { |
| DCHECK(RuntimeEnabledFeatures::OrientationEventEnabled()); |
| |
| LocalFrame* frame = GetFrame(); |
| if (!frame) |
| return 0; |
| |
| ChromeClient& chrome_client = frame->GetChromeClient(); |
| int orientation = chrome_client.GetScreenInfo(*frame).orientation_angle; |
| // For backward compatibility, we want to return a value in the range of |
| // [-90; 180] instead of [0; 360[ because window.orientation used to behave |
| // like that in WebKit (this is a WebKit proprietary API). |
| if (orientation == 270) |
| return -90; |
| return orientation; |
| } |
| |
| Screen* LocalDOMWindow::screen() { |
| if (!screen_) { |
| LocalFrame* frame = GetFrame(); |
| int64_t display_id = |
| frame ? frame->GetChromeClient().GetScreenInfo(*frame).display_id |
| : Screen::kInvalidDisplayId; |
| screen_ = MakeGarbageCollected<Screen>(this, display_id); |
| } |
| return screen_.Get(); |
| } |
| |
| History* LocalDOMWindow::history() { |
| if (!history_) |
| history_ = MakeGarbageCollected<History>(this); |
| return history_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::locationbar() { |
| if (!locationbar_) { |
| locationbar_ = MakeGarbageCollected<BarProp>(this); |
| } |
| return locationbar_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::menubar() { |
| if (!menubar_) |
| menubar_ = MakeGarbageCollected<BarProp>(this); |
| return menubar_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::personalbar() { |
| if (!personalbar_) { |
| personalbar_ = MakeGarbageCollected<BarProp>(this); |
| } |
| return personalbar_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::scrollbars() { |
| if (!scrollbars_) { |
| scrollbars_ = MakeGarbageCollected<BarProp>(this); |
| } |
| return scrollbars_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::statusbar() { |
| if (!statusbar_) |
| statusbar_ = MakeGarbageCollected<BarProp>(this); |
| return statusbar_.Get(); |
| } |
| |
| BarProp* LocalDOMWindow::toolbar() { |
| if (!toolbar_) |
| toolbar_ = MakeGarbageCollected<BarProp>(this); |
| return toolbar_.Get(); |
| } |
| |
| FrameConsole* LocalDOMWindow::GetFrameConsole() const { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return nullptr; |
| return &GetFrame()->Console(); |
| } |
| |
| Navigator* LocalDOMWindow::navigator() { |
| if (!navigator_) |
| navigator_ = MakeGarbageCollected<Navigator>(this); |
| return navigator_.Get(); |
| } |
| |
| NavigationApi* LocalDOMWindow::navigation() { |
| if (!navigation_) |
| navigation_ = MakeGarbageCollected<NavigationApi>(this); |
| return navigation_.Get(); |
| } |
| |
| void LocalDOMWindow::SchedulePostMessage(PostedMessage* posted_message) { |
| LocalDOMWindow* source = posted_message->source; |
| |
| // Record UKM metrics for the postMessage event and don't send message if |
| // gating indicates it should be dropped. |
| if (!post_message_counter_.RecordMessageAndCheckIfShouldSend( |
| source->UkmSourceID(), source->GetStorageKey(), UkmSourceID(), |
| GetStorageKey(), UkmRecorder())) { |
| return; |
| }; |
| |
| // Notify the host if the message contained a delegated capability. That state |
| // should be tracked by the browser, and messages from remote hosts already |
| // signal the browser via RemoteFrameHost's RouteMessageEvent. |
| if (posted_message->delegated_capability != |
| mojom::blink::DelegatedCapability::kNone) { |
| GetFrame()->GetLocalFrameHostRemote().ReceivedDelegatedCapability( |
| posted_message->delegated_capability); |
| } |
| |
| // Convert the posted message to a MessageEvent so it can be unpacked for |
| // local dispatch. |
| MessageEvent* event = MessageEvent::Create( |
| std::move(posted_message->channels), std::move(posted_message->data), |
| posted_message->source_origin->ToString(), String(), |
| posted_message->source, posted_message->user_activation, |
| posted_message->delegated_capability); |
| |
| // Allowing unbounded amounts of messages to build up for a suspended context |
| // is problematic; consider imposing a limit or other restriction if this |
| // surfaces often as a problem (see crbug.com/587012). |
| std::unique_ptr<SourceLocation> location = CaptureSourceLocation(source); |
| GetTaskRunner(TaskType::kPostedMessage) |
| ->PostTask( |
| FROM_HERE, |
| WTF::BindOnce(&LocalDOMWindow::DispatchPostMessage, |
| WrapPersistent(this), WrapPersistent(event), |
| std::move(posted_message->target_origin), |
| std::move(location), source->GetAgent()->cluster_id())); |
| event->async_task_context()->Schedule(this, "postMessage"); |
| uint64_t trace_id = base::trace_event::GetNextGlobalTraceId(); |
| event->SetTraceId(trace_id); |
| TRACE_EVENT_INSTANT( |
| "devtools.timeline", "SchedulePostMessage", "data", |
| [&](perfetto::TracedValue context) { |
| inspector_schedule_post_message_event::Data( |
| std::move(context), GetExecutionContext(), trace_id); |
| }, |
| perfetto::Flow::Global(trace_id)); |
| } |
| |
| void LocalDOMWindow::DispatchPostMessage( |
| MessageEvent* event, |
| scoped_refptr<const SecurityOrigin> intended_target_origin, |
| std::unique_ptr<SourceLocation> location, |
| const base::UnguessableToken& source_agent_cluster_id) { |
| // Do not report postMessage tasks to the ad tracker. This allows non-ad |
| // script to perform operations in response to events created by ad frames. |
| probe::AsyncTask async_task(this, event->async_task_context(), |
| nullptr /* step */, true /* enabled */, |
| probe::AsyncTask::AdTrackingType::kIgnore); |
| if (!IsCurrentlyDisplayedInFrame()) |
| return; |
| |
| event->EntangleMessagePorts(this); |
| |
| TRACE_EVENT( |
| "devtools.timeline", "HandlePostMessage", "data", |
| [&](perfetto::TracedValue context) { |
| inspector_handle_post_message_event::Data( |
| std::move(context), GetExecutionContext(), *event); |
| }, |
| perfetto::Flow::Global(event->GetTraceId())); |
| |
| DispatchMessageEventWithOriginCheck(intended_target_origin.get(), event, |
| std::move(location), |
| source_agent_cluster_id); |
| } |
| |
| void LocalDOMWindow::DispatchMessageEventWithOriginCheck( |
| const SecurityOrigin* intended_target_origin, |
| MessageEvent* event, |
| std::unique_ptr<SourceLocation> location, |
| const base::UnguessableToken& source_agent_cluster_id) { |
| TRACE_EVENT0("blink", "LocalDOMWindow::DispatchMessageEventWithOriginCheck"); |
| if (intended_target_origin) { |
| bool valid_target = |
| intended_target_origin->IsSameOriginWith(GetSecurityOrigin()); |
| |
| if (!valid_target) { |
| String message = ExceptionMessages::FailedToExecute( |
| "postMessage", "DOMWindow", |
| "The target origin provided ('" + intended_target_origin->ToString() + |
| "') does not match the recipient window's origin ('" + |
| GetSecurityOrigin()->ToString() + "')."); |
| auto* console_message = MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kWarning, message, std::move(location)); |
| GetFrameConsole()->AddMessage(console_message); |
| return; |
| } |
| } |
| |
| KURL sender(event->origin()); |
| if (!GetContentSecurityPolicy()->AllowConnectToSource( |
| sender, sender, RedirectStatus::kNoRedirect, |
| ReportingDisposition::kSuppressReporting)) { |
| UseCounter::Count( |
| this, WebFeature::kPostMessageIncomingWouldBeBlockedByConnectSrc); |
| } |
| |
| if (event->IsOriginCheckRequiredToAccessData()) { |
| scoped_refptr<SecurityOrigin> sender_security_origin = |
| SecurityOrigin::Create(sender); |
| if (!sender_security_origin->IsSameOriginWith(GetSecurityOrigin())) { |
| event = MessageEvent::CreateError(event->origin(), event->source()); |
| } |
| } |
| if (event->IsLockedToAgentCluster()) { |
| if (!IsSameAgentCluster(source_agent_cluster_id)) { |
| UseCounter::Count( |
| this, |
| WebFeature::kMessageEventSharedArrayBufferDifferentAgentCluster); |
| event = MessageEvent::CreateError(event->origin(), event->source()); |
| } else { |
| scoped_refptr<SecurityOrigin> sender_origin = |
| SecurityOrigin::Create(sender); |
| if (!sender_origin->IsSameOriginWith(GetSecurityOrigin())) { |
| UseCounter::Count( |
| this, WebFeature::kMessageEventSharedArrayBufferSameAgentCluster); |
| } else { |
| UseCounter::Count(this, |
| WebFeature::kMessageEventSharedArrayBufferSameOrigin); |
| } |
| } |
| } |
| |
| if (!event->CanDeserializeIn(this)) { |
| event = MessageEvent::CreateError(event->origin(), event->source()); |
| } |
| |
| if (event->delegatedCapability() == |
| mojom::blink::DelegatedCapability::kPaymentRequest) { |
| UseCounter::Count(this, WebFeature::kCapabilityDelegationOfPaymentRequest); |
| payment_request_token_.Activate(); |
| } |
| |
| if (event->delegatedCapability() == |
| mojom::blink::DelegatedCapability::kFullscreenRequest) { |
| UseCounter::Count(this, |
| WebFeature::kCapabilityDelegationOfFullscreenRequest); |
| fullscreen_request_token_.Activate(); |
| } |
| if (RuntimeEnabledFeatures::CapabilityDelegationDisplayCaptureRequestEnabled( |
| this) && |
| event->delegatedCapability() == |
| mojom::blink::DelegatedCapability::kDisplayCaptureRequest) { |
| // TODO(crbug.com/1412770): Add use counter. |
| display_capture_request_token_.Activate(); |
| } |
| |
| if (GetFrame() && |
| GetFrame()->GetPage()->GetPageScheduler()->IsInBackForwardCache()) { |
| // Enqueue the event when the page is in back/forward cache, so that it |
| // would not cause JavaScript execution. The event will be dispatched upon |
| // restore. |
| EnqueueEvent(*event, TaskType::kInternalDefault); |
| } else { |
| DispatchEvent(*event); |
| } |
| } |
| |
| DOMSelection* LocalDOMWindow::getSelection() { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return nullptr; |
| |
| return document()->GetSelection(); |
| } |
| |
| Element* LocalDOMWindow::frameElement() const { |
| if (!GetFrame()) |
| return nullptr; |
| |
| return DynamicTo<HTMLFrameOwnerElement>(GetFrame()->Owner()); |
| } |
| |
| void LocalDOMWindow::print(ScriptState* script_state) { |
| // Don't try to print if there's no frame attached anymore. |
| if (!GetFrame()) |
| return; |
| |
| if (script_state && IsRunningMicrotasks(script_state)) { |
| UseCounter::Count(this, WebFeature::kDuring_Microtask_Print); |
| } |
| |
| if (GetFrame()->IsLoading()) { |
| should_print_when_finished_loading_ = true; |
| return; |
| } |
| |
| CountUseOnlyInSameOriginIframe(WebFeature::kSameOriginIframeWindowPrint); |
| CountUseOnlyInCrossOriginIframe(WebFeature::kCrossOriginWindowPrint); |
| |
| should_print_when_finished_loading_ = false; |
| GetFrame()->GetPage()->GetChromeClient().Print(GetFrame()); |
| } |
| |
| void LocalDOMWindow::stop() { |
| if (!GetFrame()) |
| return; |
| GetFrame()->Loader().StopAllLoaders(/*abort_client=*/true); |
| } |
| |
| void LocalDOMWindow::alert(ScriptState* script_state, const String& message) { |
| if (!GetFrame()) |
| return; |
| |
| if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) { |
| UseCounter::Count(this, WebFeature::kDialogInSandboxedContext); |
| GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kSecurity, |
| mojom::blink::ConsoleMessageLevel::kError, |
| GetFrame()->IsInFencedFrameTree() |
| ? "Ignored call to 'alert()'. The document is in a fenced frame " |
| "tree." |
| : "Ignored call to 'alert()'. The document is sandboxed, and the " |
| "'allow-modals' keyword is not set.")); |
| return; |
| } |
| |
| if (IsRunningMicrotasks(script_state)) { |
| UseCounter::Count(this, WebFeature::kDuring_Microtask_Alert); |
| } |
| |
| document()->UpdateStyleAndLayoutTree(); |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return; |
| |
| CountUseOnlyInSameOriginIframe(WebFeature::kSameOriginIframeWindowAlert); |
| Deprecation::CountDeprecationCrossOriginIframe( |
| this, WebFeature::kCrossOriginWindowAlert); |
| |
| page->GetChromeClient().OpenJavaScriptAlert(GetFrame(), message); |
| } |
| |
| bool LocalDOMWindow::confirm(ScriptState* script_state, const String& message) { |
| if (!GetFrame()) |
| return false; |
| |
| if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) { |
| UseCounter::Count(this, WebFeature::kDialogInSandboxedContext); |
| GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kSecurity, |
| mojom::blink::ConsoleMessageLevel::kError, |
| GetFrame()->IsInFencedFrameTree() |
| ? "Ignored call to 'confirm()'. The document is in a fenced frame " |
| "tree." |
| : "Ignored call to 'confirm()'. The document is sandboxed, and the " |
| "'allow-modals' keyword is not set.")); |
| return false; |
| } |
| |
| if (IsRunningMicrotasks(script_state)) { |
| UseCounter::Count(this, WebFeature::kDuring_Microtask_Confirm); |
| } |
| |
| document()->UpdateStyleAndLayoutTree(); |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return false; |
| |
| CountUseOnlyInSameOriginIframe(WebFeature::kSameOriginIframeWindowConfirm); |
| Deprecation::CountDeprecationCrossOriginIframe( |
| this, WebFeature::kCrossOriginWindowConfirm); |
| |
| return page->GetChromeClient().OpenJavaScriptConfirm(GetFrame(), message); |
| } |
| |
| String LocalDOMWindow::prompt(ScriptState* script_state, |
| const String& message, |
| const String& default_value) { |
| if (!GetFrame()) |
| return String(); |
| |
| if (IsSandboxed(network::mojom::blink::WebSandboxFlags::kModals)) { |
| UseCounter::Count(this, WebFeature::kDialogInSandboxedContext); |
| GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kSecurity, |
| mojom::blink::ConsoleMessageLevel::kError, |
| GetFrame()->IsInFencedFrameTree() |
| ? "Ignored call to 'prompt()'. The document is in a fenced frame " |
| "tree." |
| : "Ignored call to 'prompt()'. The document is sandboxed, and the " |
| "'allow-modals' keyword is not set.")); |
| return String(); |
| } |
| |
| if (IsRunningMicrotasks(script_state)) { |
| UseCounter::Count(this, WebFeature::kDuring_Microtask_Prompt); |
| } |
| |
| document()->UpdateStyleAndLayoutTree(); |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return String(); |
| |
| String return_value; |
| if (page->GetChromeClient().OpenJavaScriptPrompt(GetFrame(), message, |
| default_value, return_value)) |
| return return_value; |
| |
| CountUseOnlyInSameOriginIframe(WebFeature::kSameOriginIframeWindowPrompt); |
| Deprecation::CountDeprecationCrossOriginIframe( |
| this, WebFeature::kCrossOriginWindowAlert); |
| |
| return String(); |
| } |
| |
| bool LocalDOMWindow::find(const String& string, |
| bool case_sensitive, |
| bool backwards, |
| bool wrap, |
| bool whole_word, |
| bool /*searchInFrames*/, |
| bool /*showDialog*/) const { |
| auto forced_activatable_locks = document() |
| ->GetDisplayLockDocumentState() |
| .GetScopedForceActivatableLocks(); |
| |
| if (!IsCurrentlyDisplayedInFrame()) |
| return false; |
| |
| // Up-to-date, clean tree is required for finding text in page, since it |
| // relies on TextIterator to look over the text. |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| |
| // FIXME (13016): Support searchInFrames and showDialog |
| FindOptions options = |
| (backwards ? kBackwards : 0) | (case_sensitive ? 0 : kCaseInsensitive) | |
| (wrap ? kWrapAround : 0) | (whole_word ? kWholeWord : 0); |
| return Editor::FindString(*GetFrame(), string, options); |
| } |
| |
| bool LocalDOMWindow::offscreenBuffering() const { |
| return true; |
| } |
| |
| int LocalDOMWindow::outerHeight() const { |
| if (!GetFrame()) |
| return 0; |
| |
| LocalFrame* frame = GetFrame(); |
| |
| // FencedFrames should return innerHeight to prevent passing |
| // arbitrary data through the window height. |
| if (frame->IsInFencedFrameTree()) { |
| return innerHeight(); |
| } |
| |
| Page* page = frame->GetPage(); |
| if (!page) |
| return 0; |
| |
| ChromeClient& chrome_client = page->GetChromeClient(); |
| if (page->GetSettings().GetReportScreenSizeInPhysicalPixelsQuirk()) { |
| return static_cast<int>( |
| lroundf(chrome_client.RootWindowRect(*frame).height() * |
| chrome_client.GetScreenInfo(*frame).device_scale_factor)); |
| } |
| return chrome_client.RootWindowRect(*frame).height(); |
| } |
| |
| int LocalDOMWindow::outerWidth() const { |
| if (!GetFrame()) |
| return 0; |
| |
| LocalFrame* frame = GetFrame(); |
| |
| // FencedFrames should return innerWidth to prevent passing |
| // arbitrary data through the window width. |
| if (frame->IsInFencedFrameTree()) { |
| return innerWidth(); |
| } |
| |
| Page* page = frame->GetPage(); |
| if (!page) |
| return 0; |
| |
| ChromeClient& chrome_client = page->GetChromeClient(); |
| if (page->GetSettings().GetReportScreenSizeInPhysicalPixelsQuirk()) { |
| return static_cast<int>( |
| lroundf(chrome_client.RootWindowRect(*frame).width() * |
| chrome_client.GetScreenInfo(*frame).device_scale_factor)); |
| } |
| return chrome_client.RootWindowRect(*frame).width(); |
| } |
| |
| gfx::Size LocalDOMWindow::GetViewportSize() const { |
| LocalFrameView* view = GetFrame()->View(); |
| if (!view) |
| return gfx::Size(); |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return gfx::Size(); |
| |
| // The main frame's viewport size depends on the page scale. If viewport is |
| // enabled, the initial page scale depends on the content width and is set |
| // after a layout, perform one now so queries during page load will use the |
| // up to date viewport. |
| if (page->GetSettings().GetViewportEnabled() && GetFrame()->IsMainFrame()) { |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| } |
| |
| // FIXME: This is potentially too much work. We really only need to know the |
| // dimensions of the parent frame's layoutObject. |
| if (Frame* parent = GetFrame()->Tree().Parent()) { |
| if (auto* parent_local_frame = DynamicTo<LocalFrame>(parent)) { |
| parent_local_frame->GetDocument()->UpdateStyleAndLayout( |
| DocumentUpdateReason::kJavaScript); |
| } |
| } |
| |
| return document()->View()->Size(); |
| } |
| |
| int LocalDOMWindow::innerHeight() const { |
| if (!GetFrame()) |
| return 0; |
| |
| return AdjustForAbsoluteZoom::AdjustInt(GetViewportSize().height(), |
| GetFrame()->PageZoomFactor()); |
| } |
| |
| int LocalDOMWindow::innerWidth() const { |
| if (!GetFrame()) |
| return 0; |
| |
| return AdjustForAbsoluteZoom::AdjustInt(GetViewportSize().width(), |
| GetFrame()->PageZoomFactor()); |
| } |
| |
| int LocalDOMWindow::screenX() const { |
| LocalFrame* frame = GetFrame(); |
| if (!frame) |
| return 0; |
| |
| Page* page = frame->GetPage(); |
| if (!page) |
| return 0; |
| |
| ChromeClient& chrome_client = page->GetChromeClient(); |
| if (page->GetSettings().GetReportScreenSizeInPhysicalPixelsQuirk()) { |
| return static_cast<int>( |
| lroundf(chrome_client.RootWindowRect(*frame).x() * |
| chrome_client.GetScreenInfo(*frame).device_scale_factor)); |
| } |
| return chrome_client.RootWindowRect(*frame).x(); |
| } |
| |
| int LocalDOMWindow::screenY() const { |
| LocalFrame* frame = GetFrame(); |
| if (!frame) |
| return 0; |
| |
| Page* page = frame->GetPage(); |
| if (!page) |
| return 0; |
| |
| ChromeClient& chrome_client = page->GetChromeClient(); |
| if (page->GetSettings().GetReportScreenSizeInPhysicalPixelsQuirk()) { |
| return static_cast<int>( |
| lroundf(chrome_client.RootWindowRect(*frame).y() * |
| chrome_client.GetScreenInfo(*frame).device_scale_factor)); |
| } |
| return chrome_client.RootWindowRect(*frame).y(); |
| } |
| |
| double LocalDOMWindow::scrollX() const { |
| if (!GetFrame() || !GetFrame()->GetPage()) |
| return 0; |
| |
| LocalFrameView* view = GetFrame()->View(); |
| if (!view) |
| return 0; |
| |
| // TODO(crbug.com/1499981): This should be removed once synchronized scrolling |
| // impact is understood. |
| SyncScrollAttemptHeuristic::DidAccessScrollOffset(); |
| |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| |
| // TODO(bokan): This is wrong when the document.rootScroller is non-default. |
| // crbug.com/505516. |
| double viewport_x = view->LayoutViewport()->GetWebExposedScrollOffset().x(); |
| return AdjustForAbsoluteZoom::AdjustScroll(viewport_x, |
| GetFrame()->PageZoomFactor()); |
| } |
| |
| double LocalDOMWindow::scrollY() const { |
| if (!GetFrame() || !GetFrame()->GetPage()) |
| return 0; |
| |
| LocalFrameView* view = GetFrame()->View(); |
| if (!view) |
| return 0; |
| |
| // TODO(crbug.com/1499981): This should be removed once synchronized scrolling |
| // impact is understood. |
| SyncScrollAttemptHeuristic::DidAccessScrollOffset(); |
| |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| |
| // TODO(bokan): This is wrong when the document.rootScroller is non-default. |
| // crbug.com/505516. |
| double viewport_y = view->LayoutViewport()->GetWebExposedScrollOffset().y(); |
| return AdjustForAbsoluteZoom::AdjustScroll(viewport_y, |
| GetFrame()->PageZoomFactor()); |
| } |
| |
| DOMVisualViewport* LocalDOMWindow::visualViewport() { |
| return visualViewport_.Get(); |
| } |
| |
| const AtomicString& LocalDOMWindow::name() const { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return g_null_atom; |
| |
| return GetFrame()->Tree().GetName(); |
| } |
| |
| void LocalDOMWindow::setName(const AtomicString& name) { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return; |
| |
| GetFrame()->Tree().SetName(name, FrameTree::kReplicate); |
| } |
| |
| void LocalDOMWindow::setStatus(const String& string) { |
| status_ = string; |
| } |
| |
| void LocalDOMWindow::setDefaultStatus(const String& string) { |
| DCHECK(RuntimeEnabledFeatures::WindowDefaultStatusEnabled()); |
| default_status_ = string; |
| } |
| |
| String LocalDOMWindow::origin() const { |
| return GetSecurityOrigin()->ToString(); |
| } |
| |
| Document* LocalDOMWindow::document() const { |
| return document_.Get(); |
| } |
| |
| StyleMedia* LocalDOMWindow::styleMedia() { |
| if (!media_) |
| media_ = MakeGarbageCollected<StyleMedia>(this); |
| return media_.Get(); |
| } |
| |
| CSSStyleDeclaration* LocalDOMWindow::getComputedStyle( |
| Element* elt, |
| const String& pseudo_elt) const { |
| DCHECK(elt); |
| return MakeGarbageCollected<CSSComputedStyleDeclaration>(elt, false, |
| pseudo_elt); |
| } |
| |
| ScriptPromise<ComputedAccessibleNode> LocalDOMWindow::getComputedAccessibleNode( |
| ScriptState* script_state, |
| Element* element) { |
| DCHECK(element); |
| auto* resolver = MakeGarbageCollected<ComputedAccessibleNodePromiseResolver>( |
| script_state, *element); |
| auto promise = resolver->Promise(); |
| resolver->ComputeAccessibleNode(); |
| return promise; |
| } |
| |
| double LocalDOMWindow::devicePixelRatio() const { |
| if (!GetFrame()) |
| return 0.0; |
| |
| return GetFrame()->DevicePixelRatio(); |
| } |
| |
| void LocalDOMWindow::scrollBy(double x, double y) const { |
| ScrollToOptions* options = ScrollToOptions::Create(); |
| options->setLeft(x); |
| options->setTop(y); |
| scrollBy(options); |
| } |
| |
| void LocalDOMWindow::scrollBy(const ScrollToOptions* scroll_to_options) const { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return; |
| |
| LocalFrameView* view = GetFrame()->View(); |
| if (!view) |
| return; |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return; |
| |
| // TODO(crbug.com/1499981): This should be removed once synchronized scrolling |
| // impact is understood. |
| SyncScrollAttemptHeuristic::DidSetScrollOffset(); |
| |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| |
| float x = 0.0f; |
| float y = 0.0f; |
| if (scroll_to_options->hasLeft()) { |
| x = ScrollableArea::NormalizeNonFiniteScroll( |
| base::saturated_cast<float>(scroll_to_options->left())); |
| } |
| if (scroll_to_options->hasTop()) { |
| y = ScrollableArea::NormalizeNonFiniteScroll( |
| base::saturated_cast<float>(scroll_to_options->top())); |
| } |
| |
| PaintLayerScrollableArea* viewport = view->LayoutViewport(); |
| gfx::PointF current_position = viewport->ScrollPosition(); |
| gfx::Vector2dF scaled_delta(x * GetFrame()->PageZoomFactor(), |
| y * GetFrame()->PageZoomFactor()); |
| gfx::PointF new_scaled_position = current_position + scaled_delta; |
| |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndAndDirection( |
| current_position, scaled_delta, |
| RuntimeEnabledFeatures::FractionalScrollOffsetsEnabled()); |
| new_scaled_position = |
| viewport->GetSnapPositionAndSetTarget(*strategy).value_or( |
| new_scaled_position); |
| |
| mojom::blink::ScrollBehavior scroll_behavior = |
| mojom::blink::ScrollBehavior::kAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| viewport->SetScrollOffset( |
| viewport->ScrollPositionToOffset(new_scaled_position), |
| mojom::blink::ScrollType::kProgrammatic, scroll_behavior); |
| } |
| |
| void LocalDOMWindow::scrollTo(double x, double y) const { |
| ScrollToOptions* options = ScrollToOptions::Create(); |
| options->setLeft(x); |
| options->setTop(y); |
| scrollTo(options); |
| } |
| |
| void LocalDOMWindow::scrollTo(const ScrollToOptions* scroll_to_options) const { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return; |
| |
| LocalFrameView* view = GetFrame()->View(); |
| if (!view) |
| return; |
| |
| Page* page = GetFrame()->GetPage(); |
| if (!page) |
| return; |
| |
| // TODO(crbug.com/1499981): This should be removed once synchronized scrolling |
| // impact is understood. |
| SyncScrollAttemptHeuristic::DidSetScrollOffset(); |
| |
| // It is only necessary to have an up-to-date layout if the position may be |
| // clamped, which is never the case for (0, 0). |
| if (!scroll_to_options->hasLeft() || !scroll_to_options->hasTop() || |
| scroll_to_options->left() || scroll_to_options->top()) { |
| document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript); |
| } |
| |
| float scaled_x = 0.0f; |
| float scaled_y = 0.0f; |
| |
| PaintLayerScrollableArea* viewport = view->LayoutViewport(); |
| ScrollOffset current_offset = viewport->GetScrollOffset(); |
| scaled_x = current_offset.x(); |
| scaled_y = current_offset.y(); |
| |
| if (scroll_to_options->hasLeft()) { |
| scaled_x = ScrollableArea::NormalizeNonFiniteScroll( |
| base::saturated_cast<float>(scroll_to_options->left())) * |
| GetFrame()->PageZoomFactor(); |
| } |
| |
| if (scroll_to_options->hasTop()) { |
| scaled_y = ScrollableArea::NormalizeNonFiniteScroll( |
| base::saturated_cast<float>(scroll_to_options->top())) * |
| GetFrame()->PageZoomFactor(); |
| } |
| |
| gfx::PointF new_scaled_position = |
| viewport->ScrollOffsetToPosition(ScrollOffset(scaled_x, scaled_y)); |
| |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition( |
| new_scaled_position, scroll_to_options->hasLeft(), |
| scroll_to_options->hasTop()); |
| new_scaled_position = |
| viewport->GetSnapPositionAndSetTarget(*strategy).value_or( |
| new_scaled_position); |
| mojom::blink::ScrollBehavior scroll_behavior = |
| mojom::blink::ScrollBehavior::kAuto; |
| ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), |
| scroll_behavior); |
| viewport->SetScrollOffset( |
| viewport->ScrollPositionToOffset(new_scaled_position), |
| mojom::blink::ScrollType::kProgrammatic, scroll_behavior); |
| } |
| |
| void LocalDOMWindow::moveBy(int x, int y) const { |
| if (!GetFrame() || !GetFrame()->IsOutermostMainFrame() || |
| document()->IsPrerendering()) { |
| return; |
| } |
| |
| if (IsPictureInPictureWindow()) |
| return; |
| |
| LocalFrame* frame = GetFrame(); |
| Page* page = frame->GetPage(); |
| if (!page) |
| return; |
| |
| gfx::Rect window_rect = page->GetChromeClient().RootWindowRect(*frame); |
| window_rect.Offset(x, y); |
| // Security check (the spec talks about UniversalBrowserWrite to disable this |
| // check...) |
| page->GetChromeClient().SetWindowRect(window_rect, *frame); |
| } |
| |
| void LocalDOMWindow::moveTo(int x, int y) const { |
| if (!GetFrame() || !GetFrame()->IsOutermostMainFrame() || |
| document()->IsPrerendering()) { |
| return; |
| } |
| |
| if (IsPictureInPictureWindow()) |
| return; |
| |
| LocalFrame* frame = GetFrame(); |
| Page* page = frame->GetPage(); |
| if (!page) |
| return; |
| |
| gfx::Rect window_rect = page->GetChromeClient().RootWindowRect(*frame); |
| window_rect.set_origin(gfx::Point(x, y)); |
| // Security check (the spec talks about UniversalBrowserWrite to disable this |
| // check...) |
| page->GetChromeClient().SetWindowRect(window_rect, *frame); |
| } |
| |
| void LocalDOMWindow::resizeBy(int x, |
| int y, |
| ExceptionState& exception_state) const { |
| if (!GetFrame() || !GetFrame()->IsOutermostMainFrame() || |
| document()->IsPrerendering()) { |
| return; |
| } |
| |
| if (IsPictureInPictureWindow()) { |
| if (!LocalFrame::ConsumeTransientUserActivation(GetFrame())) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "resizeBy() requires user activation in document picture-in-picture"); |
| return; |
| } |
| } |
| |
| LocalFrame* frame = GetFrame(); |
| Page* page = frame->GetPage(); |
| if (!page) |
| return; |
| |
| gfx::Rect fr = page->GetChromeClient().RootWindowRect(*frame); |
| gfx::Size dest(fr.width() + x, fr.height() + y); |
| gfx::Rect update(fr.origin(), dest); |
| page->GetChromeClient().SetWindowRect(update, *frame); |
| } |
| |
| void LocalDOMWindow::resizeTo(int width, |
| int height, |
| ExceptionState& exception_state) const { |
| if (!GetFrame() || !GetFrame()->IsOutermostMainFrame() || |
| document()->IsPrerendering()) { |
| return; |
| } |
| |
| if (IsPictureInPictureWindow()) { |
| if (!LocalFrame::ConsumeTransientUserActivation(GetFrame())) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotAllowedError, |
| "resizeTo() requires user activation in document picture-in-picture"); |
| return; |
| } |
| } |
| |
| LocalFrame* frame = GetFrame(); |
| Page* page = frame->GetPage(); |
| if (!page) |
| return; |
| |
| gfx::Rect fr = page->GetChromeClient().RootWindowRect(*frame); |
| gfx::Size dest = gfx::Size(width, height); |
| gfx::Rect update(fr.origin(), dest); |
| page->GetChromeClient().SetWindowRect(update, *frame); |
| } |
| |
| int LocalDOMWindow::requestAnimationFrame(V8FrameRequestCallback* callback) { |
| return RequestAnimationFrame(document(), callback, /*legacy=*/false); |
| } |
| |
| int LocalDOMWindow::webkitRequestAnimationFrame( |
| V8FrameRequestCallback* callback) { |
| return RequestAnimationFrame(document(), callback, /*legacy=*/true); |
| } |
| |
| void LocalDOMWindow::cancelAnimationFrame(int id) { |
| document()->CancelAnimationFrame(id); |
| } |
| |
| void LocalDOMWindow::queueMicrotask(V8VoidFunction* callback) { |
| GetAgent()->event_loop()->EnqueueMicrotask( |
| WTF::BindOnce(&V8VoidFunction::InvokeAndReportException, |
| WrapPersistent(callback), nullptr)); |
| } |
| |
| bool LocalDOMWindow::originAgentCluster() const { |
| return GetAgent()->IsOriginKeyed(); |
| } |
| |
| CustomElementRegistry* LocalDOMWindow::customElements( |
| ScriptState* script_state) const { |
| if (!script_state->World().IsMainWorld()) |
| return nullptr; |
| return customElements(); |
| } |
| |
| CustomElementRegistry* LocalDOMWindow::customElements() const { |
| if (!custom_elements_ && document_) { |
| custom_elements_ = MakeGarbageCollected<CustomElementRegistry>(this); |
| custom_elements_->AssociatedWith(*document_); |
| } |
| return custom_elements_.Get(); |
| } |
| |
| CustomElementRegistry* LocalDOMWindow::MaybeCustomElements() const { |
| return custom_elements_.Get(); |
| } |
| |
| External* LocalDOMWindow::external() { |
| if (!external_) |
| external_ = MakeGarbageCollected<External>(); |
| return external_.Get(); |
| } |
| |
| // NOLINTNEXTLINE(bugprone-virtual-near-miss) |
| bool LocalDOMWindow::isSecureContext() const { |
| return IsSecureContext(); |
| } |
| |
| void LocalDOMWindow::ClearIsolatedWorldCSPForTesting(int32_t world_id) { |
| isolated_world_csp_map_->erase(world_id); |
| } |
| |
| bool IsSuddenTerminationDisablerEvent(const AtomicString& event_type) { |
| return event_type == event_type_names::kUnload || |
| event_type == event_type_names::kBeforeunload || |
| event_type == event_type_names::kPagehide || |
| event_type == event_type_names::kVisibilitychange; |
| } |
| |
| void LocalDOMWindow::AddedEventListener( |
| const AtomicString& event_type, |
| RegisteredEventListener& registered_listener) { |
| DOMWindow::AddedEventListener(event_type, registered_listener); |
| if (auto* frame = GetFrame()) { |
| frame->GetEventHandlerRegistry().DidAddEventHandler( |
| *this, event_type, registered_listener.Options()); |
| } |
| |
| document()->AddListenerTypeIfNeeded(event_type, *this); |
| |
| for (auto& it : event_listener_observers_) { |
| it->DidAddEventListener(this, event_type); |
| } |
| |
| if (event_type == event_type_names::kUnload) { |
| CountDeprecation(WebFeature::kDocumentUnloadRegistered); |
| } else if (event_type == event_type_names::kBeforeunload) { |
| UseCounter::Count(this, WebFeature::kDocumentBeforeUnloadRegistered); |
| if (GetFrame() && !GetFrame()->IsMainFrame()) |
| UseCounter::Count(this, WebFeature::kSubFrameBeforeUnloadRegistered); |
| } else if (event_type == event_type_names::kPagehide) { |
| UseCounter::Count(this, WebFeature::kDocumentPageHideRegistered); |
| } else if (event_type == event_type_names::kPageshow) { |
| UseCounter::Count(this, WebFeature::kDocumentPageShowRegistered); |
| } |
| |
| if (GetFrame() && IsSuddenTerminationDisablerEvent(event_type)) |
| GetFrame()->AddedSuddenTerminationDisablerListener(*this, event_type); |
| } |
| |
| void LocalDOMWindow::RemovedEventListener( |
| const AtomicString& event_type, |
| const RegisteredEventListener& registered_listener) { |
| DOMWindow::RemovedEventListener(event_type, registered_listener); |
| if (auto* frame = GetFrame()) { |
| frame->GetEventHandlerRegistry().DidRemoveEventHandler( |
| *this, event_type, registered_listener.Options()); |
| } |
| |
| for (auto& it : event_listener_observers_) { |
| it->DidRemoveEventListener(this, event_type); |
| } |
| |
| // Update sudden termination disabler state if we removed a listener for |
| // unload/beforeunload/pagehide/visibilitychange. |
| if (GetFrame() && IsSuddenTerminationDisablerEvent(event_type)) |
| GetFrame()->RemovedSuddenTerminationDisablerListener(*this, event_type); |
| } |
| |
| void LocalDOMWindow::DispatchLoadEvent() { |
| Event& load_event = *Event::Create(event_type_names::kLoad); |
| DocumentLoader* document_loader = |
| GetFrame() ? GetFrame()->Loader().GetDocumentLoader() : nullptr; |
| if (document_loader && |
| document_loader->GetTiming().LoadEventStart().is_null()) { |
| DocumentLoadTiming& timing = document_loader->GetTiming(); |
| timing.MarkLoadEventStart(); |
| DispatchEvent(load_event, document()); |
| timing.MarkLoadEventEnd(); |
| } else { |
| DispatchEvent(load_event, document()); |
| } |
| |
| if (LocalFrame* frame = GetFrame()) { |
| WindowPerformance* performance = DOMWindowPerformance::performance(*this); |
| DCHECK(performance); |
| performance->NotifyNavigationTimingToObservers(); |
| |
| // For load events, send a separate load event to the enclosing frame only. |
| // This is a DOM extension and is independent of bubbling/capturing rules of |
| // the DOM. |
| if (FrameOwner* owner = frame->Owner()) |
| owner->DispatchLoad(); |
| |
| if (frame->IsAttached()) { |
| DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT( |
| "MarkLoad", inspector_mark_load_event::Data, frame); |
| probe::LoadEventFired(frame); |
| frame->GetFrameScheduler()->OnDispatchLoadEvent(); |
| } |
| } |
| } |
| |
| DispatchEventResult LocalDOMWindow::DispatchEvent(Event& event, |
| EventTarget* target) { |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| |
| event.SetTrusted(true); |
| event.SetTarget(target ? target : this); |
| event.SetCurrentTarget(this); |
| event.SetEventPhase(Event::PhaseType::kAtTarget); |
| |
| DEVTOOLS_TIMELINE_TRACE_EVENT("EventDispatch", |
| inspector_event_dispatch_event::Data, event, |
| GetIsolate()); |
| return FireEventListeners(event); |
| } |
| |
| void LocalDOMWindow::RemoveAllEventListeners() { |
| int previous_unload_handlers_count = |
| NumberOfEventListeners(event_type_names::kUnload); |
| int previous_before_unload_handlers_count = |
| NumberOfEventListeners(event_type_names::kBeforeunload); |
| int previous_page_hide_handlers_count = |
| NumberOfEventListeners(event_type_names::kPagehide); |
| int previous_visibility_change_handlers_count = |
| NumberOfEventListeners(event_type_names::kVisibilitychange); |
| EventTarget::RemoveAllEventListeners(); |
| |
| for (auto& it : event_listener_observers_) { |
| it->DidRemoveAllEventListeners(this); |
| } |
| |
| if (GetFrame()) |
| GetFrame()->GetEventHandlerRegistry().DidRemoveAllEventHandlers(*this); |
| |
| // Update sudden termination disabler state if we previously have listeners |
| // for unload/beforeunload/pagehide/visibilitychange. |
| if (GetFrame() && previous_unload_handlers_count) { |
| GetFrame()->RemovedSuddenTerminationDisablerListener( |
| *this, event_type_names::kUnload); |
| } |
| if (GetFrame() && previous_before_unload_handlers_count) { |
| GetFrame()->RemovedSuddenTerminationDisablerListener( |
| *this, event_type_names::kBeforeunload); |
| } |
| if (GetFrame() && previous_page_hide_handlers_count) { |
| GetFrame()->RemovedSuddenTerminationDisablerListener( |
| *this, event_type_names::kPagehide); |
| } |
| if (GetFrame() && previous_visibility_change_handlers_count) { |
| GetFrame()->RemovedSuddenTerminationDisablerListener( |
| *this, event_type_names::kVisibilitychange); |
| } |
| } |
| |
| void LocalDOMWindow::FinishedLoading(FrameLoader::NavigationFinishState state) { |
| bool was_should_print_when_finished_loading = |
| should_print_when_finished_loading_; |
| should_print_when_finished_loading_ = false; |
| |
| if (was_should_print_when_finished_loading && |
| state == FrameLoader::NavigationFinishState::kSuccess) { |
| print(nullptr); |
| } |
| } |
| |
| void LocalDOMWindow::PrintErrorMessage(const String& message) const { |
| if (!IsCurrentlyDisplayedInFrame()) |
| return; |
| |
| if (message.empty()) |
| return; |
| |
| GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kJavaScript, |
| mojom::ConsoleMessageLevel::kError, message)); |
| } |
| |
| DOMWindow* LocalDOMWindow::open(v8::Isolate* isolate, |
| const String& url_string, |
| const AtomicString& target, |
| const String& features, |
| ExceptionState& exception_state) { |
| // Get the window script is currently executing within the context of. |
| // This is usually, but not necessarily the same as 'this'. |
| LocalDOMWindow* entered_window = EnteredDOMWindow(isolate); |
| |
| if (!IsCurrentlyDisplayedInFrame() || !entered_window->GetFrame()) { |
| return nullptr; |
| } |
| |
| // If the bindings implementation is 100% correct, the current realm and the |
| // entered realm should be same origin-domain. However, to be on the safe |
| // side and add some defense in depth, we'll check against the entry realm |
| // as well here. |
| if (!BindingSecurity::ShouldAllowAccessTo(entered_window, this)) { |
| // Trigger DCHECK() failure, while gracefully failing on release builds. |
| NOTREACHED(); |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kWindowOpenRealmMismatch); |
| return nullptr; |
| } |
| |
| UseCounter::Count(*entered_window, WebFeature::kDOMWindowOpen); |
| entered_window->CountUseOnlyInCrossOriginIframe( |
| WebFeature::kDOMWindowOpenCrossOriginIframe); |
| if (!features.empty()) |
| UseCounter::Count(*entered_window, WebFeature::kDOMWindowOpenFeatures); |
| |
| KURL completed_url = url_string.empty() |
| ? KURL(g_empty_string) |
| : entered_window->CompleteURL(url_string); |
| if (!completed_url.IsEmpty() && !completed_url.IsValid()) { |
| UseCounter::Count(entered_window, WebFeature::kWindowOpenWithInvalidURL); |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "Unable to open a window with invalid URL '" + |
| completed_url.GetString() + "'.\n"); |
| return nullptr; |
| } |
| |
| WebWindowFeatures window_features = |
| GetWindowFeaturesFromString(features, entered_window); |
| |
| // In fenced frames, we should always use `noopener`. |
| if (GetFrame()->IsInFencedFrameTree()) { |
| window_features.noopener = true; |
| } |
| |
| FrameLoadRequest frame_request(entered_window, |
| ResourceRequest(completed_url)); |
| frame_request.SetFeaturesForWindowOpen(window_features); |
| |
| // Normally, FrameLoader would take care of setting the referrer for a |
| // navigation that is triggered from javascript. However, creating a window |
| // goes through sufficient processing that it eventually enters FrameLoader as |
| // an embedder-initiated navigation. FrameLoader assumes no responsibility |
| // for generating an embedder-initiated navigation's referrer, so we need to |
| // ensure the proper referrer is set now. |
| Referrer referrer = SecurityPolicy::GenerateReferrer( |
| entered_window->GetReferrerPolicy(), completed_url, |
| window_features.noreferrer ? Referrer::NoReferrer() |
| : entered_window->OutgoingReferrer()); |
| frame_request.GetResourceRequest().SetReferrerString(referrer.referrer); |
| frame_request.GetResourceRequest().SetReferrerPolicy( |
| referrer.referrer_policy); |
| |
| bool has_user_gesture = LocalFrame::HasTransientUserActivation(GetFrame()); |
| frame_request.GetResourceRequest().SetHasUserGesture(has_user_gesture); |
| |
| if (window_features.attribution_srcs.has_value()) { |
| // An impression must be attached prior to the |
| // `FindOrCreateFrameForNavigation()` call, as that call may result in |
| // performing a navigation if the call results in creating a new window with |
| // noopener set. |
| frame_request.SetImpression(entered_window->GetFrame() |
| ->GetAttributionSrcLoader() |
| ->RegisterNavigation( |
| /*navigation_url=*/completed_url, |
| *window_features.attribution_srcs, |
| has_user_gesture)); |
| } |
| |
| FrameTree::FindResult result = |
| GetFrame()->Tree().FindOrCreateFrameForNavigation( |
| frame_request, target.empty() ? AtomicString("_blank") : target); |
| if (!result.frame) |
| return nullptr; |
| |
| // If the resulting frame didn't create a new window and fullscreen was |
| // requested, reset the flag to prevent making a pre-existing frame |
| // fullscreen. |
| if (window_features.is_fullscreen && |
| (!result.new_window || !window_features.is_popup)) { |
| window_features.is_fullscreen = false; |
| GetFrameConsole()->AddMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kJavaScript, |
| mojom::blink::ConsoleMessageLevel::kWarning, |
| "Fullscreen request ignored: 'fullscreen' " |
| "windowFeature flag requires a new popup window.")); |
| frame_request.SetFeaturesForWindowOpen(window_features); |
| } |
| |
| if (window_features.x_set || window_features.y_set) { |
| // This runs after FindOrCreateFrameForNavigation() so blocked popups are |
| // not counted. |
| UseCounter::Count(*entered_window, |
| WebFeature::kDOMWindowOpenPositioningFeatures); |
| |
| // Coarsely measure whether coordinates may be requesting another screen. |
| ChromeClient& chrome_client = GetFrame()->GetChromeClient(); |
| const gfx::Rect screen = chrome_client.GetScreenInfo(*GetFrame()).rect; |
| const gfx::Rect window(window_features.x, window_features.y, |
| window_features.width, window_features.height); |
| if (!screen.Contains(window)) { |
| UseCounter::Count( |
| *entered_window, |
| WebFeature::kDOMWindowOpenPositioningFeaturesCrossScreen); |
| } |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Popup windows are handled just like new tabs on mobile today, but we might |
| // want to change that. https://crbug.com/1364321 |
| if (window_features.is_popup) { |
| UseCounter::Count(*entered_window, WebFeature::kWindowOpenPopupOnMobile); |
| } |
| #endif |
| |
| if (!completed_url.IsEmpty() || result.new_window) |
| result.frame->Navigate(frame_request, WebFrameLoadType::kStandard); |
| |
| // TODO(japhet): window-open-noopener.html?_top and several tests in |
| // html/browsers/windows/browsing-context-names/ appear to require that |
| // the special case target names (_top, _parent, _self) ignore opener |
| // policy (by always returning a non-null window, and by never overriding |
| // the opener). The spec doesn't mention this. |
| if (EqualIgnoringASCIICase(target, "_top") || |
| EqualIgnoringASCIICase(target, "_parent") || |
| EqualIgnoringASCIICase(target, "_self")) { |
| return result.frame->DomWindow(); |
| } |
| |
| if (window_features.noopener) |
| return nullptr; |
| if (!result.new_window) |
| result.frame->SetOpener(GetFrame()); |
| return result.frame->DomWindow(); |
| } |
| |
| DOMWindow* LocalDOMWindow::openPictureInPictureWindow( |
| v8::Isolate* isolate, |
| const WebPictureInPictureWindowOptions& options, |
| ExceptionState& exception_state) { |
| LocalDOMWindow* entered_window = EnteredDOMWindow(isolate); |
| DCHECK(isSecureContext()); |
| |
| if (!IsCurrentlyDisplayedInFrame() || !entered_window->GetFrame()) { |
| return nullptr; |
| } |
| |
| // If the bindings implementation is 100% correct, the current realm and the |
| // entered realm should be same origin-domain. However, to be on the safe |
| // side and add some defense in depth, we'll check against the entry realm |
| // as well here. |
| if (!BindingSecurity::ShouldAllowAccessTo(entered_window, this)) { |
| // Trigger DCHECK() failure, while gracefully failing on release builds. |
| NOTREACHED(); |
| UseCounter::Count(GetExecutionContext(), |
| WebFeature::kWindowOpenRealmMismatch); |
| return nullptr; |
| } |
| |
| FrameLoadRequest frame_request(entered_window, |
| ResourceRequest(KURL(g_empty_string))); |
| frame_request.SetPictureInPictureWindowOptions(options); |
| |
| // We always create a new window here. |
| FrameTree::FindResult result = |
| GetFrame()->Tree().FindOrCreateFrameForNavigation(frame_request, |
| AtomicString("_blank")); |
| if (!result.frame) |
| return nullptr; |
| |
| // A new window should always be created. |
| DCHECK(result.new_window); |
| |
| result.frame->Navigate(frame_request, WebFrameLoadType::kStandard); |
| LocalDOMWindow* pip_dom_window = |
| To<LocalDOMWindow>(result.frame->DomWindow()); |
| pip_dom_window->SetIsPictureInPictureWindow(); |
| |
| // Also copy any autoplay flags, since these are set on navigation commit. |
| // The pip window should match whatever the opener has. |
| auto* opener_page = entered_window->document()->GetPage(); |
| auto* pip_page = pip_dom_window->document()->GetPage(); |
| CHECK(opener_page); |
| CHECK(pip_page); |
| pip_page->ClearAutoplayFlags(); |
| pip_page->AddAutoplayFlags(opener_page->AutoplayFlags()); |
| |
| return pip_dom_window; |
| } |
| |
| void LocalDOMWindow::Trace(Visitor* visitor) const { |
| visitor->Trace(script_controller_); |
| visitor->Trace(document_); |
| visitor->Trace(screen_); |
| visitor->Trace(history_); |
| visitor->Trace(locationbar_); |
| visitor->Trace(menubar_); |
| visitor->Trace(personalbar_); |
| visitor->Trace(scrollbars_); |
| visitor->Trace(statusbar_); |
| visitor->Trace(toolbar_); |
| visitor->Trace(navigator_); |
| visitor->Trace(media_); |
| visitor->Trace(custom_elements_); |
| visitor->Trace(external_); |
| visitor->Trace(navigation_); |
| visitor->Trace(visualViewport_); |
| visitor->Trace(event_listener_observers_); |
| visitor->Trace(current_event_); |
| visitor->Trace(trusted_types_map_); |
| visitor->Trace(input_method_controller_); |
| visitor->Trace(spell_checker_); |
| visitor->Trace(text_suggestion_controller_); |
| visitor->Trace(isolated_world_csp_map_); |
| visitor->Trace(network_state_observer_); |
| visitor->Trace(fence_); |
| visitor->Trace(closewatcher_stack_); |
| DOMWindow::Trace(visitor); |
| ExecutionContext::Trace(visitor); |
| Supplementable<LocalDOMWindow>::Trace(visitor); |
| } |
| |
| bool LocalDOMWindow::CrossOriginIsolatedCapability() const { |
| return Agent::IsCrossOriginIsolated() && |
| IsFeatureEnabled( |
| mojom::blink::PermissionsPolicyFeature::kCrossOriginIsolated) && |
| GetPolicyContainer()->GetPolicies().allow_cross_origin_isolation; |
| } |
| |
| bool LocalDOMWindow::IsIsolatedContext() const { |
| return Agent::IsIsolatedContext(); |
| } |
| |
| ukm::UkmRecorder* LocalDOMWindow::UkmRecorder() { |
| DCHECK(document_); |
| return document_->UkmRecorder(); |
| } |
| |
| ukm::SourceId LocalDOMWindow::UkmSourceID() const { |
| DCHECK(document_); |
| return document_->UkmSourceID(); |
| } |
| |
| void LocalDOMWindow::SetStorageKey(const BlinkStorageKey& storage_key) { |
| storage_key_ = storage_key; |
| } |
| |
| void LocalDOMWindow::SetSessionStorageKey( |
| const BlinkStorageKey& session_storage_key) { |
| session_storage_key_ = session_storage_key; |
| } |
| |
| bool LocalDOMWindow::IsPaymentRequestTokenActive() const { |
| return payment_request_token_.IsActive(); |
| } |
| |
| bool LocalDOMWindow::ConsumePaymentRequestToken() { |
| return payment_request_token_.ConsumeIfActive(); |
| } |
| |
| bool LocalDOMWindow::IsFullscreenRequestTokenActive() const { |
| return fullscreen_request_token_.IsActive(); |
| } |
| |
| bool LocalDOMWindow::ConsumeFullscreenRequestToken() { |
| return fullscreen_request_token_.ConsumeIfActive(); |
| } |
| |
| bool LocalDOMWindow::IsDisplayCaptureRequestTokenActive() const { |
| return display_capture_request_token_.IsActive(); |
| } |
| |
| bool LocalDOMWindow::ConsumeDisplayCaptureRequestToken() { |
| return display_capture_request_token_.ConsumeIfActive(); |
| } |
| |
| void LocalDOMWindow::SetIsInBackForwardCache(bool is_in_back_forward_cache) { |
| ExecutionContext::SetIsInBackForwardCache(is_in_back_forward_cache); |
| if (!is_in_back_forward_cache) { |
| BackForwardCacheBufferLimitTracker::Get() |
| .DidRemoveFrameOrWorkerFromBackForwardCache( |
| total_bytes_buffered_while_in_back_forward_cache_); |
| total_bytes_buffered_while_in_back_forward_cache_ = 0; |
| } |
| } |
| |
| void LocalDOMWindow::DidBufferLoadWhileInBackForwardCache( |
| bool update_process_wide_count, |
| size_t num_bytes) { |
| total_bytes_buffered_while_in_back_forward_cache_ += num_bytes; |
| if (update_process_wide_count) { |
| BackForwardCacheBufferLimitTracker::Get().DidBufferBytes(num_bytes); |
| } |
| } |
| |
| bool LocalDOMWindow::credentialless() const { |
| return GetExecutionContext() |
| ->GetPolicyContainer() |
| ->GetPolicies() |
| .is_credentialless; |
| } |
| |
| bool LocalDOMWindow::IsInFencedFrame() const { |
| return GetFrame() && GetFrame()->IsInFencedFrameTree(); |
| } |
| |
| Fence* LocalDOMWindow::fence() { |
| // Return nullptr if we aren't in a fenced subtree. |
| if (!GetFrame()) { |
| return nullptr; |
| } |
| if (!GetFrame()->IsInFencedFrameTree()) { |
| // We temporarily allow window.fence in iframes with fenced frame reporting |
| // metadata (navigated by urn:uuids). |
| // If we are in an iframe that doesn't qualify, return nullptr. |
| if (!blink::features::IsAllowURNsInIframeEnabled() || |
| !GetFrame()->GetDocument()->Loader()->FencedFrameProperties() || |
| !GetFrame() |
| ->GetDocument() |
| ->Loader() |
| ->FencedFrameProperties() |
| ->has_fenced_frame_reporting()) { |
| return nullptr; |
| } |
| } |
| |
| if (!fence_) { |
| fence_ = MakeGarbageCollected<Fence>(*this); |
| } |
| |
| return fence_.Get(); |
| } |
| |
| bool LocalDOMWindow::IsPictureInPictureWindow() const { |
| return is_picture_in_picture_window_; |
| } |
| |
| void LocalDOMWindow::SetIsPictureInPictureWindow() { |
| is_picture_in_picture_window_ = true; |
| } |
| |
| bool LocalDOMWindow::HasStorageAccess() const { |
| return has_storage_access_; |
| } |
| |
| void LocalDOMWindow::SetHasStorageAccess() { |
| has_storage_access_ = true; |
| } |
| |
| void LocalDOMWindow::GenerateNewNavigationId() { |
| navigation_id_ = WTF::CreateCanonicalUUIDString(); |
| } |
| |
| void LocalDOMWindow::SetHasBeenRevealed(bool revealed) { |
| if (has_been_revealed_ == revealed) |
| return; |
| has_been_revealed_ = revealed; |
| CHECK(document_); |
| ViewTransitionSupplement::From(*document_)->DidChangeRevealState(); |
| } |
| } // namespace blink |