blob: 7a080c761068ce989c4df5b48c60513452502598 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/events/current_input_event.h"
#include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.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/remote_frame.h"
#include "third_party/blink/renderer/core/frame/remote_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/lazy_load_frame_observer.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h"
#include "third_party/blink/renderer/core/paint/paint_layer.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/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/network/network_state_notifier.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
namespace {
using PluginSet = HeapHashSet<Member<WebPluginContainerImpl>>;
PluginSet& PluginsPendingDispose() {
DEFINE_STATIC_LOCAL(Persistent<PluginSet>, set,
(MakeGarbageCollected<PluginSet>()));
return *set;
}
bool DoesParentAllowLazyLoadingChildren(Document& document) {
LocalFrame* containing_frame = document.GetFrame();
if (!containing_frame)
return true;
// If the embedding document has no owner, then by default allow lazy loading
// children.
FrameOwner* containing_frame_owner = containing_frame->Owner();
if (!containing_frame_owner)
return true;
return containing_frame_owner->ShouldLazyLoadChildren();
}
bool IsFrameLazyLoadable(const Document& document,
const KURL& url,
bool is_loading_attr_lazy,
bool should_lazy_load_children) {
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled() &&
!RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) {
return false;
}
// Only http:// or https:// URLs are eligible for lazy loading, excluding
// URLs like invalid or empty URLs, "about:blank", local file URLs, etc.
// that it doesn't make sense to lazily load.
if (!url.ProtocolIsInHTTPFamily())
return false;
if (is_loading_attr_lazy)
return true;
if (!should_lazy_load_children ||
// Disallow lazy loading by default if javascript in the embedding
// document would be able to access the contents of the frame, since in
// those cases deferring the frame could break the page. Note that this
// check does not take any possible redirects of |url| into account.
document.GetSecurityOrigin()->CanAccess(
SecurityOrigin::Create(url).get())) {
return false;
}
return true;
}
bool ShouldLazilyLoadFrame(const Document& document,
bool is_loading_attr_lazy) {
DCHECK(document.GetSettings());
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled() ||
!document.GetSettings()->GetLazyLoadEnabled()) {
return false;
}
// Disable explicit and automatic lazyload for backgrounded pages.
if (!document.IsPageVisible())
return false;
if (is_loading_attr_lazy)
return true;
if (!RuntimeEnabledFeatures::AutomaticLazyFrameLoadingEnabled())
return false;
// If lazy loading is restricted to only Data Saver users, then avoid
// lazy loading unless Data Saver is enabled, taking the Data Saver
// holdback into consideration.
if (RuntimeEnabledFeatures::
RestrictAutomaticLazyFrameLoadingToDataSaverEnabled() &&
!GetNetworkStateNotifier().SaveDataEnabled()) {
return false;
}
// Skip automatic lazyload when reloading a page.
if (!RuntimeEnabledFeatures::AutoLazyLoadOnReloadsEnabled() &&
document.Loader() && IsReloadLoadType(document.Loader()->LoadType())) {
return false;
}
return true;
}
} // namespace
SubframeLoadingDisabler::SubtreeRootSet&
SubframeLoadingDisabler::DisabledSubtreeRoots() {
DEFINE_STATIC_LOCAL(SubtreeRootSet, nodes, ());
return nodes;
}
// static
int HTMLFrameOwnerElement::PluginDisposeSuspendScope::suspend_count_ = 0;
void HTMLFrameOwnerElement::PluginDisposeSuspendScope::
PerformDeferredPluginDispose() {
DCHECK_EQ(suspend_count_, 1);
suspend_count_ = 0;
PluginSet dispose_set;
PluginsPendingDispose().swap(dispose_set);
for (const auto& plugin : dispose_set) {
plugin->Dispose();
}
}
HTMLFrameOwnerElement::HTMLFrameOwnerElement(const QualifiedName& tag_name,
Document& document)
: HTMLElement(tag_name, document),
content_frame_(nullptr),
embedded_content_view_(nullptr),
should_lazy_load_children_(DoesParentAllowLazyLoadingChildren(document)),
is_swapping_frames_(false) {}
LayoutEmbeddedContent* HTMLFrameOwnerElement::GetLayoutEmbeddedContent() const {
// HTMLObjectElement and HTMLEmbedElement may return arbitrary layoutObjects
// when using fallback content.
if (!GetLayoutObject() || !GetLayoutObject()->IsLayoutEmbeddedContent())
return nullptr;
return ToLayoutEmbeddedContent(GetLayoutObject());
}
void HTMLFrameOwnerElement::SetContentFrame(Frame& frame) {
// Make sure we will not end up with two frames referencing the same owner
// element.
DCHECK(!content_frame_ || content_frame_->Owner() != this);
// Disconnected frames should not be allowed to load.
DCHECK(isConnected());
// There should be no lazy load in progress since before SetContentFrame,
// |this| frame element should have been disconnected.
DCHECK(!lazy_load_frame_observer_ ||
!lazy_load_frame_observer_->IsLazyLoadPending());
content_frame_ = &frame;
// Invalidate compositing inputs, because a remote frame child can cause the
// owner to become composited.
if (auto* box = GetLayoutBox()) {
if (auto* layer = box->Layer())
layer->SetNeedsCompositingInputsUpdate();
}
SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create(
style_change_reason::kFrame));
for (ContainerNode* node = this; node; node = node->ParentOrShadowHostNode())
node->IncrementConnectedSubframeCount();
}
void HTMLFrameOwnerElement::ClearContentFrame() {
if (!content_frame_)
return;
// It's possible for there to be a lazy load in progress right now if
// Frame::Detach() was called without
// HTMLFrameOwnerElement::DisconnectContentFrame() being called first, so
// cancel any pending lazy load here.
// TODO(dcheng): Change this back to a DCHECK asserting that no lazy load is
// in progress once https://crbug.com/773683 is fixed.
CancelPendingLazyLoad();
DCHECK_EQ(content_frame_->Owner(), this);
content_frame_ = nullptr;
embedding_token_ = base::nullopt;
for (ContainerNode* node = this; node; node = node->ParentOrShadowHostNode())
node->DecrementConnectedSubframeCount();
}
void HTMLFrameOwnerElement::DisconnectContentFrame() {
if (!ContentFrame())
return;
CancelPendingLazyLoad();
// Removing a subframe that was still loading can impact the result of
// AllDescendantsAreComplete that is consulted by Document::ShouldComplete.
// Therefore we might need to re-check this after removing the subframe. The
// re-check is not needed for local frames (which will handle re-checking from
// FrameLoader::DidFinishNavigation that responds to LocalFrame::Detach).
// OTOH, re-checking is required for OOPIFs - see https://crbug.com/779433.
Document& parent_doc = GetDocument();
bool have_to_check_if_parent_is_completed =
ContentFrame()->IsRemoteFrame() && ContentFrame()->IsLoading();
// FIXME: Currently we don't do this in removedFrom because this causes an
// unload event in the subframe which could execute script that could then
// reach up into this document and then attempt to look back down. We should
// see if this behavior is really needed as Gecko does not allow this.
ContentFrame()->Detach(FrameDetachType::kRemove);
// Check if removing the subframe caused |parent_doc| to finish loading.
if (have_to_check_if_parent_is_completed)
parent_doc.CheckCompleted();
}
HTMLFrameOwnerElement::~HTMLFrameOwnerElement() {
// An owner must by now have been informed of detachment
// when the frame was closed.
DCHECK(!content_frame_);
}
Document* HTMLFrameOwnerElement::contentDocument() const {
auto* content_local_frame = DynamicTo<LocalFrame>(content_frame_.Get());
return content_local_frame ? content_local_frame->GetDocument() : nullptr;
}
DOMWindow* HTMLFrameOwnerElement::contentWindow() const {
return content_frame_ ? content_frame_->DomWindow() : nullptr;
}
void HTMLFrameOwnerElement::SetSandboxFlags(
network::mojom::blink::WebSandboxFlags flags) {
frame_policy_.sandbox_flags = flags;
// Recalculate the container policy in case the allow-same-origin flag has
// changed.
frame_policy_.container_policy = ConstructContainerPolicy(nullptr);
// Don't notify about updates if ContentFrame() is null, for example when
// the subframe hasn't been created yet.
if (ContentFrame()) {
GetDocument().GetFrame()->Client()->DidChangeFramePolicy(ContentFrame(),
frame_policy_);
}
}
void HTMLFrameOwnerElement::SetDisallowDocumentAccesss(bool disallowed) {
frame_policy_.disallow_document_access = disallowed;
// Don't notify about updates if ContentFrame() is null, for example when
// the subframe hasn't been created yet.
if (ContentFrame()) {
GetDocument().GetFrame()->Client()->DidChangeFramePolicy(ContentFrame(),
frame_policy_);
}
}
bool HTMLFrameOwnerElement::IsKeyboardFocusable() const {
return content_frame_ && HTMLElement::IsKeyboardFocusable();
}
void HTMLFrameOwnerElement::DisposePluginSoon(WebPluginContainerImpl* plugin) {
if (PluginDisposeSuspendScope::suspend_count_) {
PluginsPendingDispose().insert(plugin);
PluginDisposeSuspendScope::suspend_count_ |= 1;
} else
plugin->Dispose();
}
void HTMLFrameOwnerElement::UpdateContainerPolicy(Vector<String>* messages) {
frame_policy_.container_policy = ConstructContainerPolicy(messages);
// Don't notify about updates if ContentFrame() is null, for example when
// the subframe hasn't been created yet.
if (ContentFrame()) {
GetDocument().GetFrame()->Client()->DidChangeFramePolicy(ContentFrame(),
frame_policy_);
}
}
void HTMLFrameOwnerElement::UpdateRequiredPolicy() {
const auto* frame = GetDocument().GetFrame();
DocumentPolicy::FeatureState new_required_policy =
frame
? DocumentPolicy::MergeFeatureState(
ConstructRequiredPolicy(), /* self_required_policy */
frame->GetRequiredDocumentPolicy() /* parent_required_policy */)
: ConstructRequiredPolicy();
// Filter out policies that are disabled by origin trials.
frame_policy_.required_document_policy.clear();
for (auto i = new_required_policy.begin(), last = new_required_policy.end();
i != last;) {
if (!DisabledByOriginTrial(i->first, &GetDocument()))
frame_policy_.required_document_policy.insert(*i);
++i;
}
if (ContentFrame()) {
frame->Client()->DidChangeFramePolicy(ContentFrame(), frame_policy_);
}
}
network::mojom::blink::TrustTokenParamsPtr
HTMLFrameOwnerElement::ConstructTrustTokenParams() const {
return nullptr;
}
void HTMLFrameOwnerElement::FrameOwnerPropertiesChanged() {
// Don't notify about updates if ContentFrame() is null, for example when
// the subframe hasn't been created yet; or if we are in the middle of
// swapping one frame for another, in which case the final state of
// properties will be propagated at the end of the swapping operation.
if (!is_swapping_frames_ && ContentFrame()) {
GetDocument().GetFrame()->Client()->DidChangeFrameOwnerProperties(this);
}
}
void HTMLFrameOwnerElement::AddResourceTiming(const ResourceTimingInfo& info) {
// Resource timing info should only be reported if the subframe is attached.
DCHECK(ContentFrame() && ContentFrame()->IsLocalFrame());
DOMWindowPerformance::performance(*GetDocument().domWindow())
->GenerateAndAddResourceTiming(info, localName());
}
void HTMLFrameOwnerElement::DispatchLoad() {
if (lazy_load_frame_observer_)
lazy_load_frame_observer_->RecordMetricsOnLoadFinished();
DispatchScopedEvent(*Event::Create(event_type_names::kLoad));
}
Document* HTMLFrameOwnerElement::getSVGDocument(
ExceptionState& exception_state) const {
Document* doc = contentDocument();
if (doc && doc->IsSVGDocument())
return doc;
return nullptr;
}
void HTMLFrameOwnerElement::SetEmbeddedContentView(
EmbeddedContentView* embedded_content_view) {
if (embedded_content_view == embedded_content_view_)
return;
Document* doc = contentDocument();
if (doc && doc->GetFrame()) {
bool will_be_display_none = !embedded_content_view;
if (IsDisplayNone() != will_be_display_none) {
doc->WillChangeFrameOwnerProperties(
MarginWidth(), MarginHeight(), ScrollbarMode(), will_be_display_none);
}
}
EmbeddedContentView* old_view = embedded_content_view_.Get();
embedded_content_view_ = embedded_content_view;
if (old_view) {
if (old_view->IsAttached()) {
old_view->DetachFromLayout();
if (old_view->IsPluginView())
DisposePluginSoon(To<WebPluginContainerImpl>(old_view));
else
old_view->Dispose();
}
}
FrameOwnerPropertiesChanged();
GetDocument().GetRootScrollerController().DidUpdateIFrameFrameView(*this);
LayoutEmbeddedContent* layout_embedded_content = GetLayoutEmbeddedContent();
if (!layout_embedded_content)
return;
layout_embedded_content->UpdateOnEmbeddedContentViewChange();
if (embedded_content_view_) {
// TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout
// crashes. Perhaps view is getting reattached while document is shutting
// down.
if (doc) {
CHECK_NE(doc->Lifecycle().GetState(), DocumentLifecycle::kStopping);
}
DCHECK_EQ(GetDocument().View(), layout_embedded_content->GetFrameView());
DCHECK(layout_embedded_content->GetFrameView());
embedded_content_view_->AttachToLayout();
}
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->ChildrenChanged(layout_embedded_content);
}
EmbeddedContentView* HTMLFrameOwnerElement::ReleaseEmbeddedContentView() {
if (!embedded_content_view_)
return nullptr;
if (embedded_content_view_->IsAttached())
embedded_content_view_->DetachFromLayout();
LayoutEmbeddedContent* layout_embedded_content = GetLayoutEmbeddedContent();
if (layout_embedded_content) {
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->ChildrenChanged(layout_embedded_content);
}
return embedded_content_view_.Release();
}
bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
const KURL& url,
const AtomicString& frame_name,
bool replace_current_item) {
// Update the |should_lazy_load_children_| value according to the "loading"
// attribute immediately, so that it still gets respected even if the "src"
// attribute gets parsed in ParseAttribute() before the "loading" attribute
// does. Note that when the *feature policy* for "lazyload" is disabled, the
// attribute value loading="eager" is ignored (i.e., interpreted as
// "auto" instead).
if (should_lazy_load_children_ &&
EqualIgnoringASCIICase(FastGetAttribute(html_names::kLoadingAttr),
"eager") &&
!GetDocument().IsLazyLoadPolicyEnforced()) {
should_lazy_load_children_ = false;
}
UpdateContainerPolicy();
UpdateRequiredPolicy();
KURL url_to_request = url.IsNull() ? BlankURL() : url;
ResourceRequestHead request(url_to_request);
request.SetReferrerPolicy(ReferrerPolicyAttribute());
network::mojom::blink::TrustTokenParamsPtr trust_token_params =
ConstructTrustTokenParams();
if (trust_token_params)
request.SetTrustTokenParams(*trust_token_params);
if (ContentFrame()) {
// TODO(sclittle): Support lazily loading frame navigations.
FrameLoadRequest frame_load_request(&GetDocument(), request);
frame_load_request.SetClientRedirectReason(
ClientNavigationReason::kFrameNavigation);
WebFrameLoadType frame_load_type = WebFrameLoadType::kStandard;
if (replace_current_item)
frame_load_type = WebFrameLoadType::kReplaceCurrentItem;
ContentFrame()->Navigate(frame_load_request, frame_load_type);
return true;
}
if (!SubframeLoadingDisabler::CanLoadFrame(*this))
return false;
if (GetDocument().GetFrame()->GetPage()->SubframeCount() >=
Page::kMaxNumberOfFrames)
return false;
LocalFrame* child_frame =
GetDocument().GetFrame()->Client()->CreateFrame(frame_name, this);
DCHECK_EQ(ContentFrame(), child_frame);
if (!child_frame)
return false;
WebFrameLoadType child_load_type = WebFrameLoadType::kReplaceCurrentItem;
if (!GetDocument().LoadEventFinished() &&
GetDocument().Loader()->LoadType() ==
WebFrameLoadType::kReloadBypassingCache) {
child_load_type = WebFrameLoadType::kReloadBypassingCache;
request.SetCacheMode(mojom::FetchCacheMode::kBypassCache);
}
// Plug-ins should not load via service workers as plug-ins may have their
// own origin checking logic that may get confused if service workers respond
// with resources from another origin.
// https://w3c.github.io/ServiceWorker/#implementer-concerns
if (IsPlugin())
request.SetSkipServiceWorker(true);
// When the feature policy "loading-frame-default-eager" is disabled in
// the document, loading attribute value "auto" (or unset/invalid values) will
// also be interpreted as "lazy".
const auto& loading_attr = FastGetAttribute(html_names::kLoadingAttr);
bool loading_lazy_set = EqualIgnoringASCIICase(loading_attr, "lazy") ||
(IsLoadingFrameDefaultEagerEnforced() &&
!EqualIgnoringASCIICase(loading_attr, "eager"));
if (!lazy_load_frame_observer_ &&
IsFrameLazyLoadable(GetDocument(), url, loading_lazy_set,
should_lazy_load_children_)) {
// Avoid automatically deferring subresources inside a lazily loaded frame.
// This will make it possible for subresources in hidden frames to load that
// will never be visible, as well as make it so that deferred frames that
// have multiple layers of iframes inside them can load faster once they're
// near the viewport or visible.
should_lazy_load_children_ = false;
lazy_load_frame_observer_ =
MakeGarbageCollected<LazyLoadFrameObserver>(*this);
if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled())
lazy_load_frame_observer_->StartTrackingVisibilityMetrics();
if (ShouldLazilyLoadFrame(GetDocument(), loading_lazy_set)) {
lazy_load_frame_observer_->DeferLoadUntilNearViewport(request,
child_load_type);
return true;
}
}
FrameLoadRequest frame_load_request(&GetDocument(), request);
child_frame->Loader().StartNavigation(frame_load_request, child_load_type);
return true;
}
void HTMLFrameOwnerElement::CancelPendingLazyLoad() {
if (!lazy_load_frame_observer_)
return;
lazy_load_frame_observer_->CancelPendingLazyLoad();
}
bool HTMLFrameOwnerElement::ShouldLazyLoadChildren() const {
return should_lazy_load_children_;
}
void HTMLFrameOwnerElement::ParseAttribute(
const AttributeModificationParams& params) {
if (params.name == html_names::kLoadingAttr) {
// Note that when the *feature policy* for "lazyload" is disabled, the
// attribute value loading="eager" is ignored (i.e., interpreted as
// "auto" instead).
if (EqualIgnoringASCIICase(params.new_value, "eager") &&
!GetDocument().IsLazyLoadPolicyEnforced()) {
UseCounter::Count(GetDocument(),
WebFeature::kLazyLoadFrameLoadingAttributeEager);
should_lazy_load_children_ = false;
if (lazy_load_frame_observer_ &&
lazy_load_frame_observer_->IsLazyLoadPending()) {
lazy_load_frame_observer_->LoadImmediately();
}
} else if (EqualIgnoringASCIICase(params.new_value, "lazy")) {
UseCounter::Count(GetDocument(),
WebFeature::kLazyLoadFrameLoadingAttributeLazy);
}
} else {
HTMLElement::ParseAttribute(params);
}
}
void HTMLFrameOwnerElement::FrameCrossOriginToParentFrameChanged() {
if (base::FeatureList::IsEnabled(
blink::features::kCompositeCrossOriginIframes)) {
SetNeedsCompositingUpdate();
}
}
void HTMLFrameOwnerElement::SetEmbeddingToken(
const base::UnguessableToken& embedding_token) {
DCHECK(content_frame_);
DCHECK(content_frame_->IsRemoteFrame());
embedding_token_ = embedding_token;
}
void HTMLFrameOwnerElement::Trace(Visitor* visitor) {
visitor->Trace(content_frame_);
visitor->Trace(embedded_content_view_);
visitor->Trace(lazy_load_frame_observer_);
HTMLElement::Trace(visitor);
FrameOwner::Trace(visitor);
}
bool HTMLFrameOwnerElement::IsLoadingFrameDefaultEagerEnforced() const {
return RuntimeEnabledFeatures::ExperimentalProductivityFeaturesEnabled() &&
!GetDocument().IsFeatureEnabled(
mojom::blink::FeaturePolicyFeature::kLoadingFrameDefaultEager);
}
} // namespace blink