diff options
Diffstat (limited to 'layout/generic/nsSubDocumentFrame.cpp')
-rw-r--r-- | layout/generic/nsSubDocumentFrame.cpp | 1454 |
1 files changed, 1454 insertions, 0 deletions
diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp new file mode 100644 index 0000000000..2f7adccbd7 --- /dev/null +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -0,0 +1,1454 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * rendering object for replaced elements that contain a document, such + * as <frame>, <iframe>, and some <object>s + */ + +#include "nsSubDocumentFrame.h" + +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/HTMLFrameElement.h" +#include "mozilla/dom/BrowserParent.h" + +#include "nsCOMPtr.h" +#include "nsGenericHTMLElement.h" +#include "nsGenericHTMLFrameElement.h" +#include "nsAttrValueInlines.h" +#include "nsIDocShell.h" +#include "nsIContentViewer.h" +#include "nsIContentInlines.h" +#include "nsPresContext.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsGkAtoms.h" +#include "nsStyleConsts.h" +#include "nsStyleStruct.h" +#include "nsStyleStructInlines.h" +#include "nsFrameSetFrame.h" +#include "nsNameSpaceManager.h" +#include "nsDisplayList.h" +#include "nsIScrollableFrame.h" +#include "nsIObjectLoadingContent.h" +#include "nsLayoutUtils.h" +#include "FrameLayerBuilder.h" +#include "nsPluginFrame.h" +#include "nsContentUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsQueryObject.h" +#include "RetainedDisplayListBuilder.h" +#include "nsObjectLoadingContent.h" + +#include "Layers.h" +#include "BasicLayers.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "mozilla/layers/WebRenderScrollData.h" +#include "mozilla/layers/RenderRootStateManager.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +static Document* GetDocumentFromView(nsView* aView) { + MOZ_ASSERT(aView, "null view"); + + nsViewManager* vm = aView->GetViewManager(); + PresShell* presShell = vm ? vm->GetPresShell() : nullptr; + return presShell ? presShell->GetDocument() : nullptr; +} + +nsSubDocumentFrame::nsSubDocumentFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsAtomicContainerFrame(aStyle, aPresContext, kClassID), + mOuterView(nullptr), + mInnerView(nullptr), + mIsInline(false), + mPostedReflowCallback(false), + mDidCreateDoc(false), + mCallingShow(false) {} + +#ifdef ACCESSIBILITY +a11y::AccType nsSubDocumentFrame::AccessibleType() { + return a11y::eOuterDocType; +} +#endif + +NS_QUERYFRAME_HEAD(nsSubDocumentFrame) + NS_QUERYFRAME_ENTRY(nsSubDocumentFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) + +class AsyncFrameInit : public Runnable { + public: + explicit AsyncFrameInit(nsIFrame* aFrame) + : mozilla::Runnable("AsyncFrameInit"), mFrame(aFrame) {} + NS_IMETHOD Run() override { + AUTO_PROFILER_LABEL("AsyncFrameInit::Run", OTHER); + if (mFrame.IsAlive()) { + static_cast<nsSubDocumentFrame*>(mFrame.GetFrame())->ShowViewer(); + } + return NS_OK; + } + + private: + WeakFrame mFrame; +}; + +static void InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent); + +static void EndSwapDocShellsForViews(nsView* aView); + +void nsSubDocumentFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + MOZ_ASSERT(aContent); + // determine if we are a <frame> or <iframe> + mIsInline = !aContent->IsHTMLElement(nsGkAtoms::frame); + + nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); + + // CreateView() creates this frame's view, stored in mOuterView. It needs to + // be created first since it's the parent of the inner view, stored in + // mInnerView. + CreateView(); + EnsureInnerView(); + + // Set the primary frame now so that nsDocumentViewer::FindContainerView + // called from within EndSwapDocShellsForViews below can find it if needed. + aContent->SetPrimaryFrame(this); + + // If we have a detached subdoc's root view on our frame loader, re-insert + // it into the view tree. This happens when we've been reframed, and + // ensures the presentation persists across reframes. If the frame element + // has changed documents however, we blow away the presentation. + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) { + nsCOMPtr<Document> oldContainerDoc; + nsIFrame* detachedFrame = + frameloader->GetDetachedSubdocFrame(getter_AddRefs(oldContainerDoc)); + frameloader->SetDetachedSubdocFrame(nullptr, nullptr); + MOZ_ASSERT(oldContainerDoc || !detachedFrame); + if (oldContainerDoc) { + nsView* detachedView = detachedFrame ? detachedFrame->GetView() : nullptr; + if (detachedView && oldContainerDoc == aContent->OwnerDoc()) { + // Restore stashed presentation. + ::InsertViewsInReverseOrder(detachedView, mInnerView); + ::EndSwapDocShellsForViews(mInnerView->GetFirstChild()); + } else { + // Presentation is for a different document, don't restore it. + frameloader->Hide(); + } + } + } + + PropagateIsUnderHiddenEmbedderElementToSubView( + PresShell()->IsUnderHiddenEmbedderElement() || + !StyleVisibility()->IsVisible()); + + nsContentUtils::AddScriptRunner(new AsyncFrameInit(this)); +} + +void nsSubDocumentFrame::PropagateIsUnderHiddenEmbedderElementToSubView( + bool aIsUnderHiddenEmbedderElement) { + if (mFrameLoader && mFrameLoader->IsRemoteFrame()) { + mFrameLoader->SendIsUnderHiddenEmbedderElement( + aIsUnderHiddenEmbedderElement); + return; + } + + if (!mInnerView) { + return; + } + + nsView* subdocView = mInnerView->GetFirstChild(); + while (subdocView) { + if (mozilla::PresShell* presShell = subdocView->GetPresShell()) { + presShell->SetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement); + } + subdocView = subdocView->GetNextSibling(); + } +} + +void nsSubDocumentFrame::ShowViewer() { + if (mCallingShow) { + return; + } + + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (!frameloader) { + return; + } + + if (!frameloader->IsRemoteFrame() && !PresContext()->IsDynamic()) { + // We let the printing code take care of loading the document and + // initializing the shell; just create the inner view for it to use. + (void)EnsureInnerView(); + } else { + AutoWeakFrame weakThis(this); + mCallingShow = true; + bool didCreateDoc = frameloader->Show(this); + if (!weakThis.IsAlive()) { + return; + } + mCallingShow = false; + mDidCreateDoc = didCreateDoc; + + if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + frameloader->UpdatePositionAndSize(this); + } + + if (!weakThis.IsAlive()) { + return; + } + InvalidateFrame(); + } +} + +nsIFrame* nsSubDocumentFrame::GetSubdocumentRootFrame() { + if (!mInnerView) return nullptr; + nsView* subdocView = mInnerView->GetFirstChild(); + return subdocView ? subdocView->GetFrame() : nullptr; +} + +mozilla::PresShell* nsSubDocumentFrame::GetSubdocumentPresShellForPainting( + uint32_t aFlags) { + if (!mInnerView) return nullptr; + + nsView* subdocView = mInnerView->GetFirstChild(); + if (!subdocView) return nullptr; + + mozilla::PresShell* presShell = nullptr; + + nsIFrame* subdocRootFrame = subdocView->GetFrame(); + if (subdocRootFrame) { + presShell = subdocRootFrame->PresShell(); + } + + // If painting is suppressed in the presshell, we try to look for a better + // presshell to use. + if (!presShell || (presShell->IsPaintingSuppressed() && + !(aFlags & IGNORE_PAINT_SUPPRESSION))) { + // During page transition mInnerView will sometimes have two children, the + // first being the new page that may not have any frame, and the second + // being the old page that will probably have a frame. + nsView* nextView = subdocView->GetNextSibling(); + nsIFrame* frame = nullptr; + if (nextView) { + frame = nextView->GetFrame(); + } + if (frame) { + mozilla::PresShell* presShellForNextView = frame->PresShell(); + if (!presShell || (presShellForNextView && + !presShellForNextView->IsPaintingSuppressed() && + StaticPrefs::layout_show_previous_page())) { + subdocView = nextView; + subdocRootFrame = frame; + presShell = presShellForNextView; + } + } + if (!presShell) { + // If we don't have a frame we use this roundabout way to get the pres + // shell. + if (!mFrameLoader) return nullptr; + nsIDocShell* docShell = mFrameLoader->GetDocShell(IgnoreErrors()); + if (!docShell) return nullptr; + presShell = docShell->GetPresShell(); + } + } + + return presShell; +} + +ScreenIntSize nsSubDocumentFrame::GetSubdocumentSize() { + if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) { + nsCOMPtr<Document> oldContainerDoc; + nsIFrame* detachedFrame = + frameloader->GetDetachedSubdocFrame(getter_AddRefs(oldContainerDoc)); + if (nsView* view = detachedFrame ? detachedFrame->GetView() : nullptr) { + nsSize size = view->GetBounds().Size(); + nsPresContext* presContext = detachedFrame->PresContext(); + return ScreenIntSize(presContext->AppUnitsToDevPixels(size.width), + presContext->AppUnitsToDevPixels(size.height)); + } + } + // Pick some default size for now. Using 10x10 because that's what the + // code used to do. + return ScreenIntSize(10, 10); + } + + nsSize docSizeAppUnits; + nsPresContext* presContext = PresContext(); + if (GetContent()->IsHTMLElement(nsGkAtoms::frame)) { + docSizeAppUnits = GetSize(); + } else { + docSizeAppUnits = GetContentRect().Size(); + } + + // Adjust subdocument size, according to 'object-fit' and the subdocument's + // intrinsic size and ratio. + docSizeAppUnits = nsLayoutUtils::ComputeObjectDestRect( + nsRect(nsPoint(), docSizeAppUnits), GetIntrinsicSize(), + GetIntrinsicRatio(), StylePosition()) + .Size(); + + return ScreenIntSize( + presContext->AppUnitsToDevPixels(docSizeAppUnits.width), + presContext->AppUnitsToDevPixels(docSizeAppUnits.height)); +} + +static void WrapBackgroundColorInOwnLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) { + nsDisplayList tempItems; + nsDisplayItem* item; + while ((item = aList->RemoveBottom()) != nullptr) { + if (item->GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + nsDisplayList tmpList; + tmpList.AppendToTop(item); + item = MakeDisplayItemWithIndex<nsDisplayOwnLayer>( + aBuilder, aFrame, /* aIndex = */ nsDisplayOwnLayer::OwnLayerForSubdoc, + &tmpList, aBuilder->CurrentActiveScrolledRoot(), + nsDisplayOwnLayerFlags::None, ScrollbarData{}, true, false); + } + if (item) { + tempItems.AppendToTop(item); + } + } + aList->AppendToTop(&tempItems); +} + +void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (!IsVisibleForPainting()) { + return; + } + + nsFrameLoader* frameLoader = FrameLoader(); + bool isRemoteFrame = frameLoader && frameLoader->IsRemoteFrame(); + + // If we are pointer-events:none then we don't need to HitTest background + bool pointerEventsNone = + StyleUI()->mPointerEvents == StylePointerEvents::None; + if (!aBuilder->IsForEventDelivery() || !pointerEventsNone) { + nsDisplayListCollection decorations(aBuilder); + DisplayBorderBackgroundOutline(aBuilder, decorations); + if (isRemoteFrame) { + // Wrap background colors of <iframe>s with remote subdocuments in their + // own layer so we generate a ColorLayer. This is helpful for optimizing + // compositing; we can skip compositing the ColorLayer when the + // remote content is opaque. + WrapBackgroundColorInOwnLayer(aBuilder, this, + decorations.BorderBackground()); + } + decorations.MoveTo(aLists); + } + + if (aBuilder->IsForEventDelivery() && pointerEventsNone) { + return; + } + + // If we're passing pointer events to children then we have to descend into + // subdocuments no matter what, to determine which parts are transparent for + // hit-testing or event regions. + bool needToDescend = aBuilder->GetDescendIntoSubdocuments(); + if (!mInnerView || !needToDescend) { + return; + } + + if (isRemoteFrame) { + // We're the subdoc for <browser remote="true"> and it has + // painted content. Display its shadow layer tree. + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this); + + aLists.Content()->AppendNewToTop<nsDisplayRemote>(aBuilder, this); + return; + } + + RefPtr<mozilla::PresShell> presShell = GetSubdocumentPresShellForPainting( + aBuilder->IsIgnoringPaintSuppression() ? IGNORE_PAINT_SUPPRESSION : 0); + + if (!presShell) { + return; + } + + if (aBuilder->IsInFilter()) { + Document* outerDoc = PresShell()->GetDocument(); + Document* innerDoc = presShell->GetDocument(); + if (outerDoc && innerDoc) { + if (!outerDoc->NodePrincipal()->Equals(innerDoc->NodePrincipal())) { + outerDoc->SetUseCounter(eUseCounter_custom_FilteredCrossOriginIFrame); + } + } + } + + nsIFrame* subdocRootFrame = presShell->GetRootFrame(); + + nsPresContext* presContext = presShell->GetPresContext(); + + int32_t parentAPD = PresContext()->AppUnitsPerDevPixel(); + int32_t subdocAPD = presContext->AppUnitsPerDevPixel(); + + nsRect visible; + nsRect dirty; + bool ignoreViewportScrolling = false; + if (subdocRootFrame) { + // get the dirty rect relative to the root frame of the subdoc + visible = aBuilder->GetVisibleRect() + GetOffsetToCrossDoc(subdocRootFrame); + dirty = aBuilder->GetDirtyRect() + GetOffsetToCrossDoc(subdocRootFrame); + // and convert into the appunits of the subdoc + visible = visible.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD); + dirty = dirty.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD); + + if (nsIScrollableFrame* rootScrollableFrame = + presShell->GetRootScrollFrameAsScrollable()) { + // Use a copy, so the rects don't get modified. + nsRect copyOfDirty = dirty; + nsRect copyOfVisible = visible; + // TODO(botond): Can we just axe this DecideScrollableLayer call? + rootScrollableFrame->DecideScrollableLayer(aBuilder, ©OfVisible, + ©OfDirty, + /* aSetBase = */ true); + + ignoreViewportScrolling = presShell->IgnoringViewportScrolling(); + } + + aBuilder->EnterPresShell(subdocRootFrame, pointerEventsNone); + aBuilder->IncrementPresShellPaintCount(presShell); + } else { + visible = aBuilder->GetVisibleRect(); + dirty = aBuilder->GetDirtyRect(); + } + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this); + + nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); + bool constructZoomItem = subdocRootFrame && parentAPD != subdocAPD; + bool needsOwnLayer = false; + if (constructZoomItem || presContext->IsRootContentDocument() || + (sf && sf->IsScrollingActive(aBuilder))) { + needsOwnLayer = true; + } + + nsDisplayList childItems; + + { + DisplayListClipState::AutoSaveRestore nestedClipState(aBuilder); + if (needsOwnLayer) { + // Clear current clip. There's no point in propagating it down, since + // the layer we will construct will be clipped by the current clip. + // In fact for nsDisplayZoom propagating it down would be incorrect since + // nsDisplayZoom changes the meaning of appunits. + nestedClipState.Clear(); + } + + // Invoke AutoBuildingDisplayList to ensure that the correct dirty rect + // is used to compute the visible rect if AddCanvasBackgroundColorItem + // creates a display item. + nsIFrame* frame = subdocRootFrame ? subdocRootFrame : this; + nsDisplayListBuilder::AutoBuildingDisplayList building(aBuilder, frame, + visible, dirty); + + if (subdocRootFrame) { + nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); + nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter( + aBuilder, + ignoreViewportScrolling && rootScrollFrame && + rootScrollFrame->GetContent() + ? nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent()) + : aBuilder->GetCurrentScrollParentId()); + + bool hasDocumentLevelListenersForApzAwareEvents = + gfxPlatform::AsyncPanZoomEnabled() && + nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell); + + aBuilder->SetAncestorHasApzAwareEventHandler( + hasDocumentLevelListenersForApzAwareEvents); + subdocRootFrame->BuildDisplayListForStackingContext(aBuilder, + &childItems); + } + + if (!aBuilder->IsForEventDelivery()) { + // If we are going to use a displayzoom below then any items we put under + // it need to have underlying frames from the subdocument. So we need to + // calculate the bounds based on which frame will be the underlying frame + // for the canvas background color item. + nsRect bounds = + GetContentRectRelativeToSelf() + aBuilder->ToReferenceFrame(this); + if (subdocRootFrame) { + bounds = bounds.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD); + } + + // If we are in print preview/page layout we want to paint the grey + // background behind the page, not the canvas color. The canvas color gets + // painted on the page itself. + if (nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) { + presShell->AddPrintPreviewBackgroundItem(aBuilder, &childItems, frame, + bounds); + } else { + // Add the canvas background color to the bottom of the list. This + // happens after we've built the list so that + // AddCanvasBackgroundColorItem can monkey with the contents if + // necessary. + AddCanvasBackgroundColorFlags flags = + AddCanvasBackgroundColorFlags::ForceDraw | + AddCanvasBackgroundColorFlags::AddForSubDocument; + presShell->AddCanvasBackgroundColorItem( + aBuilder, &childItems, frame, bounds, NS_RGBA(0, 0, 0, 0), flags); + } + } + } + + if (subdocRootFrame) { + aBuilder->LeavePresShell(subdocRootFrame, &childItems); + } + + // Generate a resolution and/or zoom item if needed. If one or both of those + // is created, we don't need to create a separate nsDisplaySubDocument. + + nsDisplayOwnLayerFlags flags = + nsDisplayOwnLayerFlags::GenerateSubdocInvalidations; + // If ignoreViewportScrolling is true then the top most layer we create here + // is going to become the scrollable layer for the root scroll frame, so we + // want to add nsDisplayOwnLayer::GENERATE_SCROLLABLE_LAYER to whatever layer + // becomes the topmost. We do this below. + if (constructZoomItem) { + nsDisplayOwnLayerFlags zoomFlags = flags; + if (ignoreViewportScrolling) { + zoomFlags |= nsDisplayOwnLayerFlags::GenerateScrollableLayer; + } + childItems.AppendNewToTop<nsDisplayZoom>(aBuilder, subdocRootFrame, this, + &childItems, subdocAPD, parentAPD, + zoomFlags); + + needsOwnLayer = false; + } + // Wrap the zoom item in the resolution item if we have both because we want + // the resolution scale applied on top of the app units per dev pixel + // conversion. + if (ignoreViewportScrolling) { + flags |= nsDisplayOwnLayerFlags::GenerateScrollableLayer; + } + + // We always want top level content documents to be in their own layer. + nsDisplaySubDocument* layerItem = MakeDisplayItem<nsDisplaySubDocument>( + aBuilder, subdocRootFrame ? subdocRootFrame : this, this, &childItems, + flags); + if (layerItem) { + childItems.AppendToTop(layerItem); + layerItem->SetShouldFlattenAway(!needsOwnLayer); + } + + if (aBuilder->IsForFrameVisibility()) { + // We don't add the childItems to the return list as we're dealing with them + // here. + presShell->RebuildApproximateFrameVisibilityDisplayList(childItems); + childItems.DeleteAll(aBuilder); + } else { + aLists.Content()->AppendToTop(&childItems); + } +} + +#ifdef DEBUG_FRAME_DUMP +void nsSubDocumentFrame::List(FILE* out, const char* aPrefix, + ListFlags aFlags) const { + nsCString str; + ListGeneric(str, aPrefix, aFlags); + fprintf_stderr(out, "%s\n", str.get()); + + if (aFlags.contains(ListFlag::TraverseSubdocumentFrames)) { + nsSubDocumentFrame* f = const_cast<nsSubDocumentFrame*>(this); + nsIFrame* subdocRootFrame = f->GetSubdocumentRootFrame(); + if (subdocRootFrame) { + nsCString pfx(aPrefix); + pfx += " "; + subdocRootFrame->List(out, pfx.get(), aFlags); + } + } +} + +nsresult nsSubDocumentFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"FrameOuter"_ns, aResult); +} +#endif + +/* virtual */ +nscoord nsSubDocumentFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_MIN_INLINE_SIZE(this, result); + + nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent); + auto olc = static_cast<nsObjectLoadingContent*>(iolc.get()); + + if (olc && olc->GetSubdocumentIntrinsicSize()) { + // The subdocument is an SVG document, so technically we should call + // SVGOuterSVGFrame::GetMinISize() on its root frame. That method always + // returns 0, though, so we can just do that & don't need to bother with + // the cross-doc communication. + result = 0; + } else { + result = GetIntrinsicISize(); + } + + return result; +} + +/* virtual */ +nscoord nsSubDocumentFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord result; + DISPLAY_PREF_INLINE_SIZE(this, result); + + // If the subdocument is an SVG document, then in theory we want to return + // the same thing that SVGOuterSVGFrame::GetPrefISize does. That method + // has some special handling of percentage values to avoid unhelpful zero + // sizing in the presence of orthogonal writing modes. We don't bother + // with that for SVG documents in <embed> and <object>, since that special + // handling doesn't look up across document boundaries anyway. + result = GetIntrinsicISize(); + + return result; +} + +/* virtual */ +IntrinsicSize nsSubDocumentFrame::GetIntrinsicSize() { + if (StyleDisplay()->IsContainSize()) { + // Intrinsic size of 'contain:size' replaced elements is 0,0. + return IntrinsicSize(0, 0); + } + + if (nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent)) { + auto olc = static_cast<nsObjectLoadingContent*>(iolc.get()); + + if (auto size = olc->GetSubdocumentIntrinsicSize()) { + // Use the intrinsic size from the child SVG document, if available. + return *size; + } + } + + if (!IsInline()) { + return {}; // <frame> elements have no useful intrinsic size. + } + + if (mContent->IsXULElement()) { + return {}; // XUL <iframe> and <browser> have no useful intrinsic size + } + + // We must be an HTML <iframe>. Return fallback size. + return IntrinsicSize(kFallbackIntrinsicSize); +} + +/* virtual */ +AspectRatio nsSubDocumentFrame::GetIntrinsicRatio() const { + // FIXME(emilio): This should probably respect contain: size and return no + // ratio in the case subDocRoot is non-null. Otherwise we do it by virtue of + // using a zero-size below and reusing GetIntrinsicSize(). + if (nsCOMPtr<nsIObjectLoadingContent> iolc = do_QueryInterface(mContent)) { + auto olc = static_cast<nsObjectLoadingContent*>(iolc.get()); + + auto ratio = olc->GetSubdocumentIntrinsicRatio(); + if (ratio && *ratio) { + // Use the intrinsic aspect ratio from the child SVG document, if + // available. + return *ratio; + } + } + + // NOTE(emilio): Even though we have an intrinsic size, we may not have an + // intrinsic ratio. For example `<iframe style="width: 100px">` should not + // shrink in the vertical axis to preserve the 300x150 ratio. + return nsAtomicContainerFrame::GetIntrinsicRatio(); +} + +/* virtual */ +LogicalSize nsSubDocumentFrame::ComputeAutoSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, ComputeSizeFlags aFlags) { + if (!IsInline()) { + return nsIFrame::ComputeAutoSize(aRenderingContext, aWM, aCBSize, + aAvailableISize, aMargin, aBorderPadding, + aFlags); + } + + const WritingMode wm = GetWritingMode(); + LogicalSize result(wm, GetIntrinsicISize(), GetIntrinsicBSize()); + return result.ConvertTo(aWM, wm); +} + +/* virtual */ +nsIFrame::SizeComputationResult nsSubDocumentFrame::ComputeSize( + gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, + nscoord aAvailableISize, const LogicalSize& aMargin, + const LogicalSize& aBorderPadding, ComputeSizeFlags aFlags) { + return {ComputeSizeWithIntrinsicDimensions( + aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(), + aCBSize, aMargin, aBorderPadding, aFlags), + AspectRatioUsage::None}; +} + +void nsSubDocumentFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsSubDocumentFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + NS_FRAME_TRACE( + NS_FRAME_TRACE_CALLS, + ("enter nsSubDocumentFrame::Reflow: maxSize=%d,%d", + aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); + + NS_ASSERTION(aReflowInput.ComputedWidth() != NS_UNCONSTRAINEDSIZE, + "Shouldn't have unconstrained stuff here " + "thanks to the rules of reflow"); + NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedHeight(), + "Shouldn't have unconstrained stuff here " + "thanks to ComputeAutoSize"); + + NS_ASSERTION(mContent->GetPrimaryFrame() == this, "Shouldn't happen"); + + // XUL <iframe> or <browser>, or HTML <iframe>, <object> or <embed> + const auto wm = aReflowInput.GetWritingMode(); + aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm)); + + // "offset" is the offset of our content area from our frame's + // top-left corner. + nsPoint offset = nsPoint(aReflowInput.ComputedPhysicalBorderPadding().left, + aReflowInput.ComputedPhysicalBorderPadding().top); + + if (mInnerView) { + const nsMargin& bp = aReflowInput.ComputedPhysicalBorderPadding(); + nsSize innerSize(aDesiredSize.Width() - bp.LeftRight(), + aDesiredSize.Height() - bp.TopBottom()); + + // Size & position the view according to 'object-fit' & 'object-position'. + nsRect destRect = nsLayoutUtils::ComputeObjectDestRect( + nsRect(offset, innerSize), GetIntrinsicSize(), GetIntrinsicRatio(), + StylePosition()); + + nsViewManager* vm = mInnerView->GetViewManager(); + vm->MoveViewTo(mInnerView, destRect.x, destRect.y); + vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), destRect.Size()), true); + } + + aDesiredSize.SetOverflowAreasToDesiredBounds(); + + FinishAndStoreOverflow(&aDesiredSize); + + if (!aPresContext->IsPaginated() && !mPostedReflowCallback) { + PresShell()->PostReflowCallback(this); + mPostedReflowCallback = true; + } + + NS_FRAME_TRACE( + NS_FRAME_TRACE_CALLS, + ("exit nsSubDocumentFrame::Reflow: size=%d,%d status=%s", + aDesiredSize.Width(), aDesiredSize.Height(), ToString(aStatus).c_str())); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +bool nsSubDocumentFrame::ReflowFinished() { + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) { + AutoWeakFrame weakFrame(this); + + frameloader->UpdatePositionAndSize(this); + + if (weakFrame.IsAlive()) { + // Make sure that we can post a reflow callback in the future. + mPostedReflowCallback = false; + } + } else { + mPostedReflowCallback = false; + } + return false; +} + +void nsSubDocumentFrame::ReflowCallbackCanceled() { + mPostedReflowCallback = false; +} + +nsresult nsSubDocumentFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + if (aNameSpaceID != kNameSpaceID_None) { + return NS_OK; + } + + // If the noResize attribute changes, dis/allow frame to be resized + if (aAttribute == nsGkAtoms::noresize) { + // Note that we're not doing content type checks, but that's ok -- if + // they'd fail we will just end up with a null framesetFrame. + if (mContent->GetParent()->IsHTMLElement(nsGkAtoms::frameset)) { + nsIFrame* parentFrame = GetParent(); + + if (parentFrame) { + // There is no interface for nsHTMLFramesetFrame so QI'ing to + // concrete class, yay! + nsHTMLFramesetFrame* framesetFrame = do_QueryFrame(parentFrame); + if (framesetFrame) { + framesetFrame->RecalculateBorderResize(); + } + } + } + } else if (aAttribute == nsGkAtoms::marginwidth || + aAttribute == nsGkAtoms::marginheight) { + // Notify the frameloader + if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) { + frameloader->MarginsChanged(); + } + } + + return NS_OK; +} + +void nsSubDocumentFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { + nsAtomicContainerFrame::DidSetComputedStyle(aOldComputedStyle); + + // If this presshell has invisible ancestors, we don't need to propagate the + // visibility style change to the subdocument since the subdocument should + // have already set the IsUnderHiddenEmbedderElement flag in + // nsSubDocumentFrame::Init. + if (PresShell()->IsUnderHiddenEmbedderElement()) { + return; + } + + const bool isVisible = StyleVisibility()->IsVisible(); + if (!aOldComputedStyle || + isVisible != aOldComputedStyle->StyleVisibility()->IsVisible()) { + PropagateIsUnderHiddenEmbedderElementToSubView(!isVisible); + } +} + +nsIFrame* NS_NewSubDocumentFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) + nsSubDocumentFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSubDocumentFrame) + +class nsHideViewer : public Runnable { + public: + nsHideViewer(nsIContent* aFrameElement, nsFrameLoader* aFrameLoader, + PresShell* aPresShell, bool aHideViewerIfFrameless) + : mozilla::Runnable("nsHideViewer"), + mFrameElement(aFrameElement), + mFrameLoader(aFrameLoader), + mPresShell(aPresShell), + mHideViewerIfFrameless(aHideViewerIfFrameless) { + NS_ASSERTION(mFrameElement, "Must have a frame element"); + NS_ASSERTION(mFrameLoader, "Must have a frame loader"); + NS_ASSERTION(mPresShell, "Must have a presshell"); + } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { + // Flush frames, to ensure any pending display:none changes are made. + // Note it can be unsafe to flush if we've destroyed the presentation + // for some other reason, like if we're shutting down. + // + // But avoid the flush if we know for sure we're away, like when we're out + // of the document already. + // + // FIXME(emilio): This could still be a perf footgun when removing lots of + // siblings where each of them cause the reframe of an ancestor which happen + // to contain a subdocument. + // + // We should find some way to avoid that! + if (!mPresShell->IsDestroying() && mFrameElement->IsInComposedDoc()) { + MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Frames); + } + + // Either the frame has been constructed by now, or it never will be, + // either way we want to clear the stashed views. + mFrameLoader->SetDetachedSubdocFrame(nullptr, nullptr); + + nsSubDocumentFrame* frame = do_QueryFrame(mFrameElement->GetPrimaryFrame()); + if ((!frame && mHideViewerIfFrameless) || mPresShell->IsDestroying()) { + // Either the frame element has no nsIFrame or the presshell is being + // destroyed. Hide the nsFrameLoader, which destroys the presentation. + mFrameLoader->Hide(); + } + return NS_OK; + } + + private: + nsCOMPtr<nsIContent> mFrameElement; + RefPtr<nsFrameLoader> mFrameLoader; + RefPtr<PresShell> mPresShell; + bool mHideViewerIfFrameless; +}; + +static nsView* BeginSwapDocShellsForViews(nsView* aSibling); + +void nsSubDocumentFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + if (mPostedReflowCallback) { + PresShell()->CancelReflowCallback(this); + mPostedReflowCallback = false; + } + + // Detach the subdocument's views and stash them in the frame loader. + // We can then reattach them if we're being reframed (for example if + // the frame has been made position:fixed). + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) { + nsView* detachedViews = + ::BeginSwapDocShellsForViews(mInnerView->GetFirstChild()); + + if (detachedViews && detachedViews->GetFrame()) { + frameloader->SetDetachedSubdocFrame(detachedViews->GetFrame(), + mContent->OwnerDoc()); + + // We call nsFrameLoader::HideViewer() in a script runner so that we can + // safely determine whether the frame is being reframed or destroyed. + nsContentUtils::AddScriptRunner(new nsHideViewer( + mContent, frameloader, PresShell(), (mDidCreateDoc || mCallingShow))); + } else { + frameloader->SetDetachedSubdocFrame(nullptr, nullptr); + if (mDidCreateDoc || mCallingShow) { + frameloader->Hide(); + } + } + } + + nsAtomicContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +nsFrameLoader* nsSubDocumentFrame::FrameLoader() const { + if (mFrameLoader) { + return mFrameLoader; + } + + if (RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(GetContent())) { + mFrameLoader = loaderOwner->GetFrameLoader(); + } + + return mFrameLoader; +} + +void nsSubDocumentFrame::ResetFrameLoader() { + mFrameLoader = nullptr; + ClearDisplayItems(); + nsContentUtils::AddScriptRunner(new AsyncFrameInit(this)); +} + +// XXX this should be called ObtainDocShell or something like that, +// to indicate that it could have side effects +nsIDocShell* nsSubDocumentFrame::GetDocShell() const { + // How can FrameLoader() return null??? + if (NS_WARN_IF(!FrameLoader())) { + return nullptr; + } + return mFrameLoader->GetDocShell(IgnoreErrors()); +} + +static void DestroyDisplayItemDataForFrames(nsIFrame* aFrame) { + FrameLayerBuilder::DestroyDisplayItemDataFor(aFrame); + + for (const auto& childList : aFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + DestroyDisplayItemDataForFrames(child); + } + } +} + +static CallState BeginSwapDocShellsForDocument(Document& aDocument) { + if (PresShell* presShell = aDocument.GetPresShell()) { + // Disable painting while the views are detached, see bug 946929. + presShell->SetNeverPainting(true); + + if (nsIFrame* rootFrame = presShell->GetRootFrame()) { + ::DestroyDisplayItemDataForFrames(rootFrame); + } + } + aDocument.EnumerateActivityObservers(nsPluginFrame::BeginSwapDocShells); + aDocument.EnumerateSubDocuments(BeginSwapDocShellsForDocument); + return CallState::Continue; +} + +static nsView* BeginSwapDocShellsForViews(nsView* aSibling) { + // Collect the removed sibling views in reverse order in 'removedViews'. + nsView* removedViews = nullptr; + while (aSibling) { + if (Document* doc = ::GetDocumentFromView(aSibling)) { + ::BeginSwapDocShellsForDocument(*doc); + } + nsView* next = aSibling->GetNextSibling(); + aSibling->GetViewManager()->RemoveChild(aSibling); + aSibling->SetNextSibling(removedViews); + removedViews = aSibling; + aSibling = next; + } + return removedViews; +} + +static void InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent) { + MOZ_ASSERT(aParent, "null view"); + MOZ_ASSERT(!aParent->GetFirstChild(), "inserting into non-empty list"); + + nsViewManager* vm = aParent->GetViewManager(); + while (aSibling) { + nsView* next = aSibling->GetNextSibling(); + aSibling->SetNextSibling(nullptr); + // true means 'after' in document order which is 'before' in view order, + // so this call prepends the child, thus reversing the siblings as we go. + vm->InsertChild(aParent, aSibling, nullptr, true); + aSibling = next; + } +} + +nsresult nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther) { + if (!aOther || !aOther->IsSubDocumentFrame()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther); + if (!mFrameLoader || !mDidCreateDoc || mCallingShow || !other->mFrameLoader || + !other->mDidCreateDoc) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + ClearDisplayItems(); + other->ClearDisplayItems(); + + if (mInnerView && other->mInnerView) { + nsView* ourSubdocViews = mInnerView->GetFirstChild(); + nsView* ourRemovedViews = ::BeginSwapDocShellsForViews(ourSubdocViews); + nsView* otherSubdocViews = other->mInnerView->GetFirstChild(); + nsView* otherRemovedViews = ::BeginSwapDocShellsForViews(otherSubdocViews); + + ::InsertViewsInReverseOrder(ourRemovedViews, other->mInnerView); + ::InsertViewsInReverseOrder(otherRemovedViews, mInnerView); + } + mFrameLoader.swap(other->mFrameLoader); + return NS_OK; +} + +static CallState EndSwapDocShellsForDocument(Document& aDocument) { + // Our docshell and view trees have been updated for the new hierarchy. + // Now also update all nsDeviceContext::mWidget to that of the + // container view in the new hierarchy. + if (nsCOMPtr<nsIDocShell> ds = aDocument.GetDocShell()) { + nsCOMPtr<nsIContentViewer> cv; + ds->GetContentViewer(getter_AddRefs(cv)); + while (cv) { + RefPtr<nsPresContext> pc = cv->GetPresContext(); + if (pc && pc->GetPresShell()) { + pc->GetPresShell()->SetNeverPainting(ds->IsInvisible()); + } + nsDeviceContext* dc = pc ? pc->DeviceContext() : nullptr; + if (dc) { + nsView* v = cv->FindContainerView(); + dc->Init(v ? v->GetNearestWidget(nullptr) : nullptr); + } + cv = cv->GetPreviousViewer(); + } + } + + aDocument.EnumerateActivityObservers(nsPluginFrame::EndSwapDocShells); + aDocument.EnumerateSubDocuments(EndSwapDocShellsForDocument); + return CallState::Continue; +} + +static void EndSwapDocShellsForViews(nsView* aSibling) { + for (; aSibling; aSibling = aSibling->GetNextSibling()) { + if (Document* doc = ::GetDocumentFromView(aSibling)) { + ::EndSwapDocShellsForDocument(*doc); + } + nsIFrame* frame = aSibling->GetFrame(); + if (frame) { + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(frame); + if (parent->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + nsIFrame::AddInPopupStateBitToDescendants(frame); + } else { + nsIFrame::RemoveInPopupStateBitFromDescendants(frame); + } + if (frame->HasInvalidFrameInSubtree()) { + while (parent && + !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT | + NS_FRAME_IS_NONDISPLAY)) { + parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT); + parent = nsLayoutUtils::GetCrossDocParentFrame(parent); + } + } + } + } +} + +void nsSubDocumentFrame::EndSwapDocShells(nsIFrame* aOther) { + nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther); + AutoWeakFrame weakThis(this); + AutoWeakFrame weakOther(aOther); + + if (mInnerView) { + ::EndSwapDocShellsForViews(mInnerView->GetFirstChild()); + } + if (other->mInnerView) { + ::EndSwapDocShellsForViews(other->mInnerView->GetFirstChild()); + } + + // Now make sure we reflow both frames, in case their contents + // determine their size. + // And repaint them, for good measure, in case there's nothing + // interesting that happens during reflow. + if (weakThis.IsAlive()) { + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange, + NS_FRAME_IS_DIRTY); + InvalidateFrameSubtree(); + PropagateIsUnderHiddenEmbedderElementToSubView( + PresShell()->IsUnderHiddenEmbedderElement() || + !StyleVisibility()->IsVisible()); + } + if (weakOther.IsAlive()) { + other->PresShell()->FrameNeedsReflow(other, IntrinsicDirty::TreeChange, + NS_FRAME_IS_DIRTY); + other->InvalidateFrameSubtree(); + other->PropagateIsUnderHiddenEmbedderElementToSubView( + other->PresShell()->IsUnderHiddenEmbedderElement() || + !other->StyleVisibility()->IsVisible()); + } +} + +void nsSubDocumentFrame::ClearDisplayItems() { + DisplayItemArray* items = GetProperty(DisplayItems()); + if (!items) { + return; + } + + nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this); + MOZ_ASSERT(displayRoot); + + RetainedDisplayListBuilder* retainedBuilder = + displayRoot->GetProperty(RetainedDisplayListBuilder::Cached()); + MOZ_ASSERT(retainedBuilder); + + for (nsDisplayItemBase* i : *items) { + if (i->GetType() == DisplayItemType::TYPE_SUBDOCUMENT) { + auto* item = static_cast<nsDisplaySubDocument*>(i); + item->GetChildren()->DeleteAll(retainedBuilder->Builder()); + item->Disown(); + break; + } + } +} + +nsView* nsSubDocumentFrame::EnsureInnerView() { + if (mInnerView) { + return mInnerView; + } + + // create, init, set the parent of the view + nsView* outerView = GetView(); + NS_ASSERTION(outerView, "Must have an outer view already"); + nsRect viewBounds(0, 0, 0, 0); // size will be fixed during reflow + + nsViewManager* viewMan = outerView->GetViewManager(); + nsView* innerView = viewMan->CreateView(viewBounds, outerView); + if (!innerView) { + NS_ERROR("Could not create inner view"); + return nullptr; + } + mInnerView = innerView; + viewMan->InsertChild(outerView, innerView, nullptr, true); + + return mInnerView; +} + +nsPoint nsSubDocumentFrame::GetExtraOffset() const { + MOZ_ASSERT(mInnerView); + return mInnerView->GetPosition(); +} + +void nsSubDocumentFrame::SubdocumentIntrinsicSizeOrRatioChanged() { + if (MOZ_UNLIKELY(HasAllStateBits(NS_FRAME_IS_DIRTY))) { + // We will be reflowed soon anyway. + return; + } + + const nsStylePosition* pos = StylePosition(); + bool dependsOnIntrinsics = + !pos->mWidth.ConvertsToLength() || !pos->mHeight.ConvertsToLength(); + + if (dependsOnIntrinsics || pos->mObjectFit != StyleObjectFit::Fill) { + auto dirtyHint = dependsOnIntrinsics ? IntrinsicDirty::StyleChange + : IntrinsicDirty::Resize; + PresShell()->FrameNeedsReflow(this, dirtyHint, NS_FRAME_IS_DIRTY); + } +} + +/** + * Gets the layer-pixel offset of aContainerFrame's content rect top-left + * from the nearest display item reference frame (which we assume will be + * inducing a ContainerLayer). + */ +static nsPoint GetContentRectLayerOffset(nsIFrame* aContainerFrame, + nsDisplayListBuilder* aBuilder) { + // Offset to the content rect in case we have borders or padding + // Note that aContainerFrame could be a reference frame itself, so + // we need to be careful here to ensure that we call ToReferenceFrame + // on aContainerFrame and not its parent. + nsPoint frameOffset = + aBuilder->ToReferenceFrame(aContainerFrame) + + aContainerFrame->GetContentRectRelativeToSelf().TopLeft(); + + return frameOffset; +} + +// Return true iff |aManager| is a "temporary layer manager". They're +// used for small software rendering tasks, like drawWindow. That's +// currently implemented by a BasicLayerManager without a backing +// widget, and hence in non-retained mode. +inline static bool IsTempLayerManager(LayerManager* aManager) { + return (LayersBackend::LAYERS_BASIC == aManager->GetBackendType() && + !static_cast<BasicLayerManager*>(aManager)->IsRetained()); +} + +nsDisplayRemote::nsDisplayRemote(nsDisplayListBuilder* aBuilder, + nsSubDocumentFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame), + mTabId{0}, + mEventRegionsOverride(EventRegionsOverride::NoOverride) { + bool frameIsPointerEventsNone = aFrame->StyleUI()->GetEffectivePointerEvents( + aFrame) == StylePointerEvents::None; + if (aBuilder->IsInsidePointerEventsNoneDoc() || frameIsPointerEventsNone) { + mEventRegionsOverride |= EventRegionsOverride::ForceEmptyHitRegion; + } + if (nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents( + aFrame->PresShell())) { + mEventRegionsOverride |= EventRegionsOverride::ForceDispatchToContent; + } + + nsFrameLoader* frameLoader = GetFrameLoader(); + MOZ_ASSERT(frameLoader && frameLoader->IsRemoteFrame()); + if (frameLoader->GetRemoteBrowser()) { + mLayersId = frameLoader->GetLayersId(); + mTabId = frameLoader->GetRemoteBrowser()->GetTabId(); + } +} + +mozilla::LayerState nsDisplayRemote::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (IsTempLayerManager(aManager)) { + return mozilla::LayerState::LAYER_NONE; + } + return mozilla::LayerState::LAYER_ACTIVE_FORCE; +} + +LayerIntSize GetFrameSize(const nsIFrame* aFrame) { + LayoutDeviceSize size = LayoutDeviceRect::FromAppUnits( + aFrame->GetContentRectRelativeToSelf().Size(), + aFrame->PresContext()->AppUnitsPerDevPixel()); + + float cumulativeResolution = aFrame->PresShell()->GetCumulativeResolution(); + return LayerIntSize::Round(size.width * cumulativeResolution, + size.height * cumulativeResolution); +} + +already_AddRefed<mozilla::layers::Layer> nsDisplayRemote::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + MOZ_ASSERT(mFrame, "Makes no sense to have a shadow tree without a frame"); + + if (IsTempLayerManager(aManager)) { + // This can happen if aManager is a "temporary" manager, or if the + // widget's layer manager changed out from under us. We need to + // FIXME handle the former case somehow, probably with an API to + // draw a manager's subtree. The latter is bad bad bad, but the the + // MOZ_ASSERT() above will flag it. Returning nullptr here will just + // cause the shadow subtree not to be rendered. + NS_WARNING("Remote iframe not rendered"); + return nullptr; + } + + if (!mLayersId.IsValid()) { + return nullptr; + } + + if (RefPtr<RemoteBrowser> remoteBrowser = + GetFrameLoader()->GetRemoteBrowser()) { + // Adjust mItemVisibleRect, which is relative to the reference frame, to be + // relative to this frame + nsRect visibleRect; + if (aContainerParameters.mItemVisibleRect) { + visibleRect = *aContainerParameters.mItemVisibleRect; + } else { + visibleRect = GetBuildingRect(); + } + visibleRect -= ToReferenceFrame(); + nsRect contentRect = Frame()->GetContentRectRelativeToSelf(); + visibleRect.IntersectRect(visibleRect, contentRect); + visibleRect -= contentRect.TopLeft(); + + // Generate an effects update notifying the browser it is visible + aBuilder->AddEffectUpdate(remoteBrowser, + EffectsInfo::VisibleWithinRect( + visibleRect, aContainerParameters.mXScale, + aContainerParameters.mYScale)); + // FrameLayerBuilder will take care of notifying the browser when it is no + // longer visible + } + + RefPtr<Layer> layer = + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this); + + if (!layer) { + layer = aManager->CreateRefLayer(); + } + if (!layer || !layer->AsRefLayer()) { + // Probably a temporary layer manager that doesn't know how to + // use ref layers. + return nullptr; + } + RefLayer* refLayer = layer->AsRefLayer(); + + nsPoint layerOffset = GetContentRectLayerOffset(Frame(), aBuilder); + nscoord auPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceIntPoint offset = + mozilla::LayoutDeviceIntPoint::FromAppUnitsToNearest(layerOffset, + auPerDevPixel); + + // We can only have an offset if we're a child of an inactive + // container, but our display item is LAYER_ACTIVE_FORCE which + // forces all layers above to be active. + MOZ_ASSERT(aContainerParameters.mOffset == nsIntPoint()); + Matrix4x4 m = Matrix4x4::Translation(offset.x, offset.y, 0.0); + // Remote content can't be repainted by us, so we multiply down + // the resolution that our container expects onto our container. + m.PostScale(aContainerParameters.mXScale, aContainerParameters.mYScale, 1.0); + refLayer->SetBaseTransform(m); + refLayer->SetEventRegionsOverride(mEventRegionsOverride); + refLayer->SetReferentId(mLayersId); + refLayer->SetRemoteDocumentSize(GetFrameSize(mFrame)); + + return layer.forget(); +} + +void nsDisplayRemote::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + nsPresContext* pc = mFrame->PresContext(); + nsFrameLoader* fl = GetFrameLoader(); + if (pc->GetPrintSettings() && fl->IsRemoteFrame()) { + // See the comment below in CreateWebRenderCommands() as for why doing this. + fl->UpdatePositionAndSize(static_cast<nsSubDocumentFrame*>(mFrame)); + } + + DrawTarget* target = aCtx->GetDrawTarget(); + if (!target->IsRecording() || mTabId == 0) { + NS_WARNING("Remote iframe not rendered"); + return; + } + + int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel(); + Rect destRect = + NSRectToSnappedRect(GetContentRect(), appUnitsPerDevPixel, *target); + target->DrawDependentSurface(mTabId, destRect); +} + +bool nsDisplayRemote::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!mLayersId.IsValid()) { + return true; + } + + nsPresContext* pc = mFrame->PresContext(); + nsFrameLoader* fl = GetFrameLoader(); + if (RefPtr<RemoteBrowser> remoteBrowser = fl->GetRemoteBrowser()) { + if (pc->GetPrintSettings()) { + // HACK(emilio): Usually we update sizing/positioning from + // ReflowFinished(). Print documents have no incremental reflow at all + // though, so we can't rely on it firing after a frame becomes remote. + // Thus, if we're painting a remote frame, update its sizing and position + // now. + // + // UpdatePositionAndSize() can cause havoc for non-remote frames but + // luckily we don't care about those, so this is fine. + fl->UpdatePositionAndSize(static_cast<nsSubDocumentFrame*>(mFrame)); + } + + // Adjust mItemVisibleRect, which is relative to the reference frame, to be + // relative to this frame + nsRect visibleRect = GetBuildingRect() - ToReferenceFrame(); + nsRect contentRect = Frame()->GetContentRectRelativeToSelf(); + visibleRect.IntersectRect(visibleRect, contentRect); + visibleRect -= contentRect.TopLeft(); + + // Generate an effects update notifying the browser it is visible + // TODO - Gather scaling factors + aDisplayListBuilder->AddEffectUpdate( + remoteBrowser, EffectsInfo::VisibleWithinRect(visibleRect, 1.0f, 1.0f)); + + // Create a WebRenderRemoteData to notify the RemoteBrowser when it is no + // longer visible + RefPtr<WebRenderRemoteData> userData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderRemoteData>(this, + nullptr); + userData->SetRemoteBrowser(remoteBrowser); + } + + nscoord auPerDevPixel = pc->AppUnitsPerDevPixel(); + nsPoint layerOffset = GetContentRectLayerOffset(mFrame, aDisplayListBuilder); + mOffset = LayoutDevicePoint::FromAppUnits(layerOffset, auPerDevPixel); + + nsRect contentRect = mFrame->GetContentRectRelativeToSelf(); + contentRect.MoveTo(0, 0); + LayoutDeviceRect rect = + LayoutDeviceRect::FromAppUnits(contentRect, auPerDevPixel); + rect += mOffset; + + aBuilder.PushIFrame(mozilla::wr::ToLayoutRect(rect), !BackfaceIsHidden(), + mozilla::wr::AsPipelineId(mLayersId), + /*ignoreMissingPipelines*/ true); + + return true; +} + +bool nsDisplayRemote::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (!mLayersId.IsValid()) { + return true; + } + + if (aLayerData) { + aLayerData->SetReferentId(mLayersId); + Matrix4x4 m = Matrix4x4::Translation(mOffset.x, mOffset.y, 0.0); + + // Apply the top level resolution if we are in the same process of the top + // level document. We don't need to apply it in cases where we are in OOP + // iframes since it will be applied later in + // HitTestingTreeNode::GetTransformToGecko by walking up the tree node. + nsPresContext* inProcessRootContext = + mFrame->PresContext()->GetInProcessRootContentDocumentPresContext(); + if (inProcessRootContext && + inProcessRootContext->IsRootContentDocumentCrossProcess()) { + float resolution = inProcessRootContext->PresShell()->GetResolution(); + m.PostScale(resolution, resolution, 1.0); + } + + aLayerData->SetTransform(m); + aLayerData->SetEventRegionsOverride(mEventRegionsOverride); + aLayerData->SetRemoteDocumentSize(GetFrameSize(mFrame)); + } + return true; +} + +nsFrameLoader* nsDisplayRemote::GetFrameLoader() const { + return mFrame ? static_cast<nsSubDocumentFrame*>(mFrame)->FrameLoader() + : nullptr; +} |