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