blob: da23a7fb253a8c2f229504d9a6cab32406ce08da [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/frame/DOMWindow.h"
#include <memory>
#include "bindings/core/v8/WindowProxyManager.h"
#include "core/dom/Document.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/SecurityContext.h"
#include "core/events/MessageEvent.h"
#include "core/frame/Frame.h"
#include "core/frame/FrameClient.h"
#include "core/frame/FrameConsole.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/Location.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/input/InputDeviceCapabilities.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/loader/MixedContentChecker.h"
#include "core/page/ChromeClient.h"
#include "core/page/FocusController.h"
#include "core/page/Page.h"
#include "core/probe/CoreProbes.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/weborigin/Suborigin.h"
namespace blink {
DOMWindow::DOMWindow(Frame& frame)
: frame_(frame),
window_proxy_manager_(frame.GetWindowProxyManager()),
window_is_closing_(false) {}
DOMWindow::~DOMWindow() {
// The frame must be disconnected before finalization.
DCHECK(!frame_);
}
v8::Local<v8::Object> DOMWindow::Wrap(v8::Isolate* isolate,
v8::Local<v8::Object> creation_context) {
NOTREACHED();
return v8::Local<v8::Object>();
}
v8::Local<v8::Object> DOMWindow::AssociateWithWrapper(
v8::Isolate*,
const WrapperTypeInfo*,
v8::Local<v8::Object> wrapper) {
NOTREACHED();
return v8::Local<v8::Object>();
}
const AtomicString& DOMWindow::InterfaceName() const {
return EventTargetNames::DOMWindow;
}
const DOMWindow* DOMWindow::ToDOMWindow() const {
return this;
}
Location* DOMWindow::location() const {
if (!location_)
location_ = Location::Create(const_cast<DOMWindow*>(this));
return location_.Get();
}
bool DOMWindow::closed() const {
return window_is_closing_ || !GetFrame() || !GetFrame()->GetPage();
}
unsigned DOMWindow::length() const {
return GetFrame() ? GetFrame()->Tree().ScopedChildCount() : 0;
}
DOMWindow* DOMWindow::self() const {
if (!GetFrame())
return nullptr;
return GetFrame()->DomWindow();
}
DOMWindow* DOMWindow::opener() const {
// FIXME: Use FrameTree to get opener as well, to simplify logic here.
if (!GetFrame() || !GetFrame()->Client())
return nullptr;
Frame* opener = GetFrame()->Client()->Opener();
return opener ? opener->DomWindow() : nullptr;
}
DOMWindow* DOMWindow::parent() const {
if (!GetFrame())
return nullptr;
Frame* parent = GetFrame()->Tree().Parent();
return parent ? parent->DomWindow() : GetFrame()->DomWindow();
}
DOMWindow* DOMWindow::top() const {
if (!GetFrame())
return nullptr;
return GetFrame()->Tree().Top().DomWindow();
}
DOMWindow* DOMWindow::AnonymousIndexedGetter(uint32_t index) const {
if (!GetFrame())
return nullptr;
Frame* child = GetFrame()->Tree().ScopedChild(index);
return child ? child->DomWindow() : nullptr;
}
bool DOMWindow::IsCurrentlyDisplayedInFrame() const {
if (GetFrame())
SECURITY_CHECK(GetFrame()->DomWindow() == this);
return GetFrame() && GetFrame()->GetPage();
}
bool DOMWindow::IsInsecureScriptAccess(LocalDOMWindow& calling_window,
const KURL& url) {
if (!url.ProtocolIsJavaScript())
return false;
// If this DOMWindow isn't currently active in the Frame, then there's no
// way we should allow the access.
if (IsCurrentlyDisplayedInFrame()) {
// FIXME: Is there some way to eliminate the need for a separate
// "callingWindow == this" check?
if (&calling_window == this)
return false;
// FIXME: The name canAccess seems to be a roundabout way to ask "can
// execute script". Can we name the SecurityOrigin function better to make
// this more clear?
if (calling_window.document()->GetSecurityOrigin()->CanAccess(
GetFrame()->GetSecurityContext()->GetSecurityOrigin())) {
return false;
}
}
calling_window.PrintErrorMessage(
CrossDomainAccessErrorMessage(&calling_window));
return true;
}
void DOMWindow::postMessage(scoped_refptr<SerializedScriptValue> message,
const MessagePortArray& ports,
const String& target_origin,
LocalDOMWindow* source,
ExceptionState& exception_state) {
if (!IsCurrentlyDisplayedInFrame())
return;
Document* source_document = source->document();
// Compute the target origin. We need to do this synchronously in order
// to generate the SyntaxError exception correctly.
scoped_refptr<const SecurityOrigin> target;
if (target_origin == "/") {
if (!source_document)
return;
target = source_document->GetSecurityOrigin();
} else if (target_origin != "*") {
target = SecurityOrigin::CreateFromString(target_origin);
// It doesn't make sense target a postMessage at a unique origin
// because there's no way to represent a unique origin in a string.
if (target->IsUnique()) {
exception_state.ThrowDOMException(
kSyntaxError, "Invalid target origin '" + target_origin +
"' in a call to 'postMessage'.");
return;
}
}
auto channels = MessagePort::DisentanglePorts(GetExecutionContext(), ports,
exception_state);
if (exception_state.HadException())
return;
// Capture the source of the message. We need to do this synchronously
// in order to capture the source of the message correctly.
if (!source_document)
return;
const SecurityOrigin* security_origin = source_document->GetSecurityOrigin();
bool has_suborigin = source_document->GetSecurityOrigin()->HasSuborigin();
Suborigin::SuboriginPolicyOptions unsafe_send_opt =
Suborigin::SuboriginPolicyOptions::kUnsafePostMessageSend;
String source_origin =
(has_suborigin &&
security_origin->GetSuborigin()->PolicyContains(unsafe_send_opt))
? security_origin->ToPhysicalOriginString()
: security_origin->ToString();
String source_suborigin =
has_suborigin ? security_origin->GetSuborigin()->GetName() : String();
KURL target_url = IsLocalDOMWindow()
? blink::ToLocalDOMWindow(this)->document()->Url()
: KURL(NullURL(), GetFrame()
->GetSecurityContext()
->GetSecurityOrigin()
->ToString());
if (MixedContentChecker::IsMixedContent(source_document->GetSecurityOrigin(),
target_url)) {
UseCounter::Count(source->GetFrame(),
WebFeature::kPostMessageFromSecureToInsecure);
} else if (MixedContentChecker::IsMixedContent(
GetFrame()->GetSecurityContext()->GetSecurityOrigin(),
source_document->Url())) {
UseCounter::Count(source->GetFrame(),
WebFeature::kPostMessageFromInsecureToSecure);
if (MixedContentChecker::IsMixedContent(
GetFrame()->Tree().Top().GetSecurityContext()->GetSecurityOrigin(),
source_document->Url())) {
UseCounter::Count(source->GetFrame(),
WebFeature::kPostMessageFromInsecureToSecureToplevel);
}
}
if (!source_document->GetContentSecurityPolicy()->AllowConnectToSource(
target_url, RedirectStatus::kNoRedirect,
SecurityViolationReportingPolicy::kSuppressReporting)) {
UseCounter::Count(
source->GetFrame(),
WebFeature::kPostMessageOutgoingWouldBeBlockedByConnectSrc);
}
MessageEvent* event =
MessageEvent::Create(std::move(channels), std::move(message),
source_origin, String(), source, source_suborigin);
SchedulePostMessage(event, std::move(target), source_document);
}
// FIXME: Once we're throwing exceptions for cross-origin access violations, we
// will always sanitize the target frame details, so we can safely combine
// 'crossDomainAccessErrorMessage' with this method after considering exactly
// which details may be exposed to JavaScript.
//
// http://crbug.com/17325
String DOMWindow::SanitizedCrossDomainAccessErrorMessage(
const LocalDOMWindow* calling_window) const {
if (!calling_window || !calling_window->document() || !GetFrame())
return String();
const KURL& calling_window_url = calling_window->document()->Url();
if (calling_window_url.IsNull())
return String();
const SecurityOrigin* active_origin =
calling_window->document()->GetSecurityOrigin();
String message = "Blocked a frame with origin \"" +
active_origin->ToString() +
"\" from accessing a cross-origin frame.";
// FIXME: Evaluate which details from 'crossDomainAccessErrorMessage' may
// safely be reported to JavaScript.
return message;
}
String DOMWindow::CrossDomainAccessErrorMessage(
const LocalDOMWindow* calling_window) const {
if (!calling_window || !calling_window->document() || !GetFrame())
return String();
const KURL& calling_window_url = calling_window->document()->Url();
if (calling_window_url.IsNull())
return String();
// FIXME: This message, and other console messages, have extra newlines.
// Should remove them.
const SecurityOrigin* active_origin =
calling_window->document()->GetSecurityOrigin();
const SecurityOrigin* target_origin =
GetFrame()->GetSecurityContext()->GetSecurityOrigin();
// It's possible for a remote frame to be same origin with respect to a
// local frame, but it must still be treated as a disallowed cross-domain
// access. See https://crbug.com/601629.
DCHECK(GetFrame()->IsRemoteFrame() ||
!active_origin->CanAccess(target_origin));
String message = "Blocked a frame with origin \"" +
active_origin->ToString() +
"\" from accessing a frame with origin \"" +
target_origin->ToString() + "\". ";
// Sandbox errors: Use the origin of the frames' location, rather than their
// actual origin (since we know that at least one will be "null").
KURL active_url = calling_window->document()->Url();
// TODO(alexmos): RemoteFrames do not have a document, and their URLs
// aren't replicated. For now, construct the URL using the replicated
// origin for RemoteFrames. If the target frame is remote and sandboxed,
// there isn't anything else to show other than "null" for its origin.
KURL target_url = IsLocalDOMWindow()
? blink::ToLocalDOMWindow(this)->document()->Url()
: KURL(NullURL(), target_origin->ToString());
if (GetFrame()->GetSecurityContext()->IsSandboxed(kSandboxOrigin) ||
calling_window->document()->IsSandboxed(kSandboxOrigin)) {
message = "Blocked a frame at \"" +
SecurityOrigin::Create(active_url)->ToString() +
"\" from accessing a frame at \"" +
SecurityOrigin::Create(target_url)->ToString() + "\". ";
if (GetFrame()->GetSecurityContext()->IsSandboxed(kSandboxOrigin) &&
calling_window->document()->IsSandboxed(kSandboxOrigin))
return "Sandbox access violation: " + message +
" Both frames are sandboxed and lack the \"allow-same-origin\" "
"flag.";
if (GetFrame()->GetSecurityContext()->IsSandboxed(kSandboxOrigin))
return "Sandbox access violation: " + message +
" The frame being accessed is sandboxed and lacks the "
"\"allow-same-origin\" flag.";
return "Sandbox access violation: " + message +
" The frame requesting access is sandboxed and lacks the "
"\"allow-same-origin\" flag.";
}
// Protocol errors: Use the URL's protocol rather than the origin's protocol
// so that we get a useful message for non-heirarchal URLs like 'data:'.
if (target_origin->Protocol() != active_origin->Protocol())
return message + " The frame requesting access has a protocol of \"" +
active_url.Protocol() +
"\", the frame being accessed has a protocol of \"" +
target_url.Protocol() + "\". Protocols must match.\n";
// 'document.domain' errors.
if (target_origin->DomainWasSetInDOM() && active_origin->DomainWasSetInDOM())
return message +
"The frame requesting access set \"document.domain\" to \"" +
active_origin->Domain() +
"\", the frame being accessed set it to \"" +
target_origin->Domain() +
"\". Both must set \"document.domain\" to the same value to allow "
"access.";
if (active_origin->DomainWasSetInDOM())
return message +
"The frame requesting access set \"document.domain\" to \"" +
active_origin->Domain() +
"\", but the frame being accessed did not. Both must set "
"\"document.domain\" to the same value to allow access.";
if (target_origin->DomainWasSetInDOM())
return message + "The frame being accessed set \"document.domain\" to \"" +
target_origin->Domain() +
"\", but the frame requesting access did not. Both must set "
"\"document.domain\" to the same value to allow access.";
// Default.
return message + "Protocols, domains, and ports must match.";
}
void DOMWindow::close(LocalDOMWindow* incumbent_window) {
if (!GetFrame() || !GetFrame()->IsMainFrame())
return;
Page* page = GetFrame()->GetPage();
if (!page)
return;
Document* active_document = nullptr;
if (incumbent_window) {
DCHECK(IsMainThread());
active_document = incumbent_window->document();
if (!active_document)
return;
if (!active_document->GetFrame() ||
!active_document->GetFrame()->CanNavigate(*GetFrame()))
return;
}
Settings* settings = GetFrame()->GetSettings();
bool allow_scripts_to_close_windows =
settings && settings->GetAllowScriptsToCloseWindows();
if (!page->OpenedByDOM() && GetFrame()->Client()->BackForwardLength() > 1 &&
!allow_scripts_to_close_windows) {
if (active_document) {
active_document->domWindow()->GetFrameConsole()->AddMessage(
ConsoleMessage::Create(
kJSMessageSource, kWarningMessageLevel,
"Scripts may close only the windows that were opened by it."));
}
return;
}
if (!GetFrame()->ShouldClose())
return;
ExecutionContext* execution_context = nullptr;
if (IsLocalDOMWindow()) {
execution_context = blink::ToLocalDOMWindow(this)->GetExecutionContext();
}
probe::breakableLocation(execution_context, "DOMWindow.close");
page->CloseSoon();
// So as to make window.closed return the expected result
// after window.close(), separately record the to-be-closed
// state of this window. Scripts may access window.closed
// before the deferred close operation has gone ahead.
window_is_closing_ = true;
}
void DOMWindow::focus(LocalDOMWindow* incumbent_window) {
if (!GetFrame())
return;
Page* page = GetFrame()->GetPage();
if (!page)
return;
DCHECK(incumbent_window);
ExecutionContext* incumbent_execution_context =
incumbent_window->GetExecutionContext();
bool allow_focus = incumbent_execution_context->IsWindowInteractionAllowed();
if (allow_focus) {
incumbent_execution_context->ConsumeWindowInteraction();
} else {
DCHECK(IsMainThread());
allow_focus =
opener() && (opener() != this) &&
(ToDocument(incumbent_execution_context)->domWindow() == opener());
}
// If we're a top level window, bring the window to the front.
if (GetFrame()->IsMainFrame() && allow_focus)
page->GetChromeClient().Focus(incumbent_window->GetFrame());
page->GetFocusController().FocusDocumentView(GetFrame(),
true /* notifyEmbedder */);
}
InputDeviceCapabilitiesConstants* DOMWindow::GetInputDeviceCapabilities() {
if (!input_capabilities_)
input_capabilities_ = new InputDeviceCapabilitiesConstants;
return input_capabilities_;
}
void DOMWindow::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_);
visitor->Trace(window_proxy_manager_);
visitor->Trace(input_capabilities_);
visitor->Trace(location_);
EventTargetWithInlineData::Trace(visitor);
}
void DOMWindow::TraceWrappers(const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(location_);
EventTargetWithInlineData::TraceWrappers(visitor);
}
} // namespace blink