blob: 26bdc001651992ad0e4cde2331e043bdbf2fba7b [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/platform/modules/fetch/fetch_api_request.mojom-shared.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/exported/web_plugin_container_impl.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/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/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();
}
} // 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),
sandbox_flags_(kSandboxNone),
should_lazy_load_children_(DoesParentAllowLazyLoadingChildren(document)) {
}
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;
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 {
return (content_frame_ && content_frame_->IsLocalFrame())
? ToLocalFrame(content_frame_)->GetDocument()
: nullptr;
}
DOMWindow* HTMLFrameOwnerElement::contentWindow() const {
return content_frame_ ? content_frame_->DomWindow() : nullptr;
}
void HTMLFrameOwnerElement::SetSandboxFlags(SandboxFlags flags) {
sandbox_flags_ = flags;
// Recalculate the container policy in case the allow-same-origin flag has
// changed.
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(), sandbox_flags_, container_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) {
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(), sandbox_flags_, container_policy_);
}
}
void HTMLFrameOwnerElement::PointerEventsChanged() {
if (ContentFrame() && ContentFrame()->IsRemoteFrame()) {
ToRemoteFrame(ContentFrame())->PointerEventsChanged();
}
}
void HTMLFrameOwnerElement::FrameOwnerPropertiesChanged() {
// Don't notify about updates if ContentFrame() is null, for example when
// the subframe hasn't been created yet.
if (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));
}
const ParsedFeaturePolicy& HTMLFrameOwnerElement::ContainerPolicy() const {
return container_policy_;
}
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(), ScrollingMode(), will_be_display_none);
}
}
if (embedded_content_view_) {
if (embedded_content_view_->IsAttached()) {
embedded_content_view_->DetachFromLayout();
if (embedded_content_view_->IsPluginView())
DisposePluginSoon(ToWebPluginContainerImpl(embedded_content_view_));
else
embedded_content_view_->Dispose();
}
}
embedded_content_view_ = embedded_content_view;
FrameOwnerPropertiesChanged();
GetDocument().GetRootScrollerController().DidUpdateIFrameFrameView(*this);
LayoutEmbeddedContent* layout_embedded_content =
ToLayoutEmbeddedContent(GetLayoutObject());
if (!layout_embedded_content)
return;
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);
}
layout_embedded_content->UpdateOnEmbeddedContentViewChange();
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 =
ToLayoutEmbeddedContent(GetLayoutObject());
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 "lazyload"
// attribute immediately, so that it still gets respected even if the "src"
// attribute gets parsed in ParseAttribute() before the "lazyload" attribute
// does. Note that when the *feature policy* for "lazyload" is disabled, the
// attribute value "off" for "lazyload" is ignored (i.e., interpreted as
// "auto" instead).
if (should_lazy_load_children_ &&
EqualIgnoringASCIICase(FastGetAttribute(html_names::kLazyloadAttr),
"off") &&
!GetDocument().IsLazyLoadPolicyEnforced()) {
should_lazy_load_children_ = false;
}
UpdateContainerPolicy();
if (ContentFrame()) {
// TODO(sclittle): Support lazily loading frame navigations.
WebFrameLoadType frame_load_type = WebFrameLoadType::kStandard;
if (replace_current_item)
frame_load_type = WebFrameLoadType::kReplaceCurrentItem;
ContentFrame()->ScheduleNavigation(GetDocument(), url, frame_load_type,
UserGestureStatus::kNone);
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;
ResourceRequest request(url.IsNull() ? BlankURL() : url);
ReferrerPolicy policy = ReferrerPolicyAttribute();
request.SetReferrerPolicy(policy);
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);
if ((RuntimeEnabledFeatures::LazyFrameLoadingEnabled() ||
RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) &&
!lazy_load_frame_observer_ &&
// 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.
url.ProtocolIsInHTTPFamily() &&
(EqualIgnoringASCIICase(FastGetAttribute(html_names::kLazyloadAttr),
"on") ||
(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.
!GetDocument().GetSecurityOrigin()->CanAccess(
SecurityOrigin::Create(url).get())))) {
// By default, avoid 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_ = new LazyLoadFrameObserver(*this);
if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled())
lazy_load_frame_observer_->StartTrackingVisibilityMetrics();
if (RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
lazy_load_frame_observer_->DeferLoadUntilNearViewport(request,
child_load_type);
return true;
}
}
child_frame->Loader().StartNavigation(
FrameLoadRequest(&GetDocument(), 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::kLazyloadAttr) {
// Note that when the *feature policy* for "lazyload" is disabled, the
// attribute value "off" for "lazyload" is ignored (i.e., interpreted as
// "auto" instead).
if (EqualIgnoringASCIICase(params.new_value, "off") &&
!GetDocument().IsLazyLoadPolicyEnforced()) {
should_lazy_load_children_ = false;
if (lazy_load_frame_observer_ &&
lazy_load_frame_observer_->IsLazyLoadPending()) {
lazy_load_frame_observer_->LoadImmediately();
}
}
} else {
HTMLElement::ParseAttribute(params);
}
}
void HTMLFrameOwnerElement::Trace(blink::Visitor* visitor) {
visitor->Trace(content_frame_);
visitor->Trace(embedded_content_view_);
visitor->Trace(lazy_load_frame_observer_);
HTMLElement::Trace(visitor);
FrameOwner::Trace(visitor);
}
} // namespace blink