/* -*- 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/. */
ScreenMargin DisplayPortMargins::GetRelativeToLayoutViewport(
ContentGeometryType aGeometryType,
ScrollContainerFrame* aScrollContainerFrame, const CSSToScreenScale2D& aDisplayportScale) const { // APZ wants |mMargins| applied relative to the visual viewport. // The main-thread painting code applies margins relative to // the layout viewport. To get the main thread to paint the // area APZ wants, apply a translation between the two. The // magnitude of the translation depends on whether we are // applying the displayport to scrolled or fixed content.
CSSPoint scrollDeltaCss =
ComputeAsyncTranslation(aGeometryType, aScrollContainerFrame);
ScreenPoint scrollDelta = scrollDeltaCss * aDisplayportScale;
ScreenMargin margins = mMargins;
margins.left -= scrollDelta.x;
margins.right += scrollDelta.x;
margins.top -= scrollDelta.y;
margins.bottom += scrollDelta.y; return margins;
}
CSSPoint DisplayPortMargins::ComputeAsyncTranslation(
ContentGeometryType aGeometryType,
ScrollContainerFrame* aScrollContainerFrame) const { // If we are applying the displayport to scrolled content, the // translation is the entire difference between the visual and // layout offsets. if (aGeometryType == ContentGeometryType::Scrolled) { return mVisualOffset - mLayoutOffset;
}
// If we are applying the displayport to fixed content, only // part of the difference between the visual and layout offsets // should be applied. This is because fixed content remains fixed // to the layout viewport, and some of the async delta between // the visual and layout offsets can drag the layout viewport // with it. We want only the remaining delta, i.e. the offset of // the visual viewport relative to the (async-scrolled) layout // viewport. if (!aScrollContainerFrame) { // Displayport on a non-scrolling frame for some reason. // There will be no divergence between the two viewports. return CSSPoint();
} // Fixed content is always fixed to an RSF.
MOZ_ASSERT(aScrollContainerFrame->IsRootScrollFrameOfDocument()); if (!aScrollContainerFrame->PresShell()->IsVisualViewportSizeSet()) { // Zooming is disabled, so the layout viewport tracks the // visual viewport completely. return CSSPoint();
} // Use KeepLayoutViewportEnclosingViewportVisual() to compute // an async layout viewport the way APZ would. const CSSRect visualViewport{
mVisualOffset, // TODO: There are probably some edge cases here around async zooming // that are not currently being handled properly. For proper handling, // we'd likely need to save APZ's async zoom when populating // mVisualOffset, and using it to adjust the visual viewport size here. // Note that any incorrectness caused by this will only occur transiently // during async zooming.
CSSSize::FromAppUnits(
aScrollContainerFrame->PresShell()->GetVisualViewportSize())}; const CSSRect scrollableRect = CSSRect::FromAppUnits(
nsLayoutUtils::CalculateExpandedScrollableRect(aScrollContainerFrame));
CSSRect asyncLayoutViewport{
mLayoutOffset,
CSSSize::FromAppUnits(aScrollContainerFrame->GetScrollPortRect().Size())};
FrameMetrics::KeepLayoutViewportEnclosingVisualViewport(
visualViewport, scrollableRect, /* out */ asyncLayoutViewport); return mVisualOffset - asyncLayoutViewport.TopLeft();
}
static nsRect GetDisplayPortFromRectData(nsIContent* aContent,
DisplayPortPropertyData* aRectData) { // In the case where the displayport is set as a rect, we assume it is // already aligned and clamped as necessary. The burden to do that is // on the setter of the displayport. In practice very few places set the // displayport directly as a rect (mostly tests). return aRectData->mRect;
}
static nsRect GetDisplayPortFromMarginsData(
nsIContent* aContent, DisplayPortMarginsPropertyData* aMarginsData, const DisplayPortOptions& aOptions) { // In the case where the displayport is set via margins, we apply the margins // to a base rect. Then we align the expanded rect based on the alignment // requested, and finally, clamp it to the size of the scrollable rect.
nsRect base; if (nsRect* baseData = static_cast<nsRect*>(
aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
base = *baseData;
} else { // In theory we shouldn't get here, but we do sometimes (see bug 1212136). // Fall through for graceful handling.
}
nsIFrame* frame = nsLayoutUtils::GetScrollContainerFrameFromContent(aContent); if (!frame) { // Turns out we can't really compute it. Oops. We still should return // something sane.
NS_WARNING( "Attempting to get a displayport from a content with no primary " "frame!"); return base;
}
LayoutDeviceToScreenScale2D res =
LayoutDeviceToParentLayerScale(
presContext->PresShell()->GetCumulativeResolution()) *
nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
frame);
// Calculate the expanded scrollable rect, which we'll be clamping the // displayport to.
nsRect expandedScrollableRect =
nsLayoutUtils::CalculateExpandedScrollableRect(frame);
// GetTransformToAncestorScale() can return 0. In this case, just return the // base rect (clamped to the expanded scrollable rect), as other calculations // would run into divisions by zero. if (res == LayoutDeviceToScreenScale2D(0, 0)) { // Make sure the displayport remains within the scrollable rect. return base.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
}
// First convert the base rect to screen pixels
LayoutDeviceToScreenScale2D parentRes = res; if (isRoot) { // the base rect for root scroll frames is specified in the parent document // coordinate space, so it doesn't include the local resolution. float localRes = presContext->PresShell()->GetResolution();
parentRes.xScale /= localRes;
parentRes.yScale /= localRes;
}
ScreenRect screenRect =
LayoutDeviceRect::FromAppUnits(base, auPerDevPixel) * parentRes;
// Note on the correctness of applying the alignment in Screen space: // The correct space to apply the alignment in would be Layer space, but // we don't necessarily know the scale to convert to Layer space at this // point because Layout may not yet have chosen the resolution at which to // render (it chooses that in FrameLayerBuilder, but this can be called // during display list building). Therefore, we perform the alignment in // Screen space, which basically assumes that Layout chose to render at // screen resolution; since this is what Layout does most of the time, // this is a good approximation. A proper solution would involve moving // the choosing of the resolution to display-list building time.
ScreenSize alignment;
if (presShell->IsDisplayportSuppressed() ||
aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
alignment = ScreenSize(1, 1);
} else { // Moving the displayport is relatively expensive with WR so we use a larger // alignment that causes the displayport to move less frequently. The // alignment scales up with the size of the base rect so larger scrollframes // use a larger alignment, but we clamp the alignment to a power of two // between 128 and 1024 (inclusive). // This naturally also increases the size of the displayport compared to // always using a 128 alignment, so the displayport multipliers are also // correspondingly smaller when WR is enabled to prevent the displayport // from becoming too big.
IntSize multiplier =
layers::apz::GetDisplayportAlignmentMultiplier(screenRect.Size());
alignment = ScreenSize(128 * multiplier.width, 128 * multiplier.height);
}
// Avoid division by zero. if (alignment.width == 0) {
alignment.width = 128;
} if (alignment.height == 0) {
alignment.height = 128;
}
// Expand the rect by the margins
screenRect.Inflate(margins);
nsRect result; if (rectData) {
result = GetDisplayPortFromRectData(aContent, rectData);
} elseif (isDisplayportSuppressed ||
nsLayoutUtils::ShouldDisableApzForElement(aContent) ||
aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) { // Note: the above conditions should be in sync with the conditions in // WillUseEmptyDisplayPortMargins.
// Make a copy of the margins data but set the margins to empty. // Do not create a new DisplayPortMargins object with // DisplayPortMargins::Empty(), because that will record the visual // and layout scroll offsets in place right now on the DisplayPortMargins, // and those are only meant to be recorded when the margins are stored.
DisplayPortMarginsPropertyData noMargins = *marginsData;
noMargins.mMargins.mMargins = ScreenMargin();
result = GetDisplayPortFromMarginsData(aContent, &noMargins, aOptions);
} else {
result = GetDisplayPortFromMarginsData(aContent, marginsData, aOptions);
}
if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) {
TranslateFromScrollPortToScrollContainerFrame(aContent, &result);
}
void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent* aContent) {
DisplayPortPropertyData* rectData = nullptr;
DisplayPortMarginsPropertyData* marginsData = nullptr;
GetDisplayPortData(aContent, &rectData, &marginsData);
MOZ_ASSERT(rectData || marginsData, "MarkDisplayPortAsPainted should only be called for an element " "with a displayport"); if (rectData) {
rectData->mPainted = true;
} if (marginsData) {
marginsData->mPainted = true;
}
}
if (!marginsData) { // We have a display port, so if we don't have margin data we must have rect // data. We consider such as non zero and non minimal, it's probably not too // important as display port rects are only used in tests. returntrue;
}
if (marginsData->mMargins.mMargins != ScreenMargin()) { returntrue;
}
nsIFrame* frame = nsLayoutUtils::FindScrollContainerFrameFor(aContent); if (changed && frame) { // It is important to call SchedulePaint on the same frame that we set the // dirty rect properties on so we can find the frame later to remove the // properties.
frame->SchedulePaint();
if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) { return;
}
if (StaticPrefs::layout_display_list_retain_sc()) { // DisplayListBuildingDisplayPortRect property is not used when retain sc // mode is enabled. return;
}
auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(frame); if (!builder) { return;
}
bool found;
nsRect* rect = frame->GetProperty(
nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
if (!found) {
rect = new nsRect();
frame->AddProperty(
nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
frame->SetHasOverrideDirtyRegion(true);
DL_LOGV("Adding display port building rect for frame %p\n", frame);
RetainedDisplayListData* data = builder->Data();
data->Flags(frame) += RetainedDisplayListData::FrameFlag::HasProps;
} else {
MOZ_ASSERT(rect, "this property should only store non-null values");
}
if (aHadDisplayPort) { // We only need to build a display list for any new areas added
nsRegion newRegion(aNewDisplayPort);
newRegion.SubOut(aOldDisplayPort);
rect->UnionRect(*rect, newRegion.GetBounds());
} else {
rect->UnionRect(*rect, aNewDisplayPort);
}
}
}
if (currentData && currentData->mMargins.mVisualOffset != CSSPoint() &&
aMargins.mVisualOffset == CSSPoint()) { // If we hit this, then it's possible that we're setting a displayport // that is wrong because the old one had a layout/visual adjustment and // the new one does not.
MOZ_LOG(sDisplayportLog, LogLevel::Warning,
("Dropping visual offset %s",
ToString(currentData->mMargins.mVisualOffset).c_str()));
}
nsRect oldDisplayPort; bool hadDisplayPort = false; bool wasPainted = GetWasDisplayPortPainted(aContent); if (scrollFrame) { // We only use the two return values from this function to call // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does // nothing if aContent does not have a frame. So getting the displayport is // useless if the content has no frame, so we avoid calling this to avoid // triggering a warning about not having a frame.
hadDisplayPort = GetDisplayPort(aContent, &oldDisplayPort);
}
aContent->SetProperty(
nsGkAtoms::DisplayPortMargins, new DisplayPortMarginsPropertyData(aMargins, aPriority, wasPainted),
nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
// Display port margins changing means that the set of visible frames may // have drastically changed. Check if we should schedule an update.
hadDisplayPort = scrollContainerFrame
->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
&oldDisplayPort);
bool needVisibilityUpdate = !hadDisplayPort; // Check if the total size has changed by a large factor. if (!needVisibilityUpdate) { if ((newDisplayPort.width > 2 * oldDisplayPort.width) ||
(oldDisplayPort.width > 2 * newDisplayPort.width) ||
(newDisplayPort.height > 2 * oldDisplayPort.height) ||
(oldDisplayPort.height > 2 * newDisplayPort.height)) {
needVisibilityUpdate = true;
}
} // Check if it's moved by a significant amount. if (!needVisibilityUpdate) { if (nsRect* baseData = static_cast<nsRect*>(
aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
nsRect base = *baseData; if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) ||
(std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) >
base.width) ||
(std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
(std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) >
base.height)) {
needVisibilityUpdate = true;
}
}
} if (needVisibilityUpdate) {
aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
}
returntrue;
}
void DisplayPortUtils::SetDisplayPortBase(nsIContent* aContent, const nsRect& aBase) { if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(aContent);
MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
("Setting base rect %s for scrollId=%" PRIu64 "\n",
ToString(aBase).c_str(), viewId));
}
aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
nsINode::DeleteProperty<nsRect>);
}
bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) { // Fixed-pos frames are parented by the viewport frame or the page content // frame. We'll assume that printing/print preview don't have displayports for // their pages!
nsIFrame* parent = aFrame->GetParent(); if (!parent || parent->GetParent() ||
aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) { returnfalse;
} return ViewportHasDisplayPort(aFrame->PresContext());
}
// We want to this return true for the scroll frame, but not the // scrolled frame (which has the same content). bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame* aFrame, const nsIFrame* aScrolledFrame) { if (!aFrame->GetContent() || !HasDisplayPort(aFrame->GetContent())) { returnfalse;
}
ScrollContainerFrame* sf = do_QueryFrame(aFrame); if (sf) { if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) { returnfalse;
} returntrue;
} returnfalse;
}
nsIContent* content = aScrollContainerFrame->GetContent(); if (!content) { returnfalse;
}
// We perform an optimization where we ensure that at least one // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a // displayport. If that's not the case yet, and we are async-scrollable, we // will get a displayport.
MOZ_ASSERT(nsLayoutUtils::AsyncPanZoomEnabled(aScrollContainerFrame)); if (!aBuilder->HaveScrollableDisplayPort() &&
aScrollContainerFrame->WantAsyncScroll()) { bool haveDisplayPort = HasNonMinimalNonZeroDisplayPort(content); // If we don't already have a displayport, calculate and set one. if (!haveDisplayPort) { // We only use the viewId for logging purposes, but create it // unconditionally to minimize impact of enabling logging. If we don't // assign a viewId here it will get assigned later anyway so functionally // there should be no difference.
ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(content);
MOZ_LOG(
sDisplayportLog, LogLevel::Debug,
("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId));
CalculateAndSetDisplayPortMargins(aScrollContainerFrame, aRepaintMode); #ifdef DEBUG
haveDisplayPort = HasNonMinimalDisplayPort(content);
MOZ_ASSERT(haveDisplayPort, "should have a displayport after having just set it"); #endif
}
// Record that the we now have a scrollable display port.
aBuilder->SetHaveScrollableDisplayPort(); returntrue;
} returnfalse;
} void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
nsIFrame* aFrame) {
nsIFrame* frame = aFrame; while (frame) {
frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame); if (!frame) { break;
}
ScrollContainerFrame* scrollAncestor =
nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame); if (!scrollAncestor) { break;
}
frame = scrollAncestor;
MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
frame->PresShell()->GetRootScrollContainerFrame() == frame); if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
!HasDisplayPort(frame->GetContent())) {
SetDisplayPortMargins(frame->GetContent(), frame->PresShell(),
DisplayPortMargins::Empty(frame->GetContent()),
ClearMinimalDisplayPortProperty::No, 0,
RepaintMode::Repaint);
}
}
}
bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) { // Don't descend into the tab bar in chrome, it can be very large and does not // contain any async scrollable elements. if (XRE_IsParentProcess() && aFrame->GetContent() &&
aFrame->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox) { returnfalse;
} if (aFrame->IsScrollContainerOrSubclass()) { auto* sf = static_cast<ScrollContainerFrame*>(aFrame); if (MaybeCreateDisplayPort(aBuilder, sf, RepaintMode::Repaint)) { // If this was the first displayport found in the first scroll container // frame encountered, mark the scroll container frame with the current // paint sequence number. This is used later to ensure the displayport // created is never expired. When there is a scrollable frame with a first // scrollable sequence number found that does not match the current paint // sequence number (may occur if the dom was mutated in some way), the // value will be reset.
sf->SetIsFirstScrollableFrameSequenceNumber(
Some(nsDisplayListBuilder::GetPaintSequenceNumber())); returntrue;
}
} elseif (aFrame->IsPlaceholderFrame()) {
nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(aFrame);
nsIFrame* oof = placeholder->GetOutOfFlowFrame(); if (oof && !nsLayoutUtils::IsPopup(oof) &&
MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof, aBuilder)) { returntrue;
}
} elseif (aFrame->IsSubDocumentFrame()) {
PresShell* presShell = static_cast<nsSubDocumentFrame*>(aFrame)
->GetSubdocumentPresShellForPainting(0); if (nsIFrame* root = presShell ? presShell->GetRootFrame() : nullptr) { if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root, aBuilder)) { returntrue;
}
}
} if (aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { // Only descend the visible card of deck / tabpanels returnfalse;
} for (nsIFrame* child : aFrame->PrincipalChildList()) { if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) { returntrue;
}
} returnfalse;
}
void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
nsIFrame* aFrame) {
nsIFrame* frame = aFrame; while (frame) {
frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); if (!frame) { break;
}
ScrollContainerFrame* scrollAncestor =
nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame); if (!scrollAncestor) { break;
}
frame = scrollAncestor;
MOZ_ASSERT(frame); if (!frame) { break;
}
MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
frame->PresShell()->GetRootScrollContainerFrame() == frame); if (HasDisplayPort(frame->GetContent())) {
scrollAncestor->TriggerDisplayPortExpiration(); // Stop after the first trigger. If it failed, there's no point in // continuing because all the rest of the frames we encounter are going // to be ancestors of |scrollAncestor| which will keep its displayport. // If the trigger succeeded, we stop because when the trigger executes // it will call this function again to trigger the next ancestor up the // chain. break;
}
}
}
Maybe<nsRect> DisplayPortUtils::GetRootDisplayportBase(PresShell* aPresShell) {
DebugOnly<nsPresContext*> pc = aPresShell->GetPresContext();
MOZ_ASSERT(pc, "this function should be called after PresShell::Init");
MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess() ||
!pc->GetParentPresContext());
dom::BrowserChild* browserChild = dom::BrowserChild::GetFrom(aPresShell); if (browserChild && !browserChild->IsTopLevel()) { // If this is an in-process root in on OOP iframe, use the visible rect if // it's been set. return browserChild->GetVisibleRect();
}
// Note these conditions should be in sync with the conditions where we use // empty margins to calculate display port in GetDisplayPortImpl return aContent->GetProperty(nsGkAtoms::MinimalDisplayPort) ||
frame->PresShell()->IsDisplayportSuppressed() ||
nsLayoutUtils::ShouldDisableApzForElement(aContent);
}
} // namespace mozilla
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.