blob: 731c5afe71af3d6a3c5f6f8e9eb3bf3819359039 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/core/html/portal/portal_contents.h"
#include "base/compiler_specific.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_id_helper.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom-blink.h"
#include "third_party/blink/public/mojom/portal/portal.mojom-blink-forward.h"
#include "third_party/blink/renderer/core/dom/increment_load_event_delay_count.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/html/portal/document_portals.h"
#include "third_party/blink/renderer/core/html/portal/html_portal_element.h"
#include "third_party/blink/renderer/core/html/portal/portal_activation_delegate.h"
#include "third_party/blink/renderer/core/html/portal/portal_post_message_helper.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/document_load_timing.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/messaging/blink_transferable_message.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
namespace blink {
PortalContents::PortalContents(
HTMLPortalElement& portal_element,
const PortalToken& portal_token,
mojo::PendingAssociatedRemote<mojom::blink::Portal> remote_portal,
mojo::PendingAssociatedReceiver<mojom::blink::PortalClient>
portal_client_receiver)
: document_(portal_element.GetDocument()),
portal_element_(&portal_element),
portal_token_(portal_token),
remote_portal_(std::move(remote_portal)),
portal_client_receiver_(this, std::move(portal_client_receiver)) {
remote_portal_.set_disconnect_handler(
WTF::Bind(&PortalContents::DisconnectHandler, WrapWeakPersistent(this)));
DocumentPortals::From(GetDocument()).RegisterPortalContents(this);
}
PortalContents::~PortalContents() = default;
RemoteFrame* PortalContents::GetFrame() const {
if (portal_element_)
return To<RemoteFrame>(portal_element_->ContentFrame());
return nullptr;
}
void PortalContents::Activate(BlinkTransferableMessage data,
PortalActivationDelegate* delegate) {
DCHECK(!IsActivating());
DCHECK(portal_element_);
// Mark this contents as having activation in progress.
DocumentPortals& document_portals = DocumentPortals::From(GetDocument());
document_portals.SetActivatingPortalContents(this);
activation_delegate_ = delegate;
uint64_t trace_id = base::trace_event::GetNextGlobalTraceId();
TRACE_EVENT_WITH_FLOW0("navigation", "PortalContents::Activate",
TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT);
// Request activation from the browser process.
// This object (and thus the Mojo connection it owns) remains alive while the
// renderer awaits the response.
remote_portal_->Activate(
std::move(data), base::TimeTicks::Now(), trace_id,
WTF::Bind(&PortalContents::OnActivateResponse, WrapPersistent(this)));
// Dissociate from the element. The element is expected to do the same.
portal_element_ = nullptr;
}
void PortalContents::OnActivateResponse(
mojom::blink::PortalActivateResult result) {
auto reject = [&](const char* message) {
if (GetDocument().GetExecutionContext())
activation_delegate_->ActivationDidFail(message);
};
bool should_destroy_contents = false;
switch (result) {
case mojom::blink::PortalActivateResult::kPredecessorWasAdopted:
if (auto* page = GetDocument().GetPage())
page->SetInsidePortal(true);
FALLTHROUGH;
case mojom::blink::PortalActivateResult::kPredecessorWillUnload:
activation_delegate_->ActivationDidSucceed();
should_destroy_contents = true;
break;
case mojom::blink::PortalActivateResult::
kRejectedDueToPredecessorNavigation:
reject("A top-level navigation is in progress.");
break;
case mojom::blink::PortalActivateResult::kRejectedDueToPortalNotReady:
reject("The portal was not yet ready or was blocked.");
break;
case mojom::blink::PortalActivateResult::kRejectedDueToErrorInPortal:
reject("The portal is in an error state.");
break;
case mojom::blink::PortalActivateResult::kDisconnected:
// Only called when |remote_portal_| is disconnected. This usually happens
// when the browser/test runner is being shut down.
activation_delegate_->ActivationWasAbandoned();
break;
case mojom::blink::PortalActivateResult::kAbortedDueToBug:
// This should never happen. Ignore this and wait for the frame to be
// discarded by the browser, if it hasn't already.
activation_delegate_->ActivationWasAbandoned();
return;
}
DocumentPortals& document_portals = DocumentPortals::From(GetDocument());
DCHECK_EQ(document_portals.GetActivatingPortalContents(), this);
document_portals.ClearActivatingPortalContents();
activation_delegate_ = nullptr;
if (should_destroy_contents)
Destroy();
}
void PortalContents::PostMessageToGuest(BlinkTransferableMessage message) {
remote_portal_->PostMessageToGuest(std::move(message));
}
void PortalContents::Navigate(
const KURL& url,
network::mojom::ReferrerPolicy referrer_policy_to_use) {
if (url.IsEmpty())
return;
if (!url.ProtocolIsInHTTPFamily()) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kRendering,
mojom::ConsoleMessageLevel::kWarning,
"Portals only allow navigation to protocols in the HTTP family."));
return;
}
ExecutionContext* context = GetDocument().GetExecutionContext();
if (referrer_policy_to_use == network::mojom::ReferrerPolicy::kDefault)
referrer_policy_to_use = context->GetReferrerPolicy();
Referrer referrer = SecurityPolicy::GenerateReferrer(
referrer_policy_to_use, url, context->OutgoingReferrer());
auto mojo_referrer = mojom::blink::Referrer::New(
KURL(NullURL(), referrer.referrer), referrer.referrer_policy);
// There is a brief window of time between when Navigate is called and its
// callback is run, during which the Portal's content frame is not marked as
// loading. We use IncrementLoadEventDelayCount to block its document's load
// event in this time window. Once it goes out of scope,
// IncrementLoadEventDelayCount will call Document::CheckCompleted and fire
// load events if necessary.
std::unique_ptr<IncrementLoadEventDelayCount>
increment_load_event_delay_count =
std::make_unique<IncrementLoadEventDelayCount>(GetDocument());
remote_portal_->Navigate(
url, std::move(mojo_referrer),
WTF::Bind([](std::unique_ptr<IncrementLoadEventDelayCount>
increment_load_event_delay_count) {},
std::move(increment_load_event_delay_count)));
}
void PortalContents::Destroy() {
DCHECK(!IsActivating());
if (portal_element_) {
portal_element_->PortalContentsWillBeDestroyed(this);
portal_element_ = nullptr;
}
portal_token_ = base::nullopt;
remote_portal_.reset();
portal_client_receiver_.reset();
DocumentPortals::From(GetDocument()).DeregisterPortalContents(this);
}
void PortalContents::DisconnectHandler() {
if (IsActivating())
OnActivateResponse(mojom::blink::PortalActivateResult::kDisconnected);
Destroy();
}
void PortalContents::ForwardMessageFromGuest(
BlinkTransferableMessage message,
const scoped_refptr<const SecurityOrigin>& source_origin) {
if (!IsValid() || !portal_element_)
return;
PortalPostMessageHelper::CreateAndDispatchMessageEvent(
portal_element_, std::move(message), source_origin);
}
void PortalContents::DispatchLoadEvent() {
if (!IsValid() || !portal_element_)
return;
portal_element_->DispatchLoad();
GetDocument().CheckCompleted();
}
void PortalContents::Trace(Visitor* visitor) const {
visitor->Trace(document_);
visitor->Trace(portal_element_);
visitor->Trace(activation_delegate_);
}
} // namespace blink