diff options
Diffstat (limited to 'layout/generic/nsSubDocumentFrame.cpp')
-rw-r--r-- | layout/generic/nsSubDocumentFrame.cpp | 1263 |
1 files changed, 1263 insertions, 0 deletions
diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp new file mode 100644 index 000000000..47026b73c --- /dev/null +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -0,0 +1,1263 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "gfxPrefs.h" + +#include "mozilla/layout/RenderFrameParent.h" + +#include "nsCOMPtr.h" +#include "nsGenericHTMLElement.h" +#include "nsGenericHTMLFrameElement.h" +#include "nsAttrValueInlines.h" +#include "nsIDocShell.h" +#include "nsIContentViewer.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsIDocument.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsGkAtoms.h" +#include "nsStyleConsts.h" +#include "nsFrameSetFrame.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIScrollable.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 "nsIPermissionManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIDOMMutationEvent.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; +using mozilla::layout::RenderFrameParent; + +static bool sShowPreviousPage = true; + +static nsIDocument* +GetDocumentFromView(nsView* aView) +{ + NS_PRECONDITION(aView, ""); + + nsViewManager* vm = aView->GetViewManager(); + nsIPresShell* ps = vm ? vm->GetPresShell() : nullptr; + return ps ? ps->GetDocument() : nullptr; +} + +nsSubDocumentFrame::nsSubDocumentFrame(nsStyleContext* aContext) + : nsAtomicContainerFrame(aContext) + , 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) : mFrame(aFrame) {} + NS_IMETHOD Run() override + { + PROFILER_LABEL("mozilla", "AsyncFrameInit::Run", js::ProfileEntry::Category::OTHER); + if (mFrame.IsAlive()) { + static_cast<nsSubDocumentFrame*>(mFrame.GetFrame())->ShowViewer(); + } + return NS_OK; + } +private: + nsWeakFrame mFrame; +}; + +static void +InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent); + +static void +EndSwapDocShellsForViews(nsView* aView); + +void +nsSubDocumentFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + // determine if we are a <frame> or <iframe> + nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = do_QueryInterface(aContent); + mIsInline = frameElem ? false : true; + + static bool addedShowPreviousPage = false; + if (!addedShowPreviousPage) { + Preferences::AddBoolVarCache(&sShowPreviousPage, "layout.show_previous_page", true); + addedShowPreviousPage = true; + } + + nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); + + // We are going to create an inner view. If we need a view for the + // OuterFrame but we wait for the normal view creation path in + // nsCSSFrameConstructor, then we will lose because the inner view's + // parent will already have been set to some outer view (e.g., the + // canvas) when it really needs to have this frame's view as its + // parent. So, create this frame's view right away, whether we + // really need it or not, and the inner view will get it as the + // parent. + if (!HasView()) { + nsContainerFrame::CreateViewForFrame(this, true); + } + 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<nsIDocument> 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(); + } + } + } + + nsContentUtils::AddScriptRunner(new AsyncFrameInit(this)); +} + +void +nsSubDocumentFrame::ShowViewer() +{ + if (mCallingShow) { + return; + } + + if (!PresContext()->IsDynamic()) { + // We let the printing code take care of loading the document; just + // create the inner view for it to use. + (void) EnsureInnerView(); + } else { + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) { + CSSIntSize margin = GetMarginAttributes(); + nsWeakFrame weakThis(this); + mCallingShow = true; + const nsAttrValue* attrValue = + GetContent()->AsElement()->GetParsedAttr(nsGkAtoms::scrolling); + int32_t scrolling = + nsGenericHTMLFrameElement::MapScrollingAttribute(attrValue); + bool didCreateDoc = + frameloader->Show(margin.width, margin.height, + scrolling, scrolling, this); + if (!weakThis.IsAlive()) { + return; + } + mCallingShow = false; + mDidCreateDoc = didCreateDoc; + } + } +} + +nsIFrame* +nsSubDocumentFrame::GetSubdocumentRootFrame() +{ + if (!mInnerView) + return nullptr; + nsView* subdocView = mInnerView->GetFirstChild(); + return subdocView ? subdocView->GetFrame() : nullptr; +} + +nsIPresShell* +nsSubDocumentFrame::GetSubdocumentPresShellForPainting(uint32_t aFlags) +{ + if (!mInnerView) + return nullptr; + + nsView* subdocView = mInnerView->GetFirstChild(); + if (!subdocView) + return nullptr; + + nsIPresShell* presShell = nullptr; + + nsIFrame* subdocRootFrame = subdocView->GetFrame(); + if (subdocRootFrame) { + presShell = subdocRootFrame->PresContext()->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) { + nsIPresShell* ps = frame->PresContext()->PresShell(); + if (!presShell || (ps && !ps->IsPaintingSuppressed() && sShowPreviousPage)) { + subdocView = nextView; + subdocRootFrame = frame; + presShell = ps; + } + } + if (!presShell) { + // If we don't have a frame we use this roundabout way to get the pres shell. + if (!mFrameLoader) + return nullptr; + nsCOMPtr<nsIDocShell> docShell; + mFrameLoader->GetDocShell(getter_AddRefs(docShell)); + if (!docShell) + return nullptr; + presShell = docShell->GetPresShell(); + } + } + + return presShell; +} + + + + +ScreenIntSize +nsSubDocumentFrame::GetSubdocumentSize() +{ + if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) { + nsCOMPtr<nsIDocument> oldContainerDoc; + nsIFrame* detachedFrame = + frameloader->GetDetachedSubdocFrame(getter_AddRefs(oldContainerDoc)); + nsView* view = detachedFrame ? detachedFrame->GetView() : nullptr; + if (view) { + 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); + } else { + nsSize docSizeAppUnits; + nsPresContext* presContext = PresContext(); + nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = + do_QueryInterface(GetContent()); + if (frameElem) { + docSizeAppUnits = GetSize(); + } else { + docSizeAppUnits = GetContentRect().Size(); + } + // Adjust subdocument size, according to 'object-fit' and the + // subdocument's intrinsic size and ratio. + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + nsRect destRect = + nsLayoutUtils::ComputeObjectDestRect(nsRect(nsPoint(), docSizeAppUnits), + subDocRoot->GetIntrinsicSize(), + subDocRoot->GetIntrinsicRatio(), + StylePosition()); + docSizeAppUnits = destRect.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() == nsDisplayItem::TYPE_BACKGROUND_COLOR) { + nsDisplayList tmpList; + tmpList.AppendToTop(item); + item = new (aBuilder) nsDisplayOwnLayer(aBuilder, aFrame, &tmpList); + } + tempItems.AppendToTop(item); + } + aList->AppendToTop(&tempItems); +} + +void +nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return; + + nsFrameLoader* frameLoader = FrameLoader(); + RenderFrameParent* rfp = nullptr; + if (frameLoader) { + rfp = frameLoader->GetCurrentRenderFrame(); + } + + // If we are pointer-events:none then we don't need to HitTest background + bool pointerEventsNone = + StyleUserInterface()->mPointerEvents == NS_STYLE_POINTER_EVENTS_NONE; + if (!aBuilder->IsForEventDelivery() || !pointerEventsNone) { + nsDisplayListCollection decorations; + DisplayBorderBackgroundOutline(aBuilder, decorations); + if (rfp) { + // 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 (rfp) { + rfp->BuildDisplayList(aBuilder, this, aDirtyRect, aLists); + return; + } + + nsCOMPtr<nsIPresShell> presShell = + GetSubdocumentPresShellForPainting( + aBuilder->IsIgnoringPaintSuppression() ? IGNORE_PAINT_SUPPRESSION : 0); + + if (!presShell) { + return; + } + + nsIFrame* subdocRootFrame = presShell->GetRootFrame(); + + nsPresContext* presContext = presShell->GetPresContext(); + + int32_t parentAPD = PresContext()->AppUnitsPerDevPixel(); + int32_t subdocAPD = presContext->AppUnitsPerDevPixel(); + + nsRect dirty; + bool haveDisplayPort = false; + bool ignoreViewportScrolling = false; + nsIFrame* savedIgnoreScrollFrame = nullptr; + if (subdocRootFrame) { + // get the dirty rect relative to the root frame of the subdoc + dirty = aDirtyRect + GetOffsetToCrossDoc(subdocRootFrame); + // and convert into the appunits of the subdoc + dirty = dirty.ScaleToOtherAppUnitsRoundOut(parentAPD, subdocAPD); + + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); + MOZ_ASSERT(rootScrollableFrame); + // Use a copy, so the dirty rect doesn't get modified to the display port. + nsRect copy = dirty; + haveDisplayPort = rootScrollableFrame->DecideScrollableLayer(aBuilder, + ©, /* aAllowCreateDisplayPort = */ true); + if (!gfxPrefs::LayoutUseContainersForRootFrames()) { + haveDisplayPort = false; + } + + ignoreViewportScrolling = presShell->IgnoringViewportScrolling(); + if (ignoreViewportScrolling) { + savedIgnoreScrollFrame = aBuilder->GetIgnoreScrollFrame(); + aBuilder->SetIgnoreScrollFrame(rootScrollFrame); + } + } + + aBuilder->EnterPresShell(subdocRootFrame, pointerEventsNone); + } else { + dirty = aDirtyRect; + } + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + if (ShouldClipSubdocument()) { + clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this); + } + + nsIScrollableFrame *sf = presShell->GetRootScrollFrameAsScrollable(); + bool constructResolutionItem = subdocRootFrame && + (presShell->GetResolution() != 1.0); + bool constructZoomItem = subdocRootFrame && parentAPD != subdocAPD; + bool needsOwnLayer = false; + if (constructResolutionItem || + constructZoomItem || + haveDisplayPort || + presContext->IsRootContentDocument() || + (sf && sf->IsScrollingActive(aBuilder))) + { + needsOwnLayer = true; + } + if (!needsOwnLayer && aBuilder->IsBuildingLayerEventRegions() && + nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell)) + { + 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.EnterStackingContextContents(true); + } + + if (subdocRootFrame) { + nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); + nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter( + aBuilder, + ignoreViewportScrolling && rootScrollFrame && rootScrollFrame->GetContent() + ? nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent()) + : aBuilder->GetCurrentScrollParentId()); + + aBuilder->SetAncestorHasApzAwareEventHandler(false); + subdocRootFrame-> + BuildDisplayListForStackingContext(aBuilder, dirty, &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, subdocRootFrame ? subdocRootFrame : this, + bounds); + } else { + // 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, dirty, true); + // 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. + uint32_t flags = nsIPresShell::FORCE_DRAW; + presShell->AddCanvasBackgroundColorItem( + *aBuilder, childItems, frame, bounds, NS_RGBA(0,0,0,0), flags); + } + } + } + + if (subdocRootFrame) { + aBuilder->LeavePresShell(subdocRootFrame, &childItems); + + if (ignoreViewportScrolling) { + aBuilder->SetIgnoreScrollFrame(savedIgnoreScrollFrame); + } + } + + // 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. + + uint32_t flags = nsDisplayOwnLayer::GENERATE_SUBDOC_INVALIDATIONS; + // 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) { + uint32_t zoomFlags = flags; + if (ignoreViewportScrolling && !constructResolutionItem) { + zoomFlags |= nsDisplayOwnLayer::GENERATE_SCROLLABLE_LAYER; + } + nsDisplayZoom* zoomItem = + new (aBuilder) nsDisplayZoom(aBuilder, subdocRootFrame, &childItems, + subdocAPD, parentAPD, zoomFlags); + childItems.AppendToTop(zoomItem); + 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 |= nsDisplayOwnLayer::GENERATE_SCROLLABLE_LAYER; + } + if (constructResolutionItem) { + nsDisplayResolution* resolutionItem = + new (aBuilder) nsDisplayResolution(aBuilder, subdocRootFrame, &childItems, + flags); + childItems.AppendToTop(resolutionItem); + needsOwnLayer = false; + } + if (needsOwnLayer) { + // We always want top level content documents to be in their own layer. + nsDisplaySubDocument* layerItem = new (aBuilder) nsDisplaySubDocument( + aBuilder, subdocRootFrame ? subdocRootFrame : this, + &childItems, flags); + childItems.AppendToTop(layerItem); + } + + 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(); + } else { + aLists.Content()->AppendToTop(&childItems); + } +} + +nscoord +nsSubDocumentFrame::GetIntrinsicISize() +{ + if (!IsInline()) { + return 0; // HTML <frame> has no useful intrinsic isize + } + + if (mContent->IsXULElement()) { + return 0; // XUL <iframe> and <browser> have no useful intrinsic isize + } + + NS_ASSERTION(ObtainIntrinsicSizeFrame() == nullptr, + "Intrinsic isize should come from the embedded document."); + + // We must be an HTML <iframe>. Default to size of 300px x 150px, for IE + // compat (and per CSS2.1 draft). + WritingMode wm = GetWritingMode(); + return nsPresContext::CSSPixelsToAppUnits(wm.IsVertical() ? 150 : 300); +} + +nscoord +nsSubDocumentFrame::GetIntrinsicBSize() +{ + // <frame> processing does not use this routine, only <iframe> + NS_ASSERTION(IsInline(), "Shouldn't have been called"); + + if (mContent->IsXULElement()) { + return 0; + } + + NS_ASSERTION(ObtainIntrinsicSizeFrame() == nullptr, + "Intrinsic bsize should come from the embedded document."); + + // Use size of 300px x 150px, for compatibility with IE, and per CSS2.1 draft. + WritingMode wm = GetWritingMode(); + return nsPresContext::CSSPixelsToAppUnits(wm.IsVertical() ? 300 : 150); +} + +#ifdef DEBUG_FRAME_DUMP +void +nsSubDocumentFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const +{ + nsCString str; + ListGeneric(str, aPrefix, aFlags); + fprintf_stderr(out, "%s\n", str.get()); + + if (aFlags & TRAVERSE_SUBDOCUMENT_FRAMES) { + 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(NS_LITERAL_STRING("FrameOuter"), aResult); +} +#endif + +nsIAtom* +nsSubDocumentFrame::GetType() const +{ + return nsGkAtoms::subDocumentFrame; +} + +/* virtual */ nscoord +nsSubDocumentFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + result = subDocRoot->GetMinISize(aRenderingContext); + } else { + result = GetIntrinsicISize(); + } + + return result; +} + +/* virtual */ nscoord +nsSubDocumentFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + result = subDocRoot->GetPrefISize(aRenderingContext); + } else { + result = GetIntrinsicISize(); + } + + return result; +} + +/* virtual */ IntrinsicSize +nsSubDocumentFrame::GetIntrinsicSize() +{ + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + return subDocRoot->GetIntrinsicSize(); + } + return nsAtomicContainerFrame::GetIntrinsicSize(); +} + +/* virtual */ nsSize +nsSubDocumentFrame::GetIntrinsicRatio() +{ + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + return subDocRoot->GetIntrinsicRatio(); + } + return nsAtomicContainerFrame::GetIntrinsicRatio(); +} + +/* virtual */ +LogicalSize +nsSubDocumentFrame::ComputeAutoSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + if (!IsInline()) { + return nsFrame::ComputeAutoSize(aRenderingContext, aWM, aCBSize, + aAvailableISize, aMargin, aBorder, + aPadding, aFlags); + } + + const WritingMode wm = GetWritingMode(); + LogicalSize result(wm, GetIntrinsicISize(), GetIntrinsicBSize()); + return result.ConvertTo(aWM, wm); +} + + +/* virtual */ +LogicalSize +nsSubDocumentFrame::ComputeSize(nsRenderingContext* aRenderingContext, + WritingMode aWM, + const LogicalSize& aCBSize, + nscoord aAvailableISize, + const LogicalSize& aMargin, + const LogicalSize& aBorder, + const LogicalSize& aPadding, + ComputeSizeFlags aFlags) +{ + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, + subDocRoot->GetIntrinsicSize(), + subDocRoot->GetIntrinsicRatio(), + aCBSize, aMargin, aBorder, + aPadding, aFlags); + } + return nsAtomicContainerFrame::ComputeSize(aRenderingContext, aWM, + aCBSize, aAvailableISize, + aMargin, aBorder, aPadding, + aFlags); +} + +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); + 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_INTRINSICSIZE != aReflowInput.ComputedHeight(), + "Shouldn't have unconstrained stuff here " + "thanks to ComputeAutoSize"); + + aStatus = NS_FRAME_COMPLETE; + + NS_ASSERTION(mContent->GetPrimaryFrame() == this, + "Shouldn't happen"); + + // XUL <iframe> or <browser>, or HTML <iframe>, <object> or <embed> + aDesiredSize.SetSize(aReflowInput.GetWritingMode(), + aReflowInput.ComputedSizeWithBorderPadding()); + + // "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'. + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + IntrinsicSize intrinsSize; + nsSize intrinsRatio; + if (subDocRoot) { + intrinsSize = subDocRoot->GetIntrinsicSize(); + intrinsRatio = subDocRoot->GetIntrinsicRatio(); + } + nsRect destRect = + nsLayoutUtils::ComputeObjectDestRect(nsRect(offset, innerSize), + intrinsSize, intrinsRatio, + StylePosition()); + + nsViewManager* vm = mInnerView->GetViewManager(); + vm->MoveViewTo(mInnerView, destRect.x, destRect.y); + vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), destRect.Size()), true); + } + + aDesiredSize.SetOverflowAreasToDesiredBounds(); + if (!ShouldClipSubdocument()) { + nsIFrame* subdocRootFrame = GetSubdocumentRootFrame(); + if (subdocRootFrame) { + aDesiredSize.mOverflowAreas.UnionWith(subdocRootFrame->GetOverflowAreas() + offset); + } + } + + FinishAndStoreOverflow(&aDesiredSize); + + if (!aPresContext->IsPaginated() && !mPostedReflowCallback) { + PresContext()->PresShell()->PostReflowCallback(this); + mPostedReflowCallback = true; + } + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit nsSubDocumentFrame::Reflow: size=%d,%d status=%x", + aDesiredSize.Width(), aDesiredSize.Height(), aStatus)); + + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +bool +nsSubDocumentFrame::ReflowFinished() +{ + if (mFrameLoader) { + nsWeakFrame weakFrame(this); + + mFrameLoader->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, + nsIAtom* 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::showresizer) { + nsIFrame* rootFrame = GetSubdocumentRootFrame(); + if (rootFrame) { + rootFrame->PresContext()->PresShell()-> + FrameNeedsReflow(rootFrame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); + } + } + else if (aAttribute == nsGkAtoms::marginwidth || + aAttribute == nsGkAtoms::marginheight) { + + // Retrieve the attributes + CSSIntSize margins = GetMarginAttributes(); + + // Notify the frameloader + RefPtr<nsFrameLoader> frameloader = FrameLoader(); + if (frameloader) + frameloader->MarginsChanged(margins.width, margins.height); + } + + return NS_OK; +} + +nsIFrame* +NS_NewSubDocumentFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsSubDocumentFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSubDocumentFrame) + +class nsHideViewer : public Runnable { +public: + nsHideViewer(nsIContent* aFrameElement, + nsFrameLoader* aFrameLoader, + nsIPresShell* aPresShell, + bool aHideViewerIfFrameless) + : 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"); + } + + 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. + if (!mPresShell->IsDestroying()) { + mPresShell->FlushPendingNotifications(Flush_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; + nsCOMPtr<nsIPresShell> mPresShell; + bool mHideViewerIfFrameless; +}; + +static nsView* +BeginSwapDocShellsForViews(nsView* aSibling); + +void +nsSubDocumentFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + if (mPostedReflowCallback) { + PresContext()->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()) { + MOZ_ASSERT(mContent->OwnerDoc()); + 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, + PresContext()->PresShell(), + (mDidCreateDoc || mCallingShow))); + } else { + frameloader->SetDetachedSubdocFrame(nullptr, nullptr); + if (mDidCreateDoc || mCallingShow) { + frameloader->Hide(); + } + } + } + + nsAtomicContainerFrame::DestroyFrom(aDestructRoot); +} + +CSSIntSize +nsSubDocumentFrame::GetMarginAttributes() +{ + CSSIntSize result(-1, -1); + nsGenericHTMLElement *content = nsGenericHTMLElement::FromContent(mContent); + if (content) { + const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::marginwidth); + if (attr && attr->Type() == nsAttrValue::eInteger) + result.width = attr->GetIntegerValue(); + attr = content->GetParsedAttr(nsGkAtoms::marginheight); + if (attr && attr->Type() == nsAttrValue::eInteger) + result.height = attr->GetIntegerValue(); + } + return result; +} + +nsFrameLoader* +nsSubDocumentFrame::FrameLoader() +{ + nsIContent* content = GetContent(); + if (!content) + return nullptr; + + if (!mFrameLoader) { + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(content); + if (loaderOwner) { + mFrameLoader = loaderOwner->GetFrameLoader(); + } + } + return mFrameLoader; +} + +// XXX this should be called ObtainDocShell or something like that, +// to indicate that it could have side effects +nsresult +nsSubDocumentFrame::GetDocShell(nsIDocShell **aDocShell) +{ + *aDocShell = nullptr; + + NS_ENSURE_STATE(FrameLoader()); + return mFrameLoader->GetDocShell(aDocShell); +} + +static void +DestroyDisplayItemDataForFrames(nsIFrame* aFrame) +{ + FrameLayerBuilder::DestroyDisplayItemDataFor(aFrame); + + nsIFrame::ChildListIterator lists(aFrame); + for (; !lists.IsDone(); lists.Next()) { + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + DestroyDisplayItemDataForFrames(childFrames.get()); + } + } +} + +static bool +BeginSwapDocShellsForDocument(nsIDocument* aDocument, void*) +{ + NS_PRECONDITION(aDocument, ""); + + nsIPresShell* shell = aDocument->GetShell(); + if (shell) { + // Disable painting while the views are detached, see bug 946929. + shell->SetNeverPainting(true); + + nsIFrame* rootFrame = shell->GetRootFrame(); + if (rootFrame) { + ::DestroyDisplayItemDataForFrames(rootFrame); + } + } + aDocument->EnumerateActivityObservers( + nsPluginFrame::BeginSwapDocShells, nullptr); + aDocument->EnumerateSubDocuments(BeginSwapDocShellsForDocument, nullptr); + return true; +} + +static nsView* +BeginSwapDocShellsForViews(nsView* aSibling) +{ + // Collect the removed sibling views in reverse order in 'removedViews'. + nsView* removedViews = nullptr; + while (aSibling) { + nsIDocument* doc = ::GetDocumentFromView(aSibling); + if (doc) { + ::BeginSwapDocShellsForDocument(doc, nullptr); + } + nsView* next = aSibling->GetNextSibling(); + aSibling->GetViewManager()->RemoveChild(aSibling); + aSibling->SetNextSibling(removedViews); + removedViews = aSibling; + aSibling = next; + } + return removedViews; +} + +static void +InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent) +{ + NS_PRECONDITION(aParent, ""); + NS_PRECONDITION(!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->GetType() != nsGkAtoms::subDocumentFrame) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther); + if (!mFrameLoader || !mDidCreateDoc || mCallingShow || + !other->mFrameLoader || !other->mDidCreateDoc) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + 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 bool +EndSwapDocShellsForDocument(nsIDocument* aDocument, void*) +{ + NS_PRECONDITION(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. + nsCOMPtr<nsIDocShell> ds = aDocument->GetDocShell(); + if (ds) { + nsCOMPtr<nsIContentViewer> cv; + ds->GetContentViewer(getter_AddRefs(cv)); + while (cv) { + RefPtr<nsPresContext> pc; + cv->GetPresContext(getter_AddRefs(pc)); + 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); + } + nsCOMPtr<nsIContentViewer> prev; + cv->GetPreviousViewer(getter_AddRefs(prev)); + cv = prev; + } + } + + aDocument->EnumerateActivityObservers( + nsPluginFrame::EndSwapDocShells, nullptr); + aDocument->EnumerateSubDocuments(EndSwapDocShellsForDocument, nullptr); + return true; +} + +static void +EndSwapDocShellsForViews(nsView* aSibling) +{ + for ( ; aSibling; aSibling = aSibling->GetNextSibling()) { + nsIDocument* doc = ::GetDocumentFromView(aSibling); + if (doc) { + ::EndSwapDocShellsForDocument(doc, nullptr); + } + 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); + nsWeakFrame weakThis(this); + nsWeakFrame 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()) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); + InvalidateFrameSubtree(); + } + if (weakOther.IsAlive()) { + other->PresContext()->PresShell()-> + FrameNeedsReflow(other, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY); + other->InvalidateFrameSubtree(); + } +} + +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; +} + +nsIFrame* +nsSubDocumentFrame::ObtainIntrinsicSizeFrame() +{ + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(GetContent()); + if (olc) { + // We are an HTML <object>, <embed> or <applet> (a replaced element). + + // Try to get an nsIFrame for our sub-document's document element + nsIFrame* subDocRoot = nullptr; + + nsCOMPtr<nsIDocShell> docShell; + GetDocShell(getter_AddRefs(docShell)); + if (docShell) { + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (presShell) { + nsIScrollableFrame* scrollable = presShell->GetRootScrollFrameAsScrollable(); + if (scrollable) { + nsIFrame* scrolled = scrollable->GetScrolledFrame(); + if (scrolled) { + subDocRoot = scrolled->PrincipalChildList().FirstChild(); + } + } + } + } + + if (subDocRoot && subDocRoot->GetContent() && + subDocRoot->GetContent()->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) { + return subDocRoot; // SVG documents have an intrinsic size + } + } + return nullptr; +} |