| /* |
| * 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/exported/web_plugin_container_impl.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_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 = PersistentHeapHashSet<Member<WebPluginContainerImpl>>; |
| PluginSet& PluginsPendingDispose() { |
| DEFINE_STATIC_LOCAL(PluginSet, set, ()); |
| 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( |
| StyleChangeReason::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::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(EventTypeNames::load)); |
| } |
| |
| 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. |
| if (should_lazy_load_children_ && |
| EqualIgnoringASCIICase(FastGetAttribute(HTMLNames::lazyloadAttr), |
| "off")) { |
| should_lazy_load_children_ = false; |
| } |
| |
| UpdateContainerPolicy(); |
| |
| if (ContentFrame()) { |
| // TODO(sclittle): Support lazily loading frame navigations. |
| ContentFrame()->ScheduleNavigation(GetDocument(), url, replace_current_item, |
| 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); |
| 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(HTMLNames::lazyloadAttr), |
| "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 == HTMLNames::lazyloadAttr) { |
| if (EqualIgnoringASCIICase(params.new_value, "off")) { |
| 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 |