| /* |
| * 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 "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/time/time.h" |
| #include "services/network/public/mojom/content_security_policy.mojom-blink-forward.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/color_scheme.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h" |
| #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom-blink.h" |
| #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink.h" |
| #include "third_party/blink/public/mojom/timing/resource_timing.mojom-blink-forward.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/execution_context/execution_context.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/fenced_frame/html_fenced_frame_element.h" |
| #include "third_party/blink/renderer/core/html/lazy_load_frame_observer.h" |
| #include "third_party/blink/renderer/core/html/loading_attribute.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/loader/url_matcher.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/instrumentation/resource_coordinator/renderer_resource_coordinator.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_timing_utils.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/scheduler/public/thread_scheduler.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(ExecutionContext* context, |
| const KURL& url, |
| bool is_loading_attr_lazy, |
| bool should_lazy_load_children) { |
| // 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; |
| |
| // Do not lazyload frames when JavaScript is disabled, regardless of the |
| // `loading` attribute. |
| if (!context->CanExecuteScripts(kNotAboutToExecuteScript)) |
| 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. |
| context->GetSecurityOrigin()->CanAccess( |
| SecurityOrigin::Create(url).get())) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class AutomaticLazyLoadFrame { |
| kFeatureNotEnabled = 0, |
| kTargetFramesNotFound = 1, |
| kTargetFramesFound = 2, |
| kMaxValue = kTargetFramesFound, |
| }; |
| |
| int GetLazyAdsSkipFrameCount() { |
| DCHECK( |
| base::FeatureList::IsEnabled(features::kAutomaticLazyFrameLoadingToAds)); |
| static const int skip_frame_count = features::kSkipFrameCountForLazyAds.Get(); |
| return skip_frame_count; |
| } |
| |
| int GetLazyEmbedsSkipFrameCount() { |
| DCHECK(base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbeds)); |
| static const int skip_frame_count = |
| features::kSkipFrameCountForLazyEmbeds.Get(); |
| return skip_frame_count; |
| } |
| |
| bool CheckAndRecordIfShouldLazilyLoadFrame(const Document& document, |
| bool is_loading_attr_lazy, |
| bool is_eligible_for_lazy_embeds, |
| bool is_eligible_for_lazy_ads, |
| bool record_uma) { |
| DCHECK(document.GetSettings()); |
| if (!document.GetSettings()->GetLazyLoadEnabled()) { |
| return false; |
| } |
| |
| // Disable explicit lazyload for backgrounded pages. |
| if (!document.IsPageVisible()) |
| return false; |
| |
| if (is_loading_attr_lazy) |
| return true; |
| |
| Document& top_document = document.TopDocument(); |
| if (top_document.Loader() && |
| top_document.Loader()->IsReloadedOrFormSubmitted()) { |
| return false; |
| } |
| |
| if (record_uma) { |
| base::UmaHistogramEnumeration( |
| "Blink.AutomaticLazyLoadFrame", |
| !base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbeds) |
| ? AutomaticLazyLoadFrame::kFeatureNotEnabled |
| : is_eligible_for_lazy_embeds |
| ? AutomaticLazyLoadFrame::kTargetFramesFound |
| : AutomaticLazyLoadFrame::kTargetFramesNotFound); |
| } |
| |
| if (is_eligible_for_lazy_embeds) { |
| top_document.IncrementLazyEmbedsFrameCount(); |
| UseCounter::Count(top_document, WebFeature::kAutomaticLazyEmbeds); |
| } |
| |
| if (is_eligible_for_lazy_ads) { |
| top_document.IncrementLazyAdsFrameCount(); |
| UseCounter::Count(top_document, WebFeature::kAutomaticLazyAds); |
| } |
| |
| if (is_eligible_for_lazy_embeds && |
| base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbeds) && |
| document.GetImmediateChildFrameCreationCount() > |
| GetLazyEmbedsSkipFrameCount()) { |
| return true; |
| } |
| |
| if (is_eligible_for_lazy_ads && |
| base::FeatureList::IsEnabled(features::kAutomaticLazyFrameLoadingToAds) && |
| document.GetImmediateChildFrameCreationCount() > |
| GetLazyAdsSkipFrameCount()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Checks if the passed url is the same origin with the document. |
| // This is called in order to limit LazyEmbeds/Ads to apply only cross-origin |
| // frames. |
| // We're not sure if this is 100% needed, and we should move the check closer |
| // to context->GetSecurityOrigin()->CanAccess check. |
| bool AreSameOrigin(const Document& document, const KURL& url) { |
| return SecurityOrigin::AreSameOrigin(url, document.Url()); |
| } |
| |
| const base::TimeDelta GetLazyEmbedsTimeoutMs() { |
| DCHECK(base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbeds)); |
| static const base::TimeDelta timeout_ms = |
| base::Milliseconds(features::kTimeoutMillisForLazyEmbeds.Get()); |
| return timeout_ms; |
| } |
| |
| const base::TimeDelta GetLazyAdsTimeoutMs() { |
| DCHECK( |
| base::FeatureList::IsEnabled(features::kAutomaticLazyFrameLoadingToAds)); |
| static const base::TimeDelta timeout_ms = |
| base::Milliseconds(features::kTimeoutMillisForLazyAds.Get()); |
| return timeout_ms; |
| } |
| |
| } // 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), |
| should_lazy_load_children_(DoesParentAllowLazyLoadingChildren(document)) { |
| document.IncrementImmediateChildFrameCreationCount(); |
| } |
| |
| const QualifiedName& HTMLFrameOwnerElement::SubResourceAttributeName() const { |
| // This doesn't really make sense, but it preserves existing behavior |
| // that may or may not matter for the one caller of this method. |
| |
| // It might make more sense for this to be pure virtual and the |
| // remaining subclasses that don't override this (frame, iframe, |
| // fenced frame) to do so. |
| return QualifiedName::Null(); |
| } |
| |
| LayoutEmbeddedContent* HTMLFrameOwnerElement::GetLayoutEmbeddedContent() const { |
| // HTMLObjectElement and HTMLEmbedElement may return arbitrary layoutObjects |
| // when using fallback content. |
| return DynamicTo<LayoutEmbeddedContent>(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()); |
| |
| DCHECK_NE(content_frame_, &frame); |
| auto* resource_coordinator = RendererResourceCoordinator::Get(); |
| if (content_frame_) |
| resource_coordinator->OnBeforeContentFrameDetached(*content_frame_, *this); |
| resource_coordinator->OnBeforeContentFrameAttached(frame, *this); |
| |
| 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); |
| RendererResourceCoordinator::Get()->OnBeforeContentFrameDetached( |
| *content_frame_, *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(); |
| |
| // Reset the collapsed state. The frame element will be collapsed again if it |
| // is blocked again in the future. |
| SetCollapsed(false); |
| } |
| |
| 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(); |
| |
| // Don't notify about updates if ContentFrame() is null, for example when |
| // the subframe hasn't been created yet. |
| if (ContentFrame()) { |
| GetDocument().GetFrame()->GetLocalFrameHostRemote().DidChangeFramePolicy( |
| ContentFrame()->GetFrameToken(), frame_policy_); |
| } |
| } |
| |
| bool HTMLFrameOwnerElement::IsKeyboardFocusable( |
| UpdateBehavior update_behavior) const { |
| return content_frame_ && HTMLElement::IsKeyboardFocusable(update_behavior); |
| } |
| |
| void HTMLFrameOwnerElement::DisposePluginSoon(WebPluginContainerImpl* plugin) { |
| if (PluginDisposeSuspendScope::suspend_count_) { |
| PluginsPendingDispose().insert(plugin); |
| PluginDisposeSuspendScope::suspend_count_ |= 1; |
| } else |
| plugin->Dispose(); |
| } |
| |
| void HTMLFrameOwnerElement::UpdateContainerPolicy() { |
| frame_policy_.container_policy = ConstructContainerPolicy(); |
| DidChangeContainerPolicy(); |
| } |
| |
| void HTMLFrameOwnerElement::DidChangeContainerPolicy() { |
| // Don't notify about updates if ContentFrame() is null, for example when |
| // the subframe hasn't been created yet. |
| if (ContentFrame()) { |
| GetDocument().GetFrame()->GetLocalFrameHostRemote().DidChangeFramePolicy( |
| ContentFrame()->GetFrameToken(), frame_policy_); |
| } |
| } |
| |
| void HTMLFrameOwnerElement::UpdateRequiredPolicy() { |
| if (!RuntimeEnabledFeatures::DocumentPolicyNegotiationEnabled( |
| GetExecutionContext())) |
| return; |
| |
| auto* frame = GetDocument().GetFrame(); |
| DocumentPolicyFeatureState 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, GetExecutionContext())) |
| frame_policy_.required_document_policy.insert(*i); |
| ++i; |
| } |
| |
| if (ContentFrame()) { |
| frame->GetLocalFrameHostRemote().DidChangeFramePolicy( |
| ContentFrame()->GetFrameToken(), 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()) |
| return; |
| |
| mojom::blink::FrameOwnerPropertiesPtr properties = |
| mojom::blink::FrameOwnerProperties::New(); |
| properties->name = BrowsingContextContainerName().IsNull() |
| ? WTF::g_empty_string |
| : BrowsingContextContainerName(), |
| properties->scrollbar_mode = ScrollbarMode(); |
| properties->margin_width = MarginWidth(); |
| properties->margin_height = MarginHeight(); |
| properties->allow_fullscreen = AllowFullscreen(); |
| properties->allow_payment_request = AllowPaymentRequest(); |
| properties->is_display_none = IsDisplayNone(); |
| properties->color_scheme = GetColorScheme(); |
| |
| GetDocument() |
| .GetFrame() |
| ->GetLocalFrameHostRemote() |
| .DidChangeFrameOwnerProperties(ContentFrame()->GetFrameToken(), |
| std::move(properties)); |
| } |
| |
| void HTMLFrameOwnerElement::AddResourceTiming( |
| mojom::blink::ResourceTimingInfoPtr info) { |
| // Resource timing info should only be reported if the subframe is attached. |
| DCHECK(ContentFrame()); |
| |
| // Make sure we don't double-report, e.g. in the case of restored iframes. |
| if (!HasPendingFallbackTimingInfo()) { |
| return; |
| } |
| |
| // This would only happen in rare cases, where the frame is navigated from the |
| // outside, e.g. by a web extension or window.open() with target, and that |
| // navigation would cancel the container-initiated navigation. This safeguard |
| // would make this type of race harmless. |
| // TODO(crbug.com/1410705): fix this properly by moving IFrame reporting to |
| // the browser side. |
| if (fallback_timing_info_->name != info->name) { |
| return; |
| } |
| |
| DOMWindowPerformance::performance(*GetDocument().domWindow()) |
| ->AddResourceTiming(std::move(info), localName()); |
| DidReportResourceTiming(); |
| } |
| |
| bool HTMLFrameOwnerElement::HasPendingFallbackTimingInfo() const { |
| return !!fallback_timing_info_; |
| } |
| |
| void HTMLFrameOwnerElement::DidReportResourceTiming() { |
| fallback_timing_info_.reset(); |
| } |
| |
| // This will report fallback timing only if the "real" resource timing had not |
| // been previously reported: e.g. a cross-origin iframe without TAO. |
| void HTMLFrameOwnerElement::ReportFallbackResourceTimingIfNeeded() { |
| if (!fallback_timing_info_) { |
| return; |
| } |
| |
| mojom::blink::ResourceTimingInfoPtr resource_timing_info; |
| resource_timing_info.Swap(&fallback_timing_info_); |
| resource_timing_info->response_end = base::TimeTicks::Now(); |
| |
| DOMWindowPerformance::performance(*GetDocument().domWindow()) |
| ->AddResourceTiming(std::move(resource_timing_info), localName()); |
| } |
| |
| void HTMLFrameOwnerElement::DispatchLoad() { |
| ReportFallbackResourceTimingIfNeeded(); |
| 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, |
| GetColorScheme()); |
| } |
| } |
| |
| 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_) { |
| if (doc) { |
| DCHECK_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::LoadImmediatelyIfLazy() { |
| if (!lazy_load_frame_observer_) |
| return false; |
| |
| bool lazy_load_pending = lazy_load_frame_observer_->IsLazyLoadPending(); |
| if (lazy_load_pending) |
| lazy_load_frame_observer_->LoadImmediately(); |
| return lazy_load_pending; |
| } |
| |
| bool HTMLFrameOwnerElement::LazyLoadIfPossible( |
| const KURL& url, |
| const ResourceRequestHead& request, |
| WebFrameLoadType frame_load_type) { |
| const auto& loading_attr = FastGetAttribute(html_names::kLoadingAttr); |
| bool loading_lazy_set = EqualIgnoringASCIICase(loading_attr, "lazy"); |
| |
| if (!IsFrameLazyLoadable(GetExecutionContext(), url, loading_lazy_set, |
| should_lazy_load_children_)) { |
| return false; |
| } |
| |
| // 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; |
| |
| if (lazy_load_frame_observer_) |
| lazy_load_frame_observer_->CancelPendingLazyLoad(); |
| |
| lazy_load_frame_observer_ = MakeGarbageCollected<LazyLoadFrameObserver>( |
| *this, LazyLoadFrameObserver::LoadType::kSubsequent); |
| |
| // TODO(crbug.com/1341892) Remove having multiple booleans here. We eventually |
| // select one reason to decide the timeout, so essentially we don't have to |
| // keep them. But currently we need these two booleans separately to record |
| // UKM in CheckAndRecordIfShouldLazilyLoadFrame. Once we confirm that we can |
| // ignore AutomaticLazyLoadReason::kBothEmbedsAndAds case due to the small |
| // amount of the data size, we remove these booleans and |
| // AutomaticLazyLoadReason::kBothEmbedsAndAds. |
| const bool is_eligible_for_lazy_embeds = IsEligibleForLazyEmbeds(url); |
| const bool is_eligible_for_lazy_ads = IsEligibleForLazyAds(url); |
| AutomaticLazyLoadReason auto_lazy_load_reason; |
| if (is_eligible_for_lazy_embeds && is_eligible_for_lazy_ads) { |
| auto_lazy_load_reason = AutomaticLazyLoadReason::kBothEmbedsAndAds; |
| } else if (is_eligible_for_lazy_embeds) { |
| auto_lazy_load_reason = AutomaticLazyLoadReason::kEmbeds; |
| } else if (is_eligible_for_lazy_ads) { |
| auto_lazy_load_reason = AutomaticLazyLoadReason::kAds; |
| } else { |
| auto_lazy_load_reason = AutomaticLazyLoadReason::kNotEligible; |
| } |
| base::UmaHistogramEnumeration("Blink.AutomaticLazyFrameLoad.Reason", |
| auto_lazy_load_reason); |
| |
| if (CheckAndRecordIfShouldLazilyLoadFrame(GetDocument(), loading_lazy_set, |
| is_eligible_for_lazy_embeds, |
| is_eligible_for_lazy_ads, |
| /*record_uma=*/true)) { |
| lazy_load_frame_observer_->DeferLoadUntilNearViewport(request, |
| frame_load_type); |
| MaybeSetTimeoutToStartFrameLoading(url, loading_lazy_set, |
| auto_lazy_load_reason); |
| |
| return true; |
| } |
| return false; |
| } |
| |
| bool HTMLFrameOwnerElement::IsCurrentlyWithinFrameLimit() const { |
| LocalFrame* frame = GetDocument().GetFrame(); |
| if (!frame) |
| return false; |
| Page* page = frame->GetPage(); |
| if (!page) |
| return false; |
| return page->SubframeCount() < Page::MaxNumberOfFrames(); |
| } |
| |
| bool HTMLFrameOwnerElement::LoadOrRedirectSubframe( |
| const KURL& url, |
| const AtomicString& frame_name, |
| bool replace_current_item) { |
| TRACE_EVENT0("navigation", "HTMLFrameOwnerElement::LoadOrRedirectSubframe"); |
| |
| // If the subframe navigation is aborted or TAO fails, we report a "fallback" |
| // entry that starts at navigation and ends at load/error event. |
| if (url.ProtocolIsInHTTPFamily()) { |
| fallback_timing_info_ = |
| CreateResourceTimingInfo(base::TimeTicks::Now(), url, |
| /*response=*/nullptr); |
| } |
| |
| // 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. |
| if (should_lazy_load_children_ && |
| EqualIgnoringASCIICase(FastGetAttribute(html_names::kLoadingAttr), |
| "eager")) { |
| should_lazy_load_children_ = false; |
| } |
| |
| UpdateContainerPolicy(); |
| UpdateRequiredPolicy(); |
| |
| KURL url_to_request = url.IsNull() ? BlankURL() : url; |
| ResourceRequestHead request(url_to_request); |
| request.SetReferrerPolicy(ReferrerPolicyAttribute()); |
| request.SetHasUserGesture( |
| LocalFrame::HasTransientUserActivation(GetDocument().GetFrame())); |
| |
| network::mojom::blink::TrustTokenParamsPtr trust_token_params = |
| ConstructTrustTokenParams(); |
| if (trust_token_params) |
| request.SetTrustTokenParams(*trust_token_params); |
| |
| if (ContentFrame()) { |
| FrameLoadRequest frame_load_request(GetDocument().domWindow(), request); |
| frame_load_request.SetIsContainerInitiated(true); |
| frame_load_request.SetClientRedirectReason( |
| ClientNavigationReason::kFrameNavigation); |
| WebFrameLoadType frame_load_type = WebFrameLoadType::kStandard; |
| if (replace_current_item) |
| frame_load_type = WebFrameLoadType::kReplaceCurrentItem; |
| |
| // Check if the existing frame is eligible to be lazy-loaded. This method |
| // should be called before starting the navigation. |
| if (LazyLoadIfPossible(url, request, frame_load_type)) { |
| return true; |
| } |
| |
| ContentFrame()->Navigate(frame_load_request, frame_load_type); |
| return true; |
| } |
| |
| if (!SubframeLoadingDisabler::CanLoadFrame(*this)) |
| return false; |
| |
| if (GetDocument().GetFrame()->GetPage()->SubframeCount() >= |
| Page::MaxNumberOfFrames()) { |
| return false; |
| } |
| |
| LocalFrame* child_frame = |
| GetDocument().GetFrame()->Client()->CreateFrame(frame_name, this); |
| DCHECK_EQ(ContentFrame(), child_frame); |
| if (!child_frame) |
| return false; |
| |
| // Propagate attributes like 'csp' or 'anonymous' to the browser process. |
| DidChangeAttributes(); |
| |
| WebFrameLoadType child_load_type = WebFrameLoadType::kReplaceCurrentItem; |
| // If the frame URL is not about:blank, see if it should do a |
| // kReloadBypassingCache navigation, following the parent frame. If the frame |
| // URL is about:blank, it should be committed synchronously as a |
| // kReplaceCurrentItem navigation (see https://crbug.com/778318). |
| if (url != BlankURL() && !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); |
| |
| // Check if the newly created child frame is eligible to be lazy-loaded. |
| // This method should be called before starting the navigation. |
| if (!lazy_load_frame_observer_ && |
| LazyLoadIfPossible(url, request, child_load_type)) { |
| return true; |
| } |
| |
| FrameLoadRequest frame_load_request(GetDocument().domWindow(), request); |
| frame_load_request.SetIsContainerInitiated(true); |
| 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) { |
| LoadingAttributeValue loading = GetLoadingAttributeValue(params.new_value); |
| if (loading == LoadingAttributeValue::kEager) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kLazyLoadFrameLoadingAttributeEager); |
| } else if (loading == LoadingAttributeValue::kLazy) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kLazyLoadFrameLoadingAttributeLazy); |
| } |
| |
| // Setting the loading attribute to eager should eagerly load any pending |
| // requests, just as unsetting the loading attribute does if automatic lazy |
| // loading is disabled. |
| if (loading == LoadingAttributeValue::kEager || |
| (GetDocument().GetSettings() && |
| !CheckAndRecordIfShouldLazilyLoadFrame( |
| GetDocument(), loading == LoadingAttributeValue::kLazy, |
| /*is_eligible_for_lazy_embeds=*/false, |
| /*is_eligible_for_lazy_ads=*/false, |
| /*record_uma=*/false))) { |
| should_lazy_load_children_ = false; |
| if (lazy_load_frame_observer_ && |
| lazy_load_frame_observer_->IsLazyLoadPending()) { |
| lazy_load_frame_observer_->LoadImmediately(); |
| } |
| } |
| } else { |
| HTMLElement::ParseAttribute(params); |
| } |
| } |
| |
| bool HTMLFrameOwnerElement::IsEligibleForLazyEmbeds(const KURL& url) const { |
| #if DCHECK_IS_ON() |
| if (base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbeds)) { |
| DCHECK(base::FeatureList::IsEnabled( |
| features::kAutomaticLazyFrameLoadingToEmbedUrls)) |
| << "kAutomaticLazyFrameLoadingToEmbedUrls should be enabled when " |
| "kAutomaticLazyFrameLoadingToEmbeds is enabled."; |
| } |
| #endif // DCHECK_IS_ON() |
| |
| // LazyEmbeds targets are third-party frames. |
| // Not eligible if the frame url is a same-origin as the parent url. |
| if (AreSameOrigin(GetDocument(), url)) { |
| return false; |
| } |
| |
| DEFINE_STATIC_LOCAL( |
| features::AutomaticLazyFrameLoadingToEmbedLoadingStrategy, |
| loading_strategy, |
| (features::kAutomaticLazyFrameLoadingToEmbedLoadingStrategyParam.Get())); |
| |
| switch (loading_strategy) { |
| case features::AutomaticLazyFrameLoadingToEmbedLoadingStrategy:: |
| kAllowList: { |
| DEFINE_STATIC_LOCAL(UrlMatcher, url_matcher, |
| (UrlMatcher(base::GetFieldTrialParamValueByFeature( |
| features::kAutomaticLazyFrameLoadingToEmbedUrls, |
| "allowed_websites")))); |
| return url_matcher.Match(url); |
| } |
| case features::AutomaticLazyFrameLoadingToEmbedLoadingStrategy::kNonAds: |
| return !IsAdRelated(); |
| } |
| } |
| |
| bool HTMLFrameOwnerElement::IsAdRelated() const { |
| if (!content_frame_) |
| return false; |
| |
| return content_frame_->IsAdFrame(); |
| } |
| |
| bool HTMLFrameOwnerElement::IsEligibleForLazyAds(const KURL& url) { |
| // LazyAds targets are third-party frames. |
| // Not eligible if the frame url is a same-origin as the parent url. |
| return IsAdRelated() && !AreSameOrigin(GetDocument(), url); |
| } |
| |
| void HTMLFrameOwnerElement::MaybeSetTimeoutToStartFrameLoading( |
| const KURL& url, |
| bool is_loading_attr_lazy, |
| AutomaticLazyLoadReason auto_lazy_load_reason) { |
| // Even if the frame is ad related, respect the explicit loading="lazy" |
| // attribute and won't set a timeout if the attribute exists. |
| if (is_loading_attr_lazy) { |
| return; |
| } |
| |
| base::TimeDelta timeout_ms; |
| switch (auto_lazy_load_reason) { |
| case AutomaticLazyLoadReason::kAds: |
| timeout_ms = GetLazyAdsTimeoutMs(); |
| break; |
| // We prioritize LazyEmbeds if the frame is eligible for both reasons, at |
| // least until the LazyEmbeds experiment finishes to secure the chunk of |
| // group size. |
| case AutomaticLazyLoadReason::kBothEmbedsAndAds: |
| case AutomaticLazyLoadReason::kEmbeds: |
| timeout_ms = GetLazyEmbedsTimeoutMs(); |
| break; |
| case AutomaticLazyLoadReason::kNotEligible: |
| // If the auto lazy-load is not elibible, do nothing and return. |
| return; |
| } |
| |
| GetDocument() |
| .GetTaskRunner(TaskType::kInternalLoading) |
| ->PostDelayedTask( |
| FROM_HERE, |
| WTF::BindOnce( |
| base::IgnoreResult(&HTMLFrameOwnerElement::LoadImmediatelyIfLazy), |
| WrapWeakPersistent(this)), |
| timeout_ms); |
| } |
| |
| mojom::blink::ColorScheme HTMLFrameOwnerElement::GetColorScheme() const { |
| if (const auto* style = GetComputedStyle()) |
| return style->UsedColorScheme(); |
| return mojom::blink::ColorScheme::kLight; |
| } |
| |
| void HTMLFrameOwnerElement::SetColorScheme( |
| mojom::blink::ColorScheme color_scheme) { |
| Document* doc = contentDocument(); |
| if (doc && doc->GetFrame()) { |
| doc->WillChangeFrameOwnerProperties(MarginWidth(), MarginHeight(), |
| ScrollbarMode(), IsDisplayNone(), |
| color_scheme); |
| } |
| FrameOwnerPropertiesChanged(); |
| } |
| |
| void HTMLFrameOwnerElement::Trace(Visitor* visitor) const { |
| visitor->Trace(content_frame_); |
| visitor->Trace(embedded_content_view_); |
| visitor->Trace(lazy_load_frame_observer_); |
| HTMLElement::Trace(visitor); |
| FrameOwner::Trace(visitor); |
| } |
| |
| } // namespace blink |