1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/frame/dom_window.h"
6 
7 #include <algorithm>
8 #include <memory>
9 
10 #include "base/metrics/histogram_macros.h"
11 #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
12 #include "third_party/blink/public/common/action_after_pagehide.h"
13 #include "third_party/blink/renderer/bindings/core/v8/serialization/post_message_helper.h"
14 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
15 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
16 #include "third_party/blink/renderer/bindings/core/v8/v8_window.h"
17 #include "third_party/blink/renderer/bindings/core/v8/v8_window_post_message_options.h"
18 #include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h"
19 #include "third_party/blink/renderer/core/dom/document.h"
20 #include "third_party/blink/renderer/core/events/message_event.h"
21 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
22 #include "third_party/blink/renderer/core/execution_context/security_context.h"
23 #include "third_party/blink/renderer/core/frame/coop_access_violation_report_body.h"
24 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
25 #include "third_party/blink/renderer/core/frame/frame.h"
26 #include "third_party/blink/renderer/core/frame/frame_client.h"
27 #include "third_party/blink/renderer/core/frame/frame_console.h"
28 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
29 #include "third_party/blink/renderer/core/frame/location.h"
30 #include "third_party/blink/renderer/core/frame/report.h"
31 #include "third_party/blink/renderer/core/frame/reporting_context.h"
32 #include "third_party/blink/renderer/core/frame/settings.h"
33 #include "third_party/blink/renderer/core/frame/user_activation.h"
34 #include "third_party/blink/renderer/core/input/input_device_capabilities.h"
35 #include "third_party/blink/renderer/core/inspector/console_message.h"
36 #include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
37 #include "third_party/blink/renderer/core/page/chrome_client.h"
38 #include "third_party/blink/renderer/core/page/focus_controller.h"
39 #include "third_party/blink/renderer/core/page/page.h"
40 #include "third_party/blink/renderer/core/probe/core_probes.h"
41 #include "third_party/blink/renderer/platform/heap/heap.h"
42 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
43 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
44 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
45 
46 namespace blink {
47 
DOMWindow(Frame & frame)48 DOMWindow::DOMWindow(Frame& frame)
49     : frame_(frame),
50       window_proxy_manager_(frame.GetWindowProxyManager()),
51       window_is_closing_(false) {}
52 
~DOMWindow()53 DOMWindow::~DOMWindow() {
54   // The frame must be disconnected before finalization.
55   DCHECK(!frame_);
56 }
57 
Wrap(v8::Isolate * isolate,v8::Local<v8::Object> creation_context)58 v8::Local<v8::Value> DOMWindow::Wrap(v8::Isolate* isolate,
59                                      v8::Local<v8::Object> creation_context) {
60   // TODO(yukishiino): Get understanding of why it's possible to initialize
61   // the context after the frame is detached.  And then, remove the following
62   // lines.  See also https://crbug.com/712638 .
63   Frame* frame = GetFrame();
64   if (!frame)
65     return v8::Null(isolate);
66 
67   // TODO(yukishiino): Make this function always return the non-empty handle
68   // even if the frame is detached because the global proxy must always exist
69   // per spec.
70   ScriptState* script_state = ScriptState::From(isolate->GetCurrentContext());
71   return frame->GetWindowProxy(script_state->World())
72       ->GlobalProxyIfNotDetached();
73 }
74 
AssociateWithWrapper(v8::Isolate *,const WrapperTypeInfo *,v8::Local<v8::Object> wrapper)75 v8::Local<v8::Object> DOMWindow::AssociateWithWrapper(
76     v8::Isolate*,
77     const WrapperTypeInfo*,
78     v8::Local<v8::Object> wrapper) {
79   NOTREACHED();
80   return v8::Local<v8::Object>();
81 }
82 
InterfaceName() const83 const AtomicString& DOMWindow::InterfaceName() const {
84   return event_target_names::kWindow;
85 }
86 
ToDOMWindow() const87 const DOMWindow* DOMWindow::ToDOMWindow() const {
88   return this;
89 }
90 
IsWindowOrWorkerGlobalScope() const91 bool DOMWindow::IsWindowOrWorkerGlobalScope() const {
92   return true;
93 }
94 
location() const95 Location* DOMWindow::location() const {
96   if (!location_)
97     location_ = MakeGarbageCollected<Location>(const_cast<DOMWindow*>(this));
98   return location_.Get();
99 }
100 
closed() const101 bool DOMWindow::closed() const {
102   return window_is_closing_ || !GetFrame() || !GetFrame()->GetPage();
103 }
104 
length() const105 unsigned DOMWindow::length() const {
106   return GetFrame() ? GetFrame()->Tree().ScopedChildCount() : 0;
107 }
108 
self() const109 DOMWindow* DOMWindow::self() const {
110   if (!GetFrame())
111     return nullptr;
112 
113   return GetFrame()->DomWindow();
114 }
115 
opener() const116 DOMWindow* DOMWindow::opener() const {
117   // FIXME: Use FrameTree to get opener as well, to simplify logic here.
118   if (!GetFrame() || !GetFrame()->Client())
119     return nullptr;
120 
121   Frame* opener = GetFrame()->Opener();
122   return opener ? opener->DomWindow() : nullptr;
123 }
124 
parent() const125 DOMWindow* DOMWindow::parent() const {
126   if (!GetFrame())
127     return nullptr;
128 
129   Frame* parent = GetFrame()->Tree().Parent();
130   return parent ? parent->DomWindow() : GetFrame()->DomWindow();
131 }
132 
top() const133 DOMWindow* DOMWindow::top() const {
134   if (!GetFrame())
135     return nullptr;
136 
137   return GetFrame()->Tree().Top().DomWindow();
138 }
139 
postMessage(v8::Isolate * isolate,const ScriptValue & message,const String & target_origin,HeapVector<ScriptValue> & transfer,ExceptionState & exception_state)140 void DOMWindow::postMessage(v8::Isolate* isolate,
141                             const ScriptValue& message,
142                             const String& target_origin,
143                             HeapVector<ScriptValue>& transfer,
144                             ExceptionState& exception_state) {
145   WindowPostMessageOptions* options = WindowPostMessageOptions::Create();
146   options->setTargetOrigin(target_origin);
147   if (!transfer.IsEmpty())
148     options->setTransfer(transfer);
149   postMessage(isolate, message, options, exception_state);
150 }
151 
postMessage(v8::Isolate * isolate,const ScriptValue & message,const WindowPostMessageOptions * options,ExceptionState & exception_state)152 void DOMWindow::postMessage(v8::Isolate* isolate,
153                             const ScriptValue& message,
154                             const WindowPostMessageOptions* options,
155                             ExceptionState& exception_state) {
156   LocalDOMWindow* incumbent_window = IncumbentDOMWindow(isolate);
157   UseCounter::Count(incumbent_window->document(),
158                     WebFeature::kWindowPostMessage);
159 
160   Transferables transferables;
161   scoped_refptr<SerializedScriptValue> serialized_message =
162       PostMessageHelper::SerializeMessageByMove(isolate, message, options,
163                                                 transferables, exception_state);
164   if (exception_state.HadException())
165     return;
166   DCHECK(serialized_message);
167   DoPostMessage(std::move(serialized_message), transferables.message_ports,
168                 options, incumbent_window, exception_state);
169 }
170 
AnonymousIndexedGetter(uint32_t index)171 DOMWindow* DOMWindow::AnonymousIndexedGetter(uint32_t index) {
172   ReportCoopAccess("indexed");
173 
174   if (!GetFrame())
175     return nullptr;
176 
177   Frame* child = GetFrame()->Tree().ScopedChild(index);
178   return child ? child->DomWindow() : nullptr;
179 }
180 
IsCurrentlyDisplayedInFrame() const181 bool DOMWindow::IsCurrentlyDisplayedInFrame() const {
182   if (GetFrame())
183     SECURITY_CHECK(GetFrame()->DomWindow() == this);
184   return GetFrame() && GetFrame()->GetPage();
185 }
186 
187 // FIXME: Once we're throwing exceptions for cross-origin access violations, we
188 // will always sanitize the target frame details, so we can safely combine
189 // 'crossDomainAccessErrorMessage' with this method after considering exactly
190 // which details may be exposed to JavaScript.
191 //
192 // http://crbug.com/17325
SanitizedCrossDomainAccessErrorMessage(const LocalDOMWindow * accessing_window,CrossDocumentAccessPolicy cross_document_access) const193 String DOMWindow::SanitizedCrossDomainAccessErrorMessage(
194     const LocalDOMWindow* accessing_window,
195     CrossDocumentAccessPolicy cross_document_access) const {
196   if (!accessing_window || !GetFrame())
197     return String();
198 
199   const KURL& accessing_window_url = accessing_window->Url();
200   if (accessing_window_url.IsNull())
201     return String();
202 
203   const SecurityOrigin* active_origin = accessing_window->GetSecurityOrigin();
204   String message;
205   if (cross_document_access == CrossDocumentAccessPolicy::kDisallowed) {
206     message = "Blocked a restricted frame with origin \"" +
207               active_origin->ToString() + "\" from accessing another frame.";
208   } else {
209     message = "Blocked a frame with origin \"" + active_origin->ToString() +
210               "\" from accessing a cross-origin frame.";
211   }
212 
213   // FIXME: Evaluate which details from 'crossDomainAccessErrorMessage' may
214   // safely be reported to JavaScript.
215 
216   return message;
217 }
218 
CrossDomainAccessErrorMessage(const LocalDOMWindow * accessing_window,CrossDocumentAccessPolicy cross_document_access) const219 String DOMWindow::CrossDomainAccessErrorMessage(
220     const LocalDOMWindow* accessing_window,
221     CrossDocumentAccessPolicy cross_document_access) const {
222   if (!accessing_window || !GetFrame())
223     return String();
224 
225   const KURL& accessing_window_url = accessing_window->Url();
226   if (accessing_window_url.IsNull())
227     return String();
228 
229   // FIXME: This message, and other console messages, have extra newlines.
230   // Should remove them.
231   const SecurityOrigin* active_origin = accessing_window->GetSecurityOrigin();
232   const SecurityOrigin* target_origin =
233       GetFrame()->GetSecurityContext()->GetSecurityOrigin();
234   auto* local_dom_window = DynamicTo<LocalDOMWindow>(this);
235   // It's possible for a remote frame to be same origin with respect to a
236   // local frame, but it must still be treated as a disallowed cross-domain
237   // access. See https://crbug.com/601629.
238   DCHECK(GetFrame()->IsRemoteFrame() ||
239          !active_origin->CanAccess(target_origin) ||
240          (local_dom_window &&
241           accessing_window->GetAgent() != local_dom_window->GetAgent()));
242 
243   String message = "Blocked a frame with origin \"" +
244                    active_origin->ToString() +
245                    "\" from accessing a frame with origin \"" +
246                    target_origin->ToString() + "\". ";
247 
248   // Sandbox errors: Use the origin of the frames' location, rather than their
249   // actual origin (since we know that at least one will be "null").
250   KURL active_url = accessing_window->Url();
251   // TODO(alexmos): RemoteFrames do not have a document, and their URLs
252   // aren't replicated.  For now, construct the URL using the replicated
253   // origin for RemoteFrames. If the target frame is remote and sandboxed,
254   // there isn't anything else to show other than "null" for its origin.
255   KURL target_url = local_dom_window
256                         ? local_dom_window->Url()
257                         : KURL(NullURL(), target_origin->ToString());
258   using SandboxFlags = network::mojom::blink::WebSandboxFlags;
259   if (GetFrame()->GetSecurityContext()->IsSandboxed(SandboxFlags::kOrigin) ||
260       accessing_window->IsSandboxed(SandboxFlags::kOrigin)) {
261     message = "Blocked a frame at \"" +
262               SecurityOrigin::Create(active_url)->ToString() +
263               "\" from accessing a frame at \"" +
264               SecurityOrigin::Create(target_url)->ToString() + "\". ";
265 
266     if (GetFrame()->GetSecurityContext()->IsSandboxed(SandboxFlags::kOrigin) &&
267         accessing_window->IsSandboxed(SandboxFlags::kOrigin)) {
268       return "Sandbox access violation: " + message +
269              " Both frames are sandboxed and lack the \"allow-same-origin\" "
270              "flag.";
271     }
272 
273     if (GetFrame()->GetSecurityContext()->IsSandboxed(SandboxFlags::kOrigin)) {
274       return "Sandbox access violation: " + message +
275              " The frame being accessed is sandboxed and lacks the "
276              "\"allow-same-origin\" flag.";
277     }
278 
279     return "Sandbox access violation: " + message +
280            " The frame requesting access is sandboxed and lacks the "
281            "\"allow-same-origin\" flag.";
282   }
283 
284   // Protocol errors: Use the URL's protocol rather than the origin's protocol
285   // so that we get a useful message for non-heirarchal URLs like 'data:'.
286   if (target_origin->Protocol() != active_origin->Protocol())
287     return message + " The frame requesting access has a protocol of \"" +
288            active_url.Protocol() +
289            "\", the frame being accessed has a protocol of \"" +
290            target_url.Protocol() + "\". Protocols must match.\n";
291 
292   // 'document.domain' errors.
293   if (target_origin->DomainWasSetInDOM() && active_origin->DomainWasSetInDOM())
294     return message +
295            "The frame requesting access set \"document.domain\" to \"" +
296            active_origin->Domain() +
297            "\", the frame being accessed set it to \"" +
298            target_origin->Domain() +
299            "\". Both must set \"document.domain\" to the same value to allow "
300            "access.";
301   if (active_origin->DomainWasSetInDOM())
302     return message +
303            "The frame requesting access set \"document.domain\" to \"" +
304            active_origin->Domain() +
305            "\", but the frame being accessed did not. Both must set "
306            "\"document.domain\" to the same value to allow access.";
307   if (target_origin->DomainWasSetInDOM())
308     return message + "The frame being accessed set \"document.domain\" to \"" +
309            target_origin->Domain() +
310            "\", but the frame requesting access did not. Both must set "
311            "\"document.domain\" to the same value to allow access.";
312   if (cross_document_access == CrossDocumentAccessPolicy::kDisallowed)
313     return message + "The document-access policy denied access.";
314 
315   // Default.
316   return message + "Protocols, domains, and ports must match.";
317 }
318 
close(v8::Isolate * isolate)319 void DOMWindow::close(v8::Isolate* isolate) {
320   LocalDOMWindow* incumbent_window = IncumbentDOMWindow(isolate);
321   Close(incumbent_window);
322 }
323 
Close(LocalDOMWindow * incumbent_window)324 void DOMWindow::Close(LocalDOMWindow* incumbent_window) {
325   DCHECK(incumbent_window);
326 
327   if (!GetFrame() || !GetFrame()->IsMainFrame())
328     return;
329 
330   Page* page = GetFrame()->GetPage();
331   if (!page)
332     return;
333 
334   if (page->InsidePortal())
335     return;
336 
337   Document* active_document = incumbent_window->document();
338   if (!(active_document && active_document->GetFrame() &&
339         active_document->GetFrame()->CanNavigate(*GetFrame()))) {
340     return;
341   }
342 
343   Settings* settings = GetFrame()->GetSettings();
344   bool allow_scripts_to_close_windows =
345       settings && settings->GetAllowScriptsToCloseWindows();
346 
347   if (!page->OpenedByDOM() && GetFrame()->Client()->BackForwardLength() > 1 &&
348       !allow_scripts_to_close_windows) {
349     active_document->domWindow()->GetFrameConsole()->AddMessage(
350         MakeGarbageCollected<ConsoleMessage>(
351             mojom::ConsoleMessageSource::kJavaScript,
352             mojom::ConsoleMessageLevel::kWarning,
353             "Scripts may close only the windows that were opened by them."));
354     return;
355   }
356 
357   if (!GetFrame()->ShouldClose())
358     return;
359 
360   ExecutionContext* execution_context = nullptr;
361   if (auto* local_dom_window = DynamicTo<LocalDOMWindow>(this)) {
362     execution_context = local_dom_window->GetExecutionContext();
363   }
364   probe::BreakableLocation(execution_context, "DOMWindow.close");
365 
366   page->CloseSoon();
367 
368   // So as to make window.closed return the expected result
369   // after window.close(), separately record the to-be-closed
370   // state of this window. Scripts may access window.closed
371   // before the deferred close operation has gone ahead.
372   window_is_closing_ = true;
373 }
374 
focus(v8::Isolate * isolate)375 void DOMWindow::focus(v8::Isolate* isolate) {
376   Frame* frame = GetFrame();
377   if (!frame)
378     return;
379 
380   Page* page = frame->GetPage();
381   if (!page)
382     return;
383 
384   // HTML standard doesn't require to check the incumbent realm, but Blink
385   // historically checks it for some reasons, maybe the same reason as |close|.
386   // (|close| checks whether the incumbent realm is eligible to close the window
387   // in order to prevent a (cross origin) window from abusing |close| to close
388   // pages randomly or with a malicious intent.)
389   // https://html.spec.whatwg.org/C/#dom-window-focus
390   // https://html.spec.whatwg.org/C/#focusing-steps
391   LocalDOMWindow* incumbent_window = IncumbentDOMWindow(isolate);
392 
393   // TODO(mustaq): Use of |allow_focus| and consuming the activation here seems
394   // suspicious (https://crbug.com/959815).
395   bool allow_focus = incumbent_window->IsWindowInteractionAllowed();
396   if (allow_focus) {
397     incumbent_window->ConsumeWindowInteraction();
398   } else {
399     DCHECK(IsMainThread());
400     allow_focus = opener() && opener() != this && incumbent_window == opener();
401   }
402 
403   // If we're a top level window, bring the window to the front.
404   if (frame->IsMainFrame() && allow_focus) {
405     frame->FocusPage(incumbent_window->GetFrame());
406   } else if (auto* local_frame = DynamicTo<LocalFrame>(frame)) {
407     // We are depending on user activation twice since IsFocusAllowed() will
408     // check for activation. This should be addressed in
409     // https://crbug.com/959815.
410     if (local_frame->GetDocument() &&
411         !local_frame->GetDocument()->IsFocusAllowed()) {
412       return;
413     }
414   }
415 
416   page->GetFocusController().FocusDocumentView(GetFrame(),
417                                                true /* notifyEmbedder */);
418 }
419 
GetInputDeviceCapabilities()420 InputDeviceCapabilitiesConstants* DOMWindow::GetInputDeviceCapabilities() {
421   if (!input_capabilities_) {
422     input_capabilities_ =
423         MakeGarbageCollected<InputDeviceCapabilitiesConstants>();
424   }
425   return input_capabilities_;
426 }
427 
PostMessageForTesting(scoped_refptr<SerializedScriptValue> message,const MessagePortArray & ports,const String & target_origin,LocalDOMWindow * source,ExceptionState & exception_state)428 void DOMWindow::PostMessageForTesting(
429     scoped_refptr<SerializedScriptValue> message,
430     const MessagePortArray& ports,
431     const String& target_origin,
432     LocalDOMWindow* source,
433     ExceptionState& exception_state) {
434   WindowPostMessageOptions* options = WindowPostMessageOptions::Create();
435   options->setTargetOrigin(target_origin);
436   DoPostMessage(std::move(message), ports, options, source, exception_state);
437 }
438 
InstallCoopAccessMonitor(network::mojom::blink::CoopAccessReportType report_type,LocalFrame * accessing_frame,mojo::PendingRemote<network::mojom::blink::CrossOriginOpenerPolicyReporter> pending_reporter,bool endpoint_defined,const WTF::String & reported_window_url)439 void DOMWindow::InstallCoopAccessMonitor(
440     network::mojom::blink::CoopAccessReportType report_type,
441     LocalFrame* accessing_frame,
442     mojo::PendingRemote<network::mojom::blink::CrossOriginOpenerPolicyReporter>
443         pending_reporter,
444     bool endpoint_defined,
445     const WTF::String& reported_window_url) {
446   CoopAccessMonitor monitor;
447 
448   DCHECK(accessing_frame->IsMainFrame());
449   monitor.report_type = report_type;
450   monitor.accessing_main_frame = accessing_frame->GetFrameToken();
451   monitor.endpoint_defined = endpoint_defined;
452   monitor.reported_window_url = std::move(reported_window_url);
453 
454   monitor.reporter.Bind(std::move(pending_reporter));
455   // CoopAccessMonitor are cleared when their reporter are gone. This avoids
456   // accumulation. However it would have been interesting continuing reporting
457   // accesses past this point, at least for the ReportingObserver and Devtool.
458   // TODO(arthursonzogni): Consider observing |accessing_main_frame| deletion
459   // instead.
460   monitor.reporter.set_disconnect_handler(
461       WTF::Bind(&DOMWindow::DisconnectCoopAccessMonitor,
462                 WrapWeakPersistent(this), monitor.accessing_main_frame));
463 
464   // As long as RenderDocument isn't shipped, it can exist a CoopAccessMonitor
465   // for the same |accessing_main_frame|, because it might now host a different
466   // Document. Same is true for |this| DOMWindow, it might refer to a window
467   // hosting a different document.
468   // The new documents will still be part of a different virtual browsing
469   // context group, however the new COOPAccessMonitor might now contain updated
470   // URLs.
471   //
472   // There are up to 2 CoopAccessMonitor for the same access, because it can be
473   // reported to the accessing and the accessed window at the same time.
474   for (CoopAccessMonitor& old : coop_access_monitor_) {
475     if (old.accessing_main_frame == monitor.accessing_main_frame &&
476         network::IsAccessFromCoopPage(old.report_type) ==
477             network::IsAccessFromCoopPage(monitor.report_type)) {
478       old = std::move(monitor);
479       return;
480     }
481   }
482   coop_access_monitor_.push_back(std::move(monitor));
483   // Any attempts to access |this| window from |accessing_main_frame| will now
484   // trigger reports (network, ReportingObserver, Devtool).
485 }
486 
487 // Check if the accessing context would be able to access this window if COOP
488 // was enforced. If this isn't a report is sent.
ReportCoopAccess(const char * property_name)489 void DOMWindow::ReportCoopAccess(const char* property_name) {
490   if (coop_access_monitor_.IsEmpty())  // Fast early return. Very likely true.
491     return;
492 
493   v8::Isolate* isolate = window_proxy_manager_->GetIsolate();
494   LocalDOMWindow* accessing_window = IncumbentDOMWindow(isolate);
495   LocalFrame* accessing_frame = accessing_window->GetFrame();
496 
497   // A frame might be destroyed, but its context can still be able to execute
498   // some code. Those accesses are ignored. See https://crbug.com/1108256.
499   if (!accessing_frame)
500     return;
501 
502   // Iframes are allowed to trigger reports, only when they are same-origin with
503   // their top-level document.
504   if (accessing_frame->IsCrossOriginToParentFrame())
505     return;
506 
507   LocalFrame& accessing_main_frame =
508       To<LocalFrame>(accessing_frame->Tree().Top());
509   const base::UnguessableToken& accessing_main_frame_token =
510       accessing_main_frame.GetFrameToken();
511 
512   auto* it = coop_access_monitor_.begin();
513   while (it != coop_access_monitor_.end()) {
514     if (it->accessing_main_frame != accessing_main_frame_token) {
515       ++it;
516       continue;
517     }
518 
519     // TODO(arthursonzogni): Send the blocked-window-url.
520 
521     auto location = SourceLocation::Capture(
522         ExecutionContext::From(isolate->GetCurrentContext()));
523     // TODO(arthursonzogni): Once implemented, use the SourceLocation typemap
524     // https://chromium-review.googlesource.com/c/chromium/src/+/2041657
525     auto source_location = network::mojom::blink::SourceLocation::New(
526         location->Url() ? location->Url() : "", location->LineNumber(),
527         location->ColumnNumber());
528 
529     // TODO(https://crbug.com/1124251): Notify Devtool about the access attempt.
530 
531     // If the reporting document hasn't specified any network report
532     // endpoint(s), then it is likely not interested in receiving
533     // ReportingObserver's reports.
534     //
535     // TODO(arthursonzogni): Reconsider this decision later, developers might be
536     // interested.
537     if (it->endpoint_defined) {
538       it->reporter->QueueAccessReport(it->report_type, property_name,
539                                       std::move(source_location),
540                                       std::move(it->reported_window_url));
541       // Send a coop-access-violation report.
542       if (network::IsAccessFromCoopPage(it->report_type)) {
543         ReportingContext::From(accessing_main_frame.DomWindow())
544             ->QueueReport(MakeGarbageCollected<Report>(
545                 ReportType::kCoopAccessViolation,
546                 accessing_main_frame.GetDocument()->Url().GetString(),
547                 MakeGarbageCollected<CoopAccessViolationReportBody>(
548                     std::move(location), it->report_type, String(property_name),
549                     it->reported_window_url)));
550       }
551     }
552 
553     // CoopAccessMonitor are used once and destroyed. This avoids sending
554     // multiple reports for the same access.
555     it = coop_access_monitor_.erase(it);
556   }
557 }
558 
DoPostMessage(scoped_refptr<SerializedScriptValue> message,const MessagePortArray & ports,const WindowPostMessageOptions * options,LocalDOMWindow * source,ExceptionState & exception_state)559 void DOMWindow::DoPostMessage(scoped_refptr<SerializedScriptValue> message,
560                               const MessagePortArray& ports,
561                               const WindowPostMessageOptions* options,
562                               LocalDOMWindow* source,
563                               ExceptionState& exception_state) {
564   TRACE_EVENT0("blink", "DOMWindow::DoPostMessage");
565   auto* source_frame = source->GetFrame();
566   bool unload_event_in_progress =
567       source_frame && source_frame->GetDocument() &&
568       source_frame->GetDocument()->UnloadEventInProgress();
569   if (!unload_event_in_progress && source_frame && source_frame->GetPage() &&
570       source_frame->GetPage()->DispatchedPagehideAndStillHidden()) {
571     // The postMessage call is done after the pagehide event got dispatched
572     // and the page is still hidden, which is not normally possible (this
573     // might happen if we're doing a same-site cross-RenderFrame navigation
574     // where we dispatch pagehide during the new RenderFrame's commit but
575     // won't unload/freeze the page after the new RenderFrame finished
576     // committing). We should track this case to measure how often this is
577     // happening, except for when the unload event is currently in progress,
578     // which means the page is not actually stored in the back-forward cache and
579     // this behavior is ok.
580     UMA_HISTOGRAM_ENUMERATION("BackForwardCache.SameSite.ActionAfterPagehide2",
581                               ActionAfterPagehide::kSentPostMessage);
582   }
583   if (!IsCurrentlyDisplayedInFrame())
584     return;
585 
586   // Compute the target origin.  We need to do this synchronously in order
587   // to generate the SyntaxError exception correctly.
588   scoped_refptr<const SecurityOrigin> target =
589       PostMessageHelper::GetTargetOrigin(options, *source, exception_state);
590   if (exception_state.HadException())
591     return;
592   if (!target) {
593     UseCounter::Count(source, WebFeature::kUnspecifiedTargetOriginPostMessage);
594   }
595 
596   auto channels = MessagePort::DisentanglePorts(GetExecutionContext(), ports,
597                                                 exception_state);
598   if (exception_state.HadException())
599     return;
600 
601   const SecurityOrigin* target_security_origin =
602       GetFrame()->GetSecurityContext()->GetSecurityOrigin();
603   const SecurityOrigin* source_security_origin = source->GetSecurityOrigin();
604   auto* local_dom_window = DynamicTo<LocalDOMWindow>(this);
605   KURL target_url = local_dom_window
606                         ? local_dom_window->Url()
607                         : KURL(NullURL(), target_security_origin->ToString());
608   if (MixedContentChecker::IsMixedContent(source_security_origin, target_url)) {
609     UseCounter::Count(source, WebFeature::kPostMessageFromSecureToInsecure);
610   } else if (MixedContentChecker::IsMixedContent(target_security_origin,
611                                                  source->Url())) {
612     UseCounter::Count(source, WebFeature::kPostMessageFromInsecureToSecure);
613     if (MixedContentChecker::IsMixedContent(
614             GetFrame()->Tree().Top().GetSecurityContext()->GetSecurityOrigin(),
615             source->Url())) {
616       UseCounter::Count(source,
617                         WebFeature::kPostMessageFromInsecureToSecureToplevel);
618     }
619   }
620 
621   if (source->GetFrame() &&
622       source->GetFrame()->Tree().Top() != GetFrame()->Tree().Top()) {
623     if ((!target_security_origin->RegistrableDomain() &&
624          target_security_origin->Host() == source_security_origin->Host()) ||
625         (target_security_origin->RegistrableDomain() &&
626          target_security_origin->RegistrableDomain() ==
627              source_security_origin->RegistrableDomain())) {
628       if (target_security_origin->Protocol() ==
629           source_security_origin->Protocol()) {
630         UseCounter::Count(source, WebFeature::kSchemefulSameSitePostMessage);
631       } else {
632         UseCounter::Count(source, WebFeature::kSchemelesslySameSitePostMessage);
633         if (MixedContentChecker::IsMixedContent(source_security_origin,
634                                                 target_url)) {
635           UseCounter::Count(
636               source,
637               WebFeature::kSchemelesslySameSitePostMessageSecureToInsecure);
638         } else if (MixedContentChecker::IsMixedContent(target_security_origin,
639                                                        source->Url())) {
640           UseCounter::Count(
641               source,
642               WebFeature::kSchemelesslySameSitePostMessageInsecureToSecure);
643         }
644       }
645     } else {
646       UseCounter::Count(source, WebFeature::kCrossSitePostMessage);
647     }
648   }
649   if (!source->GetContentSecurityPolicy()->AllowConnectToSource(
650           target_url, target_url, RedirectStatus::kNoRedirect,
651           ReportingDisposition::kSuppressReporting)) {
652     UseCounter::Count(
653         source, WebFeature::kPostMessageOutgoingWouldBeBlockedByConnectSrc);
654   }
655   UserActivation* user_activation = nullptr;
656   if (options->includeUserActivation())
657     user_activation = UserActivation::CreateSnapshot(source);
658 
659   MessageEvent* event =
660       MessageEvent::Create(std::move(channels), std::move(message),
661                            source->GetSecurityOrigin()->ToString(), String(),
662                            source, user_activation);
663 
664   SchedulePostMessage(event, std::move(target), source);
665 }
666 
Trace(Visitor * visitor) const667 void DOMWindow::Trace(Visitor* visitor) const {
668   visitor->Trace(frame_);
669   visitor->Trace(window_proxy_manager_);
670   visitor->Trace(input_capabilities_);
671   visitor->Trace(location_);
672   EventTargetWithInlineData::Trace(visitor);
673 }
674 
DisconnectCoopAccessMonitor(base::UnguessableToken accessing_main_frame)675 void DOMWindow::DisconnectCoopAccessMonitor(
676     base::UnguessableToken accessing_main_frame) {
677   auto* it = coop_access_monitor_.begin();
678   while (it != coop_access_monitor_.end()) {
679     if (it->accessing_main_frame == accessing_main_frame)
680       it = coop_access_monitor_.erase(it);
681     else
682       ++it;
683   }
684 }
685 
686 }  // namespace blink
687