blob: 664fd015bc9e7f36078e131c036b88074d4756a0 [file] [log] [blame]
/*
* 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 <utility>
#include "base/metrics/histogram_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 "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/action_after_pagehide.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/widget/screen_info.h"
#include "third_party/blink/public/mojom/feature_policy/policy_disposition.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.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/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_impression_params.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/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/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/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/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/feature_policy_violation_report_body.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/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/conversion_measurement_parsing.h"
#include "third_party/blink/renderer/core/html/custom/custom_element_registry.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_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/appcache/application_cache.h"
#include "third_party/blink/renderer/core/loader/document_loader.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/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/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/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/scheduler/public/dummy_schedulers.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/timer.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_functional.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
constexpr size_t kMaxPostMessageUkmRecordedSourceIdsSize = 20;
bool ShouldRecordPostMessageIncomingFrameUkmEvent(
ukm::SourceId source_frame_ukm_source_id,
Deque<ukm::SourceId>& already_recorded_source_frame_ids) {
DCHECK_LE(already_recorded_source_frame_ids.size(),
kMaxPostMessageUkmRecordedSourceIdsSize);
if (base::Contains(already_recorded_source_frame_ids,
source_frame_ukm_source_id)) {
return false;
}
if (already_recorded_source_frame_ids.size() ==
kMaxPostMessageUkmRecordedSourceIdsSize) {
already_recorded_source_frame_ids.pop_back();
}
already_recorded_source_frame_ids.push_front(source_frame_ukm_source_id);
return true;
}
} // namespace
LocalDOMWindow::LocalDOMWindow(LocalFrame& frame, WindowAgent* agent)
: DOMWindow(frame),
ExecutionContext(V8PerIsolateData::MainThreadIsolate(), agent),
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()) {}
void LocalDOMWindow::BindContentSecurityPolicy() {
DCHECK(!GetContentSecurityPolicy()->IsBound());
GetContentSecurityPolicy()->BindToDelegate(
GetContentSecurityPolicyDelegate());
}
void LocalDOMWindow::Initialize() {
GetAgent()->AttachContext(this);
}
void LocalDOMWindow::ResetWindowAgent(WindowAgent* agent) {
GetAgent()->DetachContext(this);
ResetAgent(agent);
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(),
ToV8(ToV8UndefinedGenerator(), script_state));
}
return ScriptValue(script_state->GetIsolate(),
ToV8(CurrentEvent(), script_state));
}
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;
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;
// 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);
}
LocalDOMWindow* LocalDOMWindow::From(const ScriptState* script_state) {
v8::HandleScope scope(script_state->GetIsolate());
return blink::ToLocalDOMWindow(script_state->GetContext());
}
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;
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);
}
String LocalDOMWindow::UserAgent() const {
return GetFrame() ? GetFrame()->Loader().UserAgent() : String();
}
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() const {
return document()->Fetcher();
}
bool LocalDOMWindow::CanExecuteScripts(
ReasonForCallingCanExecuteScripts reason) {
if (!GetFrame())
return false;
// 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::ShouldBypassMainWorld(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;
}
WebContentSettingsClient* settings_client =
GetFrame()->GetContentSettingsClient();
bool script_enabled = GetFrame()->GetSettings()->GetScriptEnabled();
if (settings_client)
script_enabled = settings_client->AllowScript(script_enabled);
if (!script_enabled && reason == kAboutToExecuteScript && settings_client)
settings_client->DidNotAllowScript();
return script_enabled;
}
void LocalDOMWindow::ExceptionThrown(ErrorEvent* event) {
MainThreadDebugger::Instance()->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();
}
network::mojom::ReferrerPolicy LocalDOMWindow::GetReferrerPolicy() const {
network::mojom::ReferrerPolicy policy = ExecutionContext::GetReferrerPolicy();
// PolicyContainer took care already of policy inheritance.
if (base::FeatureList::IsEnabled(blink::features::kPolicyContainer)) {
return policy;
}
// For srcdoc documents without their own policy, walk up the frame
// tree to find the document that is either not a srcdoc or doesn't
// have its own policy. This algorithm is defined in
// https://html.spec.whatwg.org/C/#set-up-a-window-environment-settings-object.
if (!GetFrame() || policy != network::mojom::ReferrerPolicy::kDefault ||
!document()->IsSrcdocDocument()) {
return policy;
}
LocalFrame* frame = To<LocalFrame>(GetFrame()->Tree().Parent());
return frame->DomWindow()->GetReferrerPolicy();
}
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();
return detached_scheduler_.get();
}
scoped_refptr<base::SingleThreadTaskRunner> LocalDOMWindow::GetTaskRunner(
TaskType type) {
if (GetFrame())
return GetFrame()->GetTaskRunner(type);
// 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).
return Thread::Current()->GetTaskRunner();
}
void LocalDOMWindow::CountPotentialFeaturePolicyViolation(
mojom::blink::FeaturePolicyFeature feature) const {
wtf_size_t index = static_cast<wtf_size_t>(feature);
if (potentially_violated_features_.size() == 0) {
potentially_violated_features_.resize(
static_cast<wtf_size_t>(mojom::blink::FeaturePolicyFeature::kMaxValue) +
1);
} else if (potentially_violated_features_[index]) {
return;
}
potentially_violated_features_[index] = true;
UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.FeaturePolicy.PotentialViolation",
feature);
}
void LocalDOMWindow::ReportFeaturePolicyViolation(
mojom::blink::FeaturePolicyFeature feature,
mojom::blink::PolicyDisposition disposition,
const String& message) const {
if (!RuntimeEnabledFeatures::FeaturePolicyReportingEnabled(this))
return;
if (!GetFrame())
return;
// Construct the feature policy violation report.
const String& feature_name = GetNameForFeature(feature);
const String& disp_str =
(disposition == mojom::blink::PolicyDisposition::kReport ? "report"
: "enforce");
FeaturePolicyViolationReportBody* body =
MakeGarbageCollected<FeaturePolicyViolationReportBody>(feature_name,
message, disp_str);
Report* report = MakeGarbageCollected<Report>(
ReportType::kFeaturePolicyViolation, Url().GetString(), body);
// Send the feature policy violation report to any ReportingObservers.
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 base::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()));
}
}
static void RunAddConsoleMessageTask(mojom::ConsoleMessageSource source,
mojom::ConsoleMessageLevel level,
const String& message,
LocalDOMWindow* window,
bool discard_duplicates) {
window->AddConsoleMessageImpl(
MakeGarbageCollected<ConsoleMessage>(source, level, message),
discard_duplicates);
}
void LocalDOMWindow::AddConsoleMessageImpl(ConsoleMessage* console_message,
bool discard_duplicates) {
if (!IsContextThread()) {
PostCrossThreadTask(
*GetTaskRunner(TaskType::kInternalInspector), FROM_HERE,
CrossThreadBindOnce(
&RunAddConsoleMessageTask, console_message->Source(),
console_message->Level(), console_message->Message(),
WrapCrossThreadPersistent(this), discard_duplicates));
return;
}
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());
console_message = MakeGarbageCollected<ConsoleMessage>(
console_message->Source(), console_message->Level(),
console_message->Message(),
std::make_unique<SourceLocation>(Url().GetString(), line_number, 0,
nullptr));
console_message->SetNodes(GetFrame(), std::move(nodes));
}
GetFrame()->Console().AddMessage(console_message, discard_duplicates);
}
void LocalDOMWindow::AddInspectorIssue(
mojom::blink::InspectorIssueInfoPtr info) {
if (GetFrame()) {
GetFrame()->AddInspectorIssue(std::move(info));
}
}
void LocalDOMWindow::CountUse(mojom::WebFeature feature) {
if (!GetFrame())
return;
if (auto* loader = GetFrame()->Loader().GetDocumentLoader())
loader->CountUse(feature);
}
void LocalDOMWindow::CountUseOnlyInCrossOriginIframe(
mojom::blink::WebFeature feature) {
if (GetFrame() && GetFrame()->IsCrossOriginToMainFrame())
CountUse(feature);
}
bool LocalDOMWindow::HasInsecureContextInAncestors() {
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();
GetScriptController().UpdateDocument();
document_->GetViewportData().UpdateViewportDescription();
auto* frame_scheduler = GetFrame()->GetFrameScheduler();
frame_scheduler->TraceUrlChange(document_->Url().GetString());
frame_scheduler->SetCrossOriginToMainFrame(
GetFrame()->IsCrossOriginToMainFrame());
GetFrame()->GetPage()->GetChromeClient().InstallSupplements(*GetFrame());
#if !defined(OS_ANDROID)
// Enable SharedArrayBuffer for the reverse Origin Trial.
if (RuntimeEnabledFeatures::UnrestrictedSharedArrayBufferEnabled(this)) {
v8::V8::SetIsCrossOriginIsolated();
}
#endif
return document_;
}
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::Bind(&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();
if (pending_state_object_)
EnqueuePopstateEvent(std::move(pending_state_object_));
}
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);
return;
}
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_->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) {
// https://html.spec.whatwg.org/C/#history-traversal
EnqueueWindowEvent(*HashChangeEvent::Create(old_url, new_url),
TaskType::kDOMManipulation);
}
void LocalDOMWindow::EnqueuePopstateEvent(
scoped_refptr<SerializedScriptValue> state_object) {
// FIXME: https://bugs.webkit.org/show_bug.cgi?id=36202 Popstate event needs
// to fire asynchronously
DispatchEvent(*PopStateEvent::Create(std::move(state_object), history()));
}
void LocalDOMWindow::StatePopped(
scoped_refptr<SerializedScriptValue> state_object) {
if (!GetFrame())
return;
// Per step 11 of section 6.5.9 (history traversal) of the HTML5 spec, we
// defer firing of popstate until we're in the complete state.
if (document()->IsLoadCompleted())
EnqueuePopstateEvent(std::move(state_object));
else
pending_state_object_ = std::move(state_object);
}
LocalDOMWindow::~LocalDOMWindow() = default;
void LocalDOMWindow::Dispose() {
// 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() {
// 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()->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;
application_cache_ = 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_)
screen_ = MakeGarbageCollected<Screen>(this);
return screen_.Get();
}
History* LocalDOMWindow::history() {
if (!history_)
history_ = MakeGarbageCollected<History>(this);
return history_.Get();
}
BarProp* LocalDOMWindow::locationbar() {
if (!locationbar_) {
locationbar_ = MakeGarbageCollected<BarProp>(this, BarProp::kLocationbar);
}
return locationbar_.Get();
}
BarProp* LocalDOMWindow::menubar() {
if (!menubar_)
menubar_ = MakeGarbageCollected<BarProp>(this, BarProp::kMenubar);
return menubar_.Get();
}
BarProp* LocalDOMWindow::personalbar() {
if (!personalbar_) {
personalbar_ = MakeGarbageCollected<BarProp>(this, BarProp::kPersonalbar);
}
return personalbar_.Get();
}
BarProp* LocalDOMWindow::scrollbars() {
if (!scrollbars_) {
scrollbars_ = MakeGarbageCollected<BarProp>(this, BarProp::kScrollbars);
}
return scrollbars_.Get();
}
BarProp* LocalDOMWindow::statusbar() {
if (!statusbar_)
statusbar_ = MakeGarbageCollected<BarProp>(this, BarProp::kStatusbar);
return statusbar_.Get();
}
BarProp* LocalDOMWindow::toolbar() {
if (!toolbar_)
toolbar_ = MakeGarbageCollected<BarProp>(this, BarProp::kToolbar);
return toolbar_.Get();
}
FrameConsole* LocalDOMWindow::GetFrameConsole() const {
if (!IsCurrentlyDisplayedInFrame())
return nullptr;
return &GetFrame()->Console();
}
ApplicationCache* LocalDOMWindow::applicationCache() {
DCHECK(RuntimeEnabledFeatures::AppCacheEnabled(this));
if (!IsCurrentlyDisplayedInFrame())
return nullptr;
if (!IsSecureContext()) {
Deprecation::CountDeprecation(
this, WebFeature::kApplicationCacheAPIInsecureOrigin);
}
if (!application_cache_)
application_cache_ = MakeGarbageCollected<ApplicationCache>(this);
return application_cache_.Get();
}
Navigator* LocalDOMWindow::navigator() {
if (!navigator_)
navigator_ = MakeGarbageCollected<Navigator>(this);
return navigator_.Get();
}
void LocalDOMWindow::SchedulePostMessage(
MessageEvent* event,
scoped_refptr<const SecurityOrigin> target,
LocalDOMWindow* source) {
// Record UKM metrics for postMessage event.
ukm::SourceId source_frame_ukm_source_id = source->UkmSourceID();
if (ShouldRecordPostMessageIncomingFrameUkmEvent(
source_frame_ukm_source_id, post_message_ukm_recorded_source_ids_)) {
ukm::builders::PostMessage_Incoming_Frame(UkmSourceID())
.SetSourceFrameSourceId(source_frame_ukm_source_id)
.Record(UkmRecorder());
}
// 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 = SourceLocation::Capture(source);
GetTaskRunner(TaskType::kPostedMessage)
->PostTask(
FROM_HERE,
WTF::Bind(&LocalDOMWindow::DispatchPostMessage, WrapPersistent(this),
WrapPersistent(event), std::move(target),
std::move(location), source->GetAgent()->cluster_id()));
probe::AsyncTaskScheduled(this, "postMessage", event->async_task_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_id(), nullptr /* step */,
true /* enabled */,
probe::AsyncTask::AdTrackingType::kIgnore);
if (!IsCurrentlyDisplayedInFrame())
return;
event->EntangleMessagePorts(this);
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::kError, 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 (GetFrame() && GetFrame()->GetPage() &&
GetFrame()->GetPage()->DispatchedPagehideAndStillHidden() &&
!document()->UnloadEventInProgress()) {
// The message arrived after the pagehide event got dispatched and the page
// is still hidden, which is not normally possible (this might happen if
// we're doing a same-site cross-RenderFrame navigation where we dispatch
// pagehide during the new RenderFrame's commit but won't unload/freeze the
// page after the new RenderFrame finished committing). We should track
// this case to measure how often this is happening, except for when the
// unload event is currently in progress, which means the page is not
// actually stored in the back-forward cache and this behavior is ok.
UMA_HISTOGRAM_ENUMERATION("BackForwardCache.SameSite.ActionAfterPagehide2",
ActionAfterPagehide::kReceivedPostMessage);
}
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::blur() {}
void LocalDOMWindow::print(ScriptState* script_state) {
// Don't try to print if there's no frame attached anymore.
if (!GetFrame())
return;
if (script_state &&
v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
UseCounter::Count(this, WebFeature::kDuring_Microtask_Print);
}
if (GetFrame()->IsLoading()) {
should_print_when_finished_loading_ = true;
return;
}
if (!GetFrame()->IsMainFrame() && !GetFrame()->IsCrossOriginToMainFrame()) {
CountUse(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::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Ignored call to 'alert()'. The document is sandboxed, and the "
"'allow-modals' keyword is not set."));
return;
}
if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
UseCounter::Count(this, WebFeature::kDuring_Microtask_Alert);
}
document()->UpdateStyleAndLayoutTree();
Page* page = GetFrame()->GetPage();
if (!page)
return;
if (!GetFrame()->IsMainFrame() && !GetFrame()->IsCrossOriginToMainFrame()) {
CountUse(WebFeature::kSameOriginIframeWindowAlert);
}
CountUseOnlyInCrossOriginIframe(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::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Ignored call to 'confirm()'. The document is sandboxed, and the "
"'allow-modals' keyword is not set."));
return false;
}
if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
UseCounter::Count(this, WebFeature::kDuring_Microtask_Confirm);
}
document()->UpdateStyleAndLayoutTree();
Page* page = GetFrame()->GetPage();
if (!page)
return false;
if (!GetFrame()->IsMainFrame() && !GetFrame()->IsCrossOriginToMainFrame()) {
CountUse(WebFeature::kSameOriginIframeWindowConfirm);
}
CountUseOnlyInCrossOriginIframe(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::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Ignored call to 'prompt()'. The document is sandboxed, and the "
"'allow-modals' keyword is not set."));
return String();
}
if (v8::MicrotasksScope::IsRunningMicrotasks(script_state->GetIsolate())) {
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;
if (!GetFrame()->IsMainFrame() && !GetFrame()->IsCrossOriginToMainFrame()) {
CountUse(WebFeature::kSameOriginIframeWindowPrompt);
}
CountUseOnlyInCrossOriginIframe(WebFeature::kCrossOriginWindowPrompt);
return String();
}
bool LocalDOMWindow::find(const String& string,
bool case_sensitive,
bool backwards,
bool wrap,
bool whole_word,
bool /*searchInFrames*/,
bool /*showDialog*/) const {
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();
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();
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();
}
IntSize LocalDOMWindow::GetViewportSize() const {
LocalFrameView* view = GetFrame()->View();
if (!view)
return IntSize();
Page* page = GetFrame()->GetPage();
if (!page)
return IntSize();
// 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;
document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
// TODO(bokan): This is wrong when the document.rootScroller is non-default.
// crbug.com/505516.
double viewport_x = view->LayoutViewport()->GetScrollOffset().Width();
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;
document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
// TODO(bokan): This is wrong when the document.rootScroller is non-default.
// crbug.com/505516.
double viewport_y = view->LayoutViewport()->GetScrollOffset().Height();
return AdjustForAbsoluteZoom::AdjustScroll(viewport_y,
GetFrame()->PageZoomFactor());
}
HeapVector<Member<DOMRect>> LocalDOMWindow::getWindowSegments() const {
HeapVector<Member<DOMRect>> window_segments;
LocalFrame* frame = GetFrame();
if (!frame)
return window_segments;
Page* page = frame->GetPage();
if (!page)
return window_segments;
WebVector<gfx::Rect> web_segments =
frame->GetWidgetForLocalRoot()->WindowSegments();
// The rect passed to us from content is in DIP, relative to the main
// frame/widget. This doesn't take the page's zoom factor into account so we
// must scale by the inverse of the page zoom in order to get correct client
// coordinates.
// Note that when use-zoom-for-dsf is enabled, WindowToViewportScalar will
// be the device scale factor, and PageZoomFactor will be the combination
// of the device scale factor and the zoom percent of the page.
ChromeClient& chrome_client = page->GetChromeClient();
const float window_to_viewport_factor =
chrome_client.WindowToViewportScalar(frame, 1.0f);
const float page_zoom_factor = frame->PageZoomFactor();
const float scale_factor = window_to_viewport_factor / page_zoom_factor;
for (auto const& web_segment : web_segments) {
blink::FloatQuad quad = blink::FloatQuad(IntRect(web_segment));
quad.Scale(scale_factor, scale_factor);
window_segments.push_back(DOMRect::FromFloatRect(quad.BoundingBox()));
}
return window_segments;
}
DOMVisualViewport* LocalDOMWindow::visualViewport() {
return visualViewport_;
}
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) {
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 LocalDOMWindow::getComputedAccessibleNode(
ScriptState* script_state,
Element* element) {
DCHECK(element);
auto* resolver = MakeGarbageCollected<ComputedAccessibleNodePromiseResolver>(
script_state, *element);
ScriptPromise 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;
document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
LocalFrameView* view = GetFrame()->View();
if (!view)
return;
Page* page = GetFrame()->GetPage();
if (!page)
return;
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();
FloatPoint current_position = viewport->ScrollPosition();
FloatPoint scaled_delta(x * GetFrame()->PageZoomFactor(),
y * GetFrame()->PageZoomFactor());
FloatPoint new_scaled_position = current_position + scaled_delta;
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndAndDirection(
gfx::ScrollOffset(current_position), gfx::ScrollOffset(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;
// 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.Width();
scaled_y = current_offset.Height();
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();
}
FloatPoint new_scaled_position =
viewport->ScrollOffsetToPosition(ScrollOffset(scaled_x, scaled_y));
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(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()->IsMainFrame())
return;
LocalFrame* frame = GetFrame();
Page* page = frame->GetPage();
if (!page)
return;
IntRect window_rect = page->GetChromeClient().RootWindowRect(*frame);
window_rect.SaturatedMove(x, y);
// Security check (the spec talks about UniversalBrowserWrite to disable this
// check...)
page->GetChromeClient().SetWindowRectWithAdjustment(window_rect, *frame);
}
void LocalDOMWindow::moveTo(int x, int y) const {
if (!GetFrame() || !GetFrame()->IsMainFrame())
return;
LocalFrame* frame = GetFrame();
Page* page = frame->GetPage();
if (!page)
return;
IntRect window_rect = page->GetChromeClient().RootWindowRect(*frame);
window_rect.SetLocation(IntPoint(x, y));
// Security check (the spec talks about UniversalBrowserWrite to disable this
// check...)
page->GetChromeClient().SetWindowRectWithAdjustment(window_rect, *frame);
}
void LocalDOMWindow::resizeBy(int x, int y) const {
if (!GetFrame() || !GetFrame()->IsMainFrame())
return;
LocalFrame* frame = GetFrame();
Page* page = frame->GetPage();
if (!page)
return;
IntRect fr = page->GetChromeClient().RootWindowRect(*frame);
IntSize dest = fr.Size() + IntSize(x, y);
IntRect update(fr.Location(), dest);
page->GetChromeClient().SetWindowRectWithAdjustment(update, *frame);
}
void LocalDOMWindow::resizeTo(int width, int height) const {
if (!GetFrame() || !GetFrame()->IsMainFrame())
return;
LocalFrame* frame = GetFrame();
Page* page = frame->GetPage();
if (!page)
return;
IntRect fr = page->GetChromeClient().RootWindowRect(*frame);
IntSize dest = IntSize(width, height);
IntRect update(fr.Location(), dest);
page->GetChromeClient().SetWindowRectWithAdjustment(update, *frame);
}
int LocalDOMWindow::requestAnimationFrame(V8FrameRequestCallback* callback) {
auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
frame_callback->SetUseLegacyTimeBase(false);
return document()->RequestAnimationFrame(frame_callback);
}
int LocalDOMWindow::webkitRequestAnimationFrame(
V8FrameRequestCallback* callback) {
auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
frame_callback->SetUseLegacyTimeBase(true);
return document()->RequestAnimationFrame(frame_callback);
}
void LocalDOMWindow::cancelAnimationFrame(int id) {
document()->CancelAnimationFrame(id);
}
void LocalDOMWindow::queueMicrotask(V8VoidFunction* callback) {
Microtask::EnqueueMicrotask(
WTF::Bind(&V8VoidFunction::InvokeAndReportException,
WrapPersistent(callback), nullptr));
}
const Vector<String>& LocalDOMWindow::originPolicyIds() const {
return origin_policy_ids_;
}
void LocalDOMWindow::SetOriginPolicyIds(const Vector<String>& ids) {
origin_policy_ids_ = ids;
}
bool LocalDOMWindow::originAgentCluster() const {
return GetAgent()->IsOriginKeyed();
}
int LocalDOMWindow::requestIdleCallback(V8IdleRequestCallback* callback,
const IdleRequestOptions* options) {
if (!GetFrame())
return 0;
return document_->RequestIdleCallback(V8IdleTask::Create(callback), options);
}
void LocalDOMWindow::cancelIdleCallback(int id) {
document()->CancelIdleCallback(id);
}
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);
return custom_elements_;
}
CustomElementRegistry* LocalDOMWindow::MaybeCustomElements() const {
return custom_elements_;
}
void LocalDOMWindow::SetModulator(Modulator* modulator) {
DCHECK(!modulator_);
modulator_ = modulator;
}
External* LocalDOMWindow::external() {
if (!external_)
external_ = MakeGarbageCollected<External>();
return external_;
}
bool LocalDOMWindow::isSecureContext() const {
return GetFrame() && 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) {
UseCounter::Count(this, 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 (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.
FrameOwner* owner = GetFrame() ? GetFrame()->Owner() : nullptr;
if (owner)
owner->DispatchLoad();
TRACE_EVENT_INSTANT1("devtools.timeline", "MarkLoad",
TRACE_EVENT_SCOPE_THREAD, "data",
inspector_mark_load_event::Data(GetFrame()));
probe::LoadEventFired(GetFrame());
}
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::kAtTarget);
TRACE_EVENT1("devtools.timeline", "EventDispatch", "data",
inspector_event_dispatch_event::Data(event));
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.IsEmpty())
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) {
return open(isolate, url_string, target, features,
nullptr /* impression_params */, exception_state);
}
DOMWindow* LocalDOMWindow::open(v8::Isolate* isolate,
const String& url_string,
const AtomicString& target,
const String& features,
bool unused,
ExceptionState& exception_state) {
UseCounter::Count(this, WebFeature::kWindowOpenWithAdditionalBoolParameter);
PrintErrorMessage(
"A boolean is being passed as a fourth parameter to "
"window.open. This is not used and may cause an "
"exception in a future release.");
// Ignore the unused bool argument.
return open(isolate, url_string, target, features,
nullptr /* impression_params */, exception_state);
}
DOMWindow* LocalDOMWindow::open(v8::Isolate* isolate,
const String& url_string,
const AtomicString& target,
const String& features,
const ImpressionParams* impression_params,
ExceptionState& exception_state) {
if (!RuntimeEnabledFeatures::ConversionMeasurementEnabled(this)) {
// The usage of `impression_params` is gated on a runtime enabled feature
// in the implementation rather than in the IDL definition, because the
// RuntimeEnabled extended attribute cannot be used with an operation
// overload.
impression_params = nullptr;
}
LocalDOMWindow* incumbent_window = IncumbentDOMWindow(isolate);
LocalDOMWindow* entered_window = EnteredDOMWindow(isolate);
// 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,
exception_state)) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kWindowOpenRealmMismatch);
return nullptr;
}
if (!IsCurrentlyDisplayedInFrame())
return nullptr;
if (!incumbent_window->GetFrame() || !entered_window->GetFrame())
return nullptr;
UseCounter::Count(*incumbent_window, WebFeature::kDOMWindowOpen);
if (!features.IsEmpty())
UseCounter::Count(*incumbent_window, WebFeature::kDOMWindowOpenFeatures);
KURL completed_url = url_string.IsEmpty()
? KURL(g_empty_string)
: entered_window->CompleteURL(url_string);
if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
UseCounter::Count(incumbent_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);
FrameLoadRequest frame_request(incumbent_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(
incumbent_window->GetReferrerPolicy(), completed_url,
window_features.noreferrer ? Referrer::NoReferrer()
: incumbent_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);
GetFrame()->MaybeLogAdClickNavigation();
if (has_user_gesture && impression_params) {
base::Optional<WebImpression> impression =
GetImpressionForParams(incumbent_window, impression_params);
if (impression)
frame_request.SetImpression(*impression);
}
FrameTree::FindResult result =
GetFrame()->Tree().FindOrCreateFrameForNavigation(
frame_request, target.IsEmpty() ? "_blank" : target);
if (!result.frame)
return nullptr;
if (window_features.x_set || window_features.y_set) {
// This runs after FindOrCreateFrameForNavigation() so blocked popups are
// not counted.
UseCounter::Count(*incumbent_window,
WebFeature::kDOMWindowOpenPositioningFeatures);
// Coarsely measure whether coordinates may be requesting another screen.
ChromeClient& chrome_client = GetFrame()->GetChromeClient();
const IntRect screen(chrome_client.GetScreenInfo(*GetFrame()).rect);
const IntRect window(window_features.x, window_features.y,
window_features.width, window_features.height);
if (!screen.Contains(window)) {
UseCounter::Count(
*incumbent_window,
WebFeature::kDOMWindowOpenPositioningFeaturesCrossScreen);
}
}
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();
}
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(modulator_);
visitor->Trace(external_);
visitor->Trace(application_cache_);
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_);
DOMWindow::Trace(visitor);
ExecutionContext::Trace(visitor);
Supplementable<LocalDOMWindow>::Trace(visitor);
}
bool LocalDOMWindow::CrossOriginIsolatedCapability() const {
return Agent::IsCrossOriginIsolated() &&
IsFeatureEnabled(
mojom::blink::FeaturePolicyFeature::kCrossOriginIsolated);
}
ukm::UkmRecorder* LocalDOMWindow::UkmRecorder() {
DCHECK(document_);
return document_->UkmRecorder();
}
ukm::SourceId LocalDOMWindow::UkmSourceID() const {
DCHECK(document_);
return document_->UkmSourceID();
}
} // namespace blink