Source code

Revision control

Other Tools

/* -*- 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/. */
#include "nsLayoutUtils.h"
#include <algorithm>
#include <limits>
#include "ActiveLayerTracker.h"
#include "ClientLayerManager.h"
#include "DisplayItemClip.h"
#include "DisplayListChecker.h"
#include "FrameLayerBuilder.h"
#include "GeckoProfiler.h"
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxDrawable.h"
#include "gfxEnv.h"
#include "gfxMatrix.h"
#include "gfxPlatform.h"
#include "gfxRect.h"
#include "gfxTypes.h"
#include "gfxUtils.h"
#include "ImageContainer.h"
#include "ImageOps.h"
#include "ImageRegion.h"
#include "imgIContainer.h"
#include "imgIRequest.h"
#include "LayersLogging.h"
#include "LayoutLogging.h"
#include "MobileViewportManager.h"
#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/CanvasUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/DOMRect.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/InspectorFontFace.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "mozilla/dom/SVGViewportElement.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/layers/APZCCallbackHelper.h"
#include "mozilla/layers/APZPublicUtils.h" // for apz::CalculatePendingDisplayPort
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/PAPZ.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/Likely.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PerfStats.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ScrollOrigin.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/ServoStyleSetInlines.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_font.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_image.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StyleAnimationValue.h"
#include "mozilla/SVGImageContext.h"
#include "mozilla/SVGIntegrationUtils.h"
#include "mozilla/SVGTextFrame.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/ViewportFrame.h"
#include "mozilla/ViewportUtils.h"
#include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils
#include "nsAnimationManager.h"
#include "nsAtom.h"
#include "nsBidiPresUtils.h"
#include "nsBlockFrame.h"
#include "nsCanvasFrame.h"
#include "nsCaret.h"
#include "nsCharTraits.h"
#include "nsCOMPtr.h"
#include "nsComputedDOMStyle.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSColorUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSRendering.h"
#include "nsDataHashtable.h"
#include "nsDeckFrame.h"
#include "nsDisplayList.h"
#include "nsFlexContainerFrame.h"
#include "nsFontInflationData.h"
#include "nsFontMetrics.h"
#include "nsFrameList.h"
#include "nsFrameSelection.h"
#include "nsGenericHTMLElement.h"
#include "nsGkAtoms.h"
#include "nsICanvasRenderingContextInternal.h"
#include "nsIContent.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsIFrameInlines.h"
#include "nsIImageLoadingContent.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIScrollableFrame.h"
#include "nsIWidget.h"
#include "nsListControlFrame.h"
#include "nsPIDOMWindow.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsPresContextInlines.h"
#include "nsRefreshDriver.h"
#include "nsRegion.h"
#include "nsStyleConsts.h"
#include "nsStyleStructInlines.h"
#include "nsStyleTransformMatrix.h"
#include "nsSubDocumentFrame.h"
#include "nsTableWrapperFrame.h"
#include "nsTArray.h"
#include "nsTextFragment.h"
#include "nsTextFrame.h"
#include "nsTransitionManager.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "prenv.h"
#include "RegionBuilder.h"
#include "RetainedDisplayListBuilder.h"
#include "TextDrawTarget.h"
#include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE
#include "UnitTransforms.h"
#include "ViewportFrame.h"
#ifdef MOZ_XUL
# include "nsXULPopupManager.h"
#endif
// Make sure getpid() works.
#ifdef XP_WIN
# include <process.h>
# define getpid _getpid
#else
# include <unistd.h>
#endif
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::image;
using namespace mozilla::layers;
using namespace mozilla::layout;
using namespace mozilla::gfx;
using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
using mozilla::dom::HTMLMediaElement_Binding::HAVE_NOTHING;
#ifdef DEBUG
// TODO: remove, see bug 598468.
bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
#endif // DEBUG
typedef ScrollableLayerGuid::ViewID ViewID;
typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
static ViewID sScrollIdCounter = ScrollableLayerGuid::START_SCROLL_ID;
static mozilla::LazyLogModule sDisplayportLog("apz.displayport");
typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap;
static ContentMap* sContentMap = nullptr;
static ContentMap& GetContentMap() {
if (!sContentMap) {
sContentMap = new ContentMap();
}
return *sContentMap;
}
template <typename TestType>
static bool HasMatchingAnimations(EffectSet& aEffects, TestType&& aTest) {
for (KeyframeEffect* effect : aEffects) {
if (!effect->GetAnimation() || !effect->GetAnimation()->IsRelevant()) {
continue;
}
if (aTest(*effect, aEffects)) {
return true;
}
}
return false;
}
template <typename TestType>
static bool HasMatchingAnimations(const nsIFrame* aFrame,
const nsCSSPropertyIDSet& aPropertySet,
TestType&& aTest) {
MOZ_ASSERT(aFrame);
if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
!aFrame->MayHaveOpacityAnimation()) {
return false;
}
if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
!aFrame->MayHaveTransformAnimation()) {
return false;
}
EffectSet* effectSet = EffectSet::GetEffectSetForFrame(aFrame, aPropertySet);
if (!effectSet) {
return false;
}
return HasMatchingAnimations(*effectSet, aTest);
}
/* static */
bool nsLayoutUtils::HasAnimationOfPropertySet(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
return HasMatchingAnimations(
aFrame, aPropertySet,
[&aPropertySet](KeyframeEffect& aEffect, const EffectSet&) {
return aEffect.HasAnimationOfPropertySet(aPropertySet);
});
}
/* static */
bool nsLayoutUtils::HasAnimationOfPropertySet(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
EffectSet* aEffectSet) {
MOZ_ASSERT(
!aEffectSet ||
EffectSet::GetEffectSetForFrame(aFrame, aPropertySet) == aEffectSet,
"The EffectSet, if supplied, should match what we would otherwise fetch");
if (!aEffectSet) {
return nsLayoutUtils::HasAnimationOfPropertySet(aFrame, aPropertySet);
}
if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
!aEffectSet->MayHaveTransformAnimation()) {
return false;
}
if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
!aEffectSet->MayHaveOpacityAnimation()) {
return false;
}
return HasMatchingAnimations(
*aEffectSet,
[&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
return aEffect.HasAnimationOfPropertySet(aPropertySet);
});
}
/* static */
bool nsLayoutUtils::HasAnimationOfTransformAndMotionPath(
const nsIFrame* aFrame) {
return nsLayoutUtils::HasAnimationOfPropertySet(
aFrame,
nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_translate,
eCSSProperty_rotate, eCSSProperty_scale,
eCSSProperty_offset_path}) ||
(!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
nsLayoutUtils::HasAnimationOfPropertySet(
aFrame, nsCSSPropertyIDSet::MotionPathProperties()));
}
/* static */
bool nsLayoutUtils::HasEffectiveAnimation(
const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
return HasMatchingAnimations(
aFrame, aPropertySet,
[&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
return aEffect.HasEffectiveAnimationOfPropertySet(aPropertySet,
aEffectSet);
});
}
/* static */
nsCSSPropertyIDSet nsLayoutUtils::GetAnimationPropertiesForCompositor(
const nsIFrame* aStyleFrame) {
nsCSSPropertyIDSet properties;
// We fetch the effects for the style frame here since this method is called
// by RestyleManager::AddLayerChangesForAnimation which takes care to apply
// the relevant hints to the primary frame as needed.
EffectSet* effects = EffectSet::GetEffectSetForStyleFrame(aStyleFrame);
if (!effects) {
return properties;
}
AnimationPerformanceWarning::Type warning;
if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aStyleFrame,
warning)) {
return properties;
}
for (const KeyframeEffect* effect : *effects) {
properties |= effect->GetPropertiesForCompositor(*effects, aStyleFrame);
}
// If properties only have motion-path properties, we have to make sure they
// have effects. i.e. offset-path is not none or we have offset-path
// animations.
if (properties.IsSubsetOf(nsCSSPropertyIDSet::MotionPathProperties()) &&
!properties.HasProperty(eCSSProperty_offset_path) &&
aStyleFrame->StyleDisplay()->mOffsetPath.IsNone()) {
properties.Empty();
}
return properties;
}
static float GetSuitableScale(float aMaxScale, float aMinScale,
nscoord aVisibleDimension,
nscoord aDisplayDimension) {
float displayVisibleRatio =
float(aDisplayDimension) / float(aVisibleDimension);
// We want to rasterize based on the largest scale used during the
// transform animation, unless that would make us rasterize something
// larger than the screen. But we never want to go smaller than the
// minimum scale over the animation.
if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
// Using aMaxScale may make us rasterize something a fraction larger than
// the screen. However, if aMaxScale happens to be the final scale of a
// transform animation it is better to use aMaxScale so that for the
// fraction of a second before we delayerize the composited texture it has
// a better chance of being pixel aligned and composited without resampling
// (avoiding visually clunky delayerization).
return aMaxScale;
}
return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
}
// The first value in this pair is the min scale, and the second one is the max
// scale.
using MinAndMaxScale = std::pair<Size, Size>;
static inline void UpdateMinMaxScale(const nsIFrame* aFrame,
const AnimationValue& aValue,
MinAndMaxScale& aMinAndMaxScale) {
Size size = aValue.GetScaleValue(aFrame);
Size& minScale = aMinAndMaxScale.first;
Size& maxScale = aMinAndMaxScale.second;
minScale = Min(minScale, size);
maxScale = Max(maxScale, size);
}
// The final transform matrix is calculated by merging the final results of each
// transform-like properties, so do the scale factors. In other words, the
// potential min/max scales could be gotten by multiplying the max/min scales of
// each properties.
//
// For example, there is an animation:
// from { "transform: scale(1, 1)", "scale: 3, 3" };
// to { "transform: scale(2, 2)", "scale: 1, 1" };
//
// the min scale is (1, 1) * (1, 1) = (1, 1), and
// The max scale is (2, 2) * (3, 3) = (6, 6).
// This means we multiply the min/max scale factor of transform property and the
// min/max scale factor of scale property to get the final max/min scale factor.
static Array<MinAndMaxScale, 2> GetMinAndMaxScaleForAnimationProperty(
const nsIFrame* aFrame,
const nsTArray<RefPtr<dom::Animation>>& aAnimations) {
// We use a fixed array to store the min/max scales for each property.
// The first element in the array is for eCSSProperty_transform, and the
// second one is for eCSSProperty_scale.
const MinAndMaxScale defaultValue =
std::make_pair(Size(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max()),
Size(std::numeric_limits<float>::min(),
std::numeric_limits<float>::min()));
Array<MinAndMaxScale, 2> minAndMaxScales(defaultValue, defaultValue);
for (dom::Animation* anim : aAnimations) {
// This method is only expected to be passed animations that are running on
// the compositor and we only pass playing animations to the compositor,
// which are, by definition, "relevant" animations (animations that are
// not yet finished or which are filling forwards).
MOZ_ASSERT(anim->IsRelevant());
const dom::KeyframeEffect* effect =
anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
MOZ_ASSERT(effect, "A playing animation should have a keyframe effect");
for (const AnimationProperty& prop : effect->Properties()) {
if (prop.mProperty != eCSSProperty_transform &&
prop.mProperty != eCSSProperty_scale) {
continue;
}
// 0: eCSSProperty_transform.
// 1: eCSSProperty_scale.
MinAndMaxScale& scales =
minAndMaxScales[prop.mProperty == eCSSProperty_transform ? 0 : 1];
// We need to factor in the scale of the base style if the base style
// will be used on the compositor.
const AnimationValue& baseStyle = effect->BaseStyle(prop.mProperty);
if (!baseStyle.IsNull()) {
UpdateMinMaxScale(aFrame, baseStyle, scales);
}
for (const AnimationPropertySegment& segment : prop.mSegments) {
// In case of add or accumulate composite, StyleAnimationValue does
// not have a valid value.
if (segment.HasReplaceableFromValue()) {
UpdateMinMaxScale(aFrame, segment.mFromValue, scales);
}
if (segment.HasReplaceableToValue()) {
UpdateMinMaxScale(aFrame, segment.mToValue, scales);
}
}
}
}
return minAndMaxScales;
}
Size nsLayoutUtils::ComputeSuitableScaleForAnimation(
const nsIFrame* aFrame, const nsSize& aVisibleSize,
const nsSize& aDisplaySize) {
const nsTArray<RefPtr<dom::Animation>> compositorAnimations =
EffectCompositor::GetAnimationsForCompositor(
aFrame,
nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_scale});
if (compositorAnimations.IsEmpty()) {
return Size(1.0, 1.0);
}
const Array<MinAndMaxScale, 2> minAndMaxScales =
GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations);
// This might cause an issue if users use std::numeric_limits<float>::min()
// (or max()) as the scale value. However, in this case, we may render an
// extreme small (or large) element, so this may not be a problem. If so,
// please fix this.
Size maxScale(std::numeric_limits<float>::min(),
std::numeric_limits<float>::min());
Size minScale(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
auto isUnset = [](const Size& aMax, const Size& aMin) {
return aMax.width == std::numeric_limits<float>::min() &&
aMax.height == std::numeric_limits<float>::min() &&
aMin.width == std::numeric_limits<float>::max() &&
aMin.height == std::numeric_limits<float>::max();
};
// Iterate the slots to get the final scale value.
for (const auto& pair : minAndMaxScales) {
const Size& currMinScale = pair.first;
const Size& currMaxScale = pair.second;
if (isUnset(currMaxScale, currMinScale)) {
// We don't have this animation property, so skip.
continue;
}
if (isUnset(maxScale, minScale)) {
// Initialize maxScale and minScale.
maxScale = currMaxScale;
minScale = currMinScale;
} else {
// The scale factors of each transform-like property should be multiplied
// by others because we merge their sampled values as a final matrix by
// matrix multiplication, so here we multiply the scale factors by the
// previous one to get the possible max and min scale factors.
maxScale = maxScale * currMaxScale;
minScale = minScale * currMinScale;
}
}
if (isUnset(maxScale, minScale)) {
// We didn't encounter any transform-like property.
return Size(1.0, 1.0);
}
return Size(GetSuitableScale(maxScale.width, minScale.width,
aVisibleSize.width, aDisplaySize.width),
GetSuitableScale(maxScale.height, minScale.height,
aVisibleSize.height, aDisplaySize.height));
}
bool nsLayoutUtils::AreAsyncAnimationsEnabled() {
return StaticPrefs::layers_offmainthreadcomposition_async_animations() &&
gfxPlatform::OffMainThreadCompositingEnabled();
}
bool nsLayoutUtils::AreRetainedDisplayListsEnabled() {
#ifdef MOZ_WIDGET_ANDROID
return StaticPrefs::layout_display_list_retain();
#else
if (XRE_IsContentProcess()) {
return StaticPrefs::layout_display_list_retain();
}
if (XRE_IsE10sParentProcess()) {
return StaticPrefs::layout_display_list_retain_chrome();
}
// Retained display lists require e10s.
return false;
#endif
}
bool nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame) {
const nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
MOZ_ASSERT(displayRoot);
return displayRoot->HasProperty(RetainedDisplayListBuilder::Cached());
}
bool nsLayoutUtils::GPUImageScalingEnabled() {
static bool sGPUImageScalingEnabled;
static bool sGPUImageScalingPrefInitialised = false;
if (!sGPUImageScalingPrefInitialised) {
sGPUImageScalingPrefInitialised = true;
sGPUImageScalingEnabled =
Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
}
return sGPUImageScalingEnabled;
}
void nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
nsOverflowAreas& aOverflowAreas,
FrameChildListIDs aSkipChildLists) {
// Iterate over all children except pop-ups.
FrameChildListIDs skip(aSkipChildLists);
skip += {nsIFrame::kSelectPopupList, nsIFrame::kPopupList};
for (const auto& [list, listID] : aFrame->ChildLists()) {
if (skip.contains(listID)) {
continue;
}
for (nsIFrame* child : list) {
nsOverflowAreas childOverflow =
child->GetOverflowAreas() + child->GetPosition();
aOverflowAreas.UnionWith(childOverflow);
}
}
}
static void DestroyViewID(void* aObject, nsAtom* aPropertyName,
void* aPropertyValue, void* aData) {
ViewID* id = static_cast<ViewID*>(aPropertyValue);
GetContentMap().Remove(*id);
delete id;
}
/**
* A namespace class for static layout utilities.
*/
bool nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId) {
void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId);
if (scrollIdProperty) {
*aOutViewId = *static_cast<ViewID*>(scrollIdProperty);
return true;
}
return false;
}
ViewID nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent) {
ViewID scrollId;
if (!FindIDFor(aContent, &scrollId)) {
scrollId = sScrollIdCounter++;
aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
DestroyViewID);
GetContentMap().Put(scrollId, aContent);
}
return scrollId;
}
nsIContent* nsLayoutUtils::FindContentFor(ViewID aId) {
MOZ_ASSERT(aId != ScrollableLayerGuid::NULL_SCROLL_ID,
"Cannot find a content element in map for null IDs.");
nsIContent* content;
bool exists = GetContentMap().Get(aId, &content);
if (exists) {
return content;
} else {
return nullptr;
}
}
static nsIFrame* GetScrollFrameFromContent(nsIContent* aContent) {
nsIFrame* frame = aContent->GetPrimaryFrame();
if (aContent->OwnerDoc()->GetRootElement() == aContent) {
PresShell* presShell = frame ? frame->PresShell() : nullptr;
if (!presShell) {
presShell = aContent->OwnerDoc()->GetPresShell();
}
// We want the scroll frame, the root scroll frame differs from all
// others in that the primary frame is not the scroll frame.
nsIFrame* rootScrollFrame =
presShell ? presShell->GetRootScrollFrame() : nullptr;
if (rootScrollFrame) {
frame = rootScrollFrame;
}
}
return frame;
}
nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(
nsIContent* aContent) {
nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
}
nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(ViewID aId) {
nsIContent* content = FindContentFor(aId);
if (!content) {
return nullptr;
}
return FindScrollableFrameFor(content);
}
ViewID nsLayoutUtils::FindIDForScrollableFrame(
nsIScrollableFrame* aScrollable) {
if (!aScrollable) {
return ScrollableLayerGuid::NULL_SCROLL_ID;
}
nsIFrame* scrollFrame = do_QueryFrame(aScrollable);
nsIContent* scrollContent = scrollFrame->GetContent();
ScrollableLayerGuid::ViewID scrollId;
if (scrollContent && nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) {
return scrollId;
}
return ScrollableLayerGuid::NULL_SCROLL_ID;
}
static nsRect ApplyRectMultiplier(nsRect aRect, float aMultiplier) {
if (aMultiplier == 1.0f) {
return aRect;
}
float newWidth = aRect.width * aMultiplier;
float newHeight = aRect.height * aMultiplier;
float newX = aRect.x - ((newWidth - aRect.width) / 2.0f);
float newY = aRect.y - ((newHeight - aRect.height) / 2.0f);
// Rounding doesn't matter too much here, do a round-in
return nsRect(ceil(newX), ceil(newY), floor(newWidth), floor(newHeight));
}
bool nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame) {
#ifdef MOZ_WIDGET_ANDROID
// We always have async scrolling for android
return true;
#endif
return AsyncPanZoomEnabled(aFrame);
}
bool nsLayoutUtils::AsyncPanZoomEnabled(const nsIFrame* aFrame) {
// We use this as a shortcut, since if the compositor will never use APZ,
// no widget will either.
if (!gfxPlatform::AsyncPanZoomEnabled()) {
return false;
}
const nsIFrame* frame = nsLayoutUtils::GetDisplayRootFrame(aFrame);
nsIWidget* widget = frame->GetNearestWidget();
if (!widget) {
return false;
}
return widget->AsyncPanZoomEnabled();
}
bool nsLayoutUtils::AllowZoomingForDocument(
const mozilla::dom::Document* aDocument) {
// True if we allow zooming for all documents on this platform, or if we are
// in RDM and handling meta viewports, which force zoom under some
// circumstances.
BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
return StaticPrefs::apz_allow_zooming() ||
(bc && bc->InRDMPane() &&
nsLayoutUtils::ShouldHandleMetaViewport(aDocument));
}
float nsLayoutUtils::GetCurrentAPZResolutionScale(PresShell* aPresShell) {
return aPresShell ? aPresShell->GetCumulativeResolution() : 1.0;
}
// Return the maximum displayport size, based on the LayerManager's maximum
// supported texture size. The result is in app units.
static nscoord GetMaxDisplayPortSize(nsIContent* aContent,
nsPresContext* aFallbackPrescontext) {
MOZ_ASSERT(!StaticPrefs::layers_enable_tiles_AtStartup(),
"Do not clamp displayports if tiling is enabled");
// Pick a safe maximum displayport size for sanity purposes. This is the
// lowest maximum texture size on tileless-platforms (Windows, D3D10).
// If the gfx.max-texture-size pref is set, further restrict the displayport
// size to fit within that, because the compositor won't upload stuff larger
// than this size.
nscoord safeMaximum = aFallbackPrescontext
? aFallbackPrescontext->DevPixelsToAppUnits(
std::min(8192, gfxPlatform::MaxTextureSize()))
: nscoord_MAX;
nsIFrame* frame = aContent->GetPrimaryFrame();
if (!frame) {
return safeMaximum;
}
frame = nsLayoutUtils::GetDisplayRootFrame(frame);
nsIWidget* widget = frame->GetNearestWidget();
if (!widget) {
return safeMaximum;
}
LayerManager* lm = widget->GetLayerManager();
if (!lm) {
return safeMaximum;
}
nsPresContext* presContext = frame->PresContext();
int32_t maxSizeInDevPixels = lm->GetMaxTextureSize();
if (maxSizeInDevPixels < 0 || maxSizeInDevPixels == INT_MAX) {
return safeMaximum;
}
maxSizeInDevPixels =
std::min(maxSizeInDevPixels, gfxPlatform::MaxTextureSize());
return presContext->DevPixelsToAppUnits(maxSizeInDevPixels);
}
static nsRect GetDisplayPortFromRectData(nsIContent* aContent,
DisplayPortPropertyData* aRectData,
float aMultiplier) {
// 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). We still do need to
// expand it by the multiplier though.
return ApplyRectMultiplier(aRectData->mRect, aMultiplier);
}
static nsRect GetDisplayPortFromMarginsData(
nsIContent* aContent, DisplayPortMarginsPropertyData* aMarginsData,
float aMultiplier) {
// 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, further expand the rect by the multiplier, 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 = GetScrollFrameFromContent(aContent);
if (!frame) {
// Turns out we can't really compute it. Oops. We still should return
// something sane. Note that since we can't clamp the rect without a
// frame, we don't apply the multiplier either as it can cause the result
// to leak outside the scrollable area.
NS_WARNING(
"Attempting to get a displayport from a content with no primary "
"frame!");
return base;
}
bool isRoot = false;
if (aContent->OwnerDoc()->GetRootElement() == aContent) {
isRoot = true;
}
nsPoint scrollPos;
if (nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame()) {
scrollPos = scrollableFrame->GetScrollPosition();
}
nsPresContext* presContext = frame->PresContext();
int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
LayoutDeviceToScreenScale2D res(
presContext->PresShell()->GetCumulativeResolution() *
nsLayoutUtils::GetTransformToAncestorScale(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;
PresShell* presShell = presContext->PresShell();
MOZ_ASSERT(presShell);
bool useWebRender = gfxVars::UseWebRender();
if (presShell->IsDisplayportSuppressed()) {
alignment = ScreenSize(1, 1);
} else if (useWebRender) {
// 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 =
apz::GetDisplayportAlignmentMultiplier(screenRect.Size());
alignment = ScreenSize(128 * multiplier.width, 128 * multiplier.height);
} else if (StaticPrefs::layers_enable_tiles_AtStartup()) {
// Don't align to tiles if they are too large, because we could expand
// the displayport by a lot which can take more paint time. It's a tradeoff
// though because if we don't align to tiles we have more waste on upload.
IntSize tileSize = gfxVars::TileSize();
alignment = ScreenSize(std::min(256, tileSize.width),
std::min(256, tileSize.height));
} else {
// If we're not drawing with tiles then we need to be careful about not
// hitting the max texture size and we only need 1 draw call per layer
// so we can align to a smaller multiple.
alignment = ScreenSize(128, 128);
}
// Avoid division by zero.
if (alignment.width == 0) {
alignment.width = 128;
}
if (alignment.height == 0) {
alignment.height = 128;
}
if (StaticPrefs::layers_enable_tiles_AtStartup() || useWebRender) {
// Expand the rect by the margins
screenRect.Inflate(aMarginsData->mMargins);
} else {
// Calculate the displayport to make sure we fit within the max texture size
// when not tiling.
nscoord maxSizeAppUnits = GetMaxDisplayPortSize(aContent, presContext);
MOZ_ASSERT(maxSizeAppUnits < nscoord_MAX);
// The alignment code can round up to 3 tiles, we want to make sure
// that the displayport can grow by up to 3 tiles without going
// over the max texture size.
const int MAX_ALIGN_ROUNDING = 3;
// Find the maximum size in screen pixels.
int32_t maxSizeDevPx = presContext->AppUnitsToDevPixels(maxSizeAppUnits);
int32_t maxWidthScreenPx = floor(double(maxSizeDevPx) * res.xScale) -
MAX_ALIGN_ROUNDING * alignment.width;
int32_t maxHeightScreenPx = floor(double(maxSizeDevPx) * res.yScale) -
MAX_ALIGN_ROUNDING * alignment.height;
// For each axis, inflate the margins up to the maximum size.
const ScreenMargin& margins = aMarginsData->mMargins;
if (screenRect.height < maxHeightScreenPx) {
int32_t budget = maxHeightScreenPx - screenRect.height;
// Scale the margins down to fit into the budget if necessary, maintaining
// their relative ratio.
float scale = 1.0f;
if (float(budget) < margins.TopBottom()) {
scale = float(budget) / margins.TopBottom();
}
float top = margins.top * scale;
float bottom = margins.bottom * scale;
screenRect.y -= top;
screenRect.height += top + bottom;
}
if (screenRect.width < maxWidthScreenPx) {
int32_t budget = maxWidthScreenPx - screenRect.width;
float scale = 1.0f;
if (float(budget) < margins.LeftRight()) {
scale = float(budget) / margins.LeftRight();
}
float left = margins.left * scale;
float right = margins.right * scale;
screenRect.x -= left;
screenRect.width += left + right;
}
}
ScreenPoint scrollPosScreen =
LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel) * res;
// Align the display port.
screenRect += scrollPosScreen;
float x = alignment.width * floor(screenRect.x / alignment.width);
float y = alignment.height * floor(screenRect.y / alignment.height);
float w = alignment.width * ceil(screenRect.width / alignment.width + 1);
float h = alignment.height * ceil(screenRect.height / alignment.height + 1);
screenRect = ScreenRect(x, y, w, h);
screenRect -= scrollPosScreen;
// Convert the aligned rect back into app units.
nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel);
// If we have non-zero margins, expand the displayport for the low-res buffer
// if that's what we're drawing. If we have zero margins, we want the
// displayport to reflect the scrollport.
if (aMarginsData->mMargins != ScreenMargin()) {
result = ApplyRectMultiplier(result, aMultiplier);
}
// Make sure the displayport remains within the scrollable rect.
result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
return result;
}
static bool HasVisibleAnonymousContents(Document* aDoc) {
for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) {
// We check to see if the anonymous content node has a frame. If it doesn't,
// that means that's not visible to the user because e.g. it's display:none.
// For now we assume that if it has a frame, it is visible. We might be able
// to refine this further by adding complexity if it turns out this
// condition results in a lot of false positives.
if (ac->ContentNode().GetPrimaryFrame()) {
return true;
}
}
return false;
}
bool nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent) {
if (!aContent) {
return false;
}
if (aContent->GetProperty(nsGkAtoms::apzDisabled)) {
return true;
}
Document* doc = aContent->GetComposedDoc();
if (PresShell* rootPresShell =
APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
aContent)) {
if (Document* rootDoc = rootPresShell->GetDocument()) {
nsIContent* rootContent =
rootPresShell->GetRootScrollFrame()
? rootPresShell->GetRootScrollFrame()->GetContent()
: rootDoc->GetDocumentElement();
// For the AccessibleCaret and other anonymous contents: disable APZ on
// any scrollable subframes that are not the root scrollframe of a
// document, if the document has any visible anonymous contents.
//
// If we find this is triggering in too many scenarios then we might
// want to tighten this check further. The main use cases for which we
// want to disable APZ as of this writing are listed in bug 1316318.
if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) {
return true;
}
}
}
if (!doc) {
return false;
}
if (PresShell* presShell = doc->GetPresShell()) {
if (RefPtr<AccessibleCaretEventHub> eventHub =
presShell->GetAccessibleCaretEventHub()) {
// Disable APZ for all elements if AccessibleCaret tells us to do so.
if (eventHub->ShouldDisableApz()) {
return true;
}
}
}
return StaticPrefs::apz_disable_for_scroll_linked_effects() &&
doc->HasScrollLinkedEffect();
}
static bool GetDisplayPortData(
nsIContent* aContent, DisplayPortPropertyData** aOutRectData,
DisplayPortMarginsPropertyData** aOutMarginsData) {
MOZ_ASSERT(aOutRectData && aOutMarginsData);
*aOutRectData = static_cast<DisplayPortPropertyData*>(
aContent->GetProperty(nsGkAtoms::DisplayPort));
*aOutMarginsData = static_cast<DisplayPortMarginsPropertyData*>(
aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
if (!*aOutRectData && !*aOutMarginsData) {
// This content element has no displayport data at all
return false;
}
if (*aOutRectData && *aOutMarginsData) {
// choose margins if equal priority
if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) {
*aOutMarginsData = nullptr;
} else {
*aOutRectData = nullptr;
}
}
NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr),
"Only one of aOutRectData or aOutMarginsData should be set!");
return true;
}
static bool GetWasDisplayPortPainted(nsIContent* aContent) {
DisplayPortPropertyData* rectData = nullptr;
DisplayPortMarginsPropertyData* marginsData = nullptr;
if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
return false;
}
return rectData ? rectData->mPainted : marginsData->mPainted;
}
bool nsLayoutUtils::IsMissingDisplayPortBaseRect(nsIContent* aContent) {
DisplayPortPropertyData* rectData = nullptr;
DisplayPortMarginsPropertyData* marginsData = nullptr;
if (GetDisplayPortData(aContent, &rectData, &marginsData) && marginsData) {
return !aContent->GetProperty(nsGkAtoms::DisplayPortBase);
}
return false;
}
enum class MaxSizeExceededBehaviour {
// Ask GetDisplayPortImpl to assert if the calculated displayport exceeds
// the maximum allowed size.
Assert,
// Ask GetDisplayPortImpl to pretend like there's no displayport at all, if
// the calculated displayport exceeds the maximum allowed size.
Drop,
};
static bool GetDisplayPortImpl(
nsIContent* aContent, nsRect* aResult, float aMultiplier,
MaxSizeExceededBehaviour aBehaviour = MaxSizeExceededBehaviour::Assert,
bool* aOutPainted = nullptr) {
DisplayPortPropertyData* rectData = nullptr;
DisplayPortMarginsPropertyData* marginsData = nullptr;
if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
return false;
}
if (aOutPainted) {
*aOutPainted = rectData ? rectData->mPainted : marginsData->mPainted;
}
nsIFrame* frame = aContent->GetPrimaryFrame();
if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) {
return false;
}
if (!aResult) {
// We have displayport data, but the caller doesn't want the actual
// rect, so we don't need to actually compute it.
return true;
}
bool isDisplayportSuppressed = false;
if (frame) {
nsPresContext* presContext = frame->PresContext();
MOZ_ASSERT(presContext);
PresShell* presShell = presContext->PresShell();
MOZ_ASSERT(presShell);
isDisplayportSuppressed = presShell->IsDisplayportSuppressed();
}
nsRect result;
if (rectData) {
result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier);
} else if (isDisplayportSuppressed ||
nsLayoutUtils::ShouldDisableApzForElement(aContent)) {
DisplayPortMarginsPropertyData noMargins(ScreenMargin(), 1,
/*painted=*/false);
result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier);
} else {
result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier);
}
if (!StaticPrefs::layers_enable_tiles_AtStartup()) {
// Perform the desired error handling if the displayport dimensions
// exceeds the maximum allowed size
nscoord maxSize = GetMaxDisplayPortSize(aContent, nullptr);
if (result.width > maxSize || result.height > maxSize) {
switch (aBehaviour) {
case MaxSizeExceededBehaviour::Assert:
NS_ASSERTION(false, "Displayport must be a valid texture size");
break;
case MaxSizeExceededBehaviour::Drop:
return false;
}
}
}
*aResult = result;
return true;
}
static void TranslateFromScrollPortToScrollFrame(nsIContent* aContent,
nsRect* aRect) {
MOZ_ASSERT(aRect);
if (nsIScrollableFrame* scrollableFrame =
nsLayoutUtils::FindScrollableFrameFor(aContent)) {
*aRect += scrollableFrame->GetScrollPortRect().TopLeft();
}
}
bool nsLayoutUtils::GetDisplayPort(
nsIContent* aContent, nsRect* aResult,
DisplayportRelativeTo aRelativeTo /* = DisplayportRelativeTo::ScrollPort */,
bool* aOutPainted /* = nullptr */) {
float multiplier = StaticPrefs::layers_low_precision_buffer()
? 1.0f / StaticPrefs::layers_low_precision_resolution()
: 1.0f;
bool usingDisplayPort =
GetDisplayPortImpl(aContent, aResult, multiplier,
MaxSizeExceededBehaviour::Assert, aOutPainted);
if (aResult && usingDisplayPort &&
aRelativeTo == DisplayportRelativeTo::ScrollFrame) {
TranslateFromScrollPortToScrollFrame(aContent, aResult);
}
return usingDisplayPort;
}
bool nsLayoutUtils::HasDisplayPort(nsIContent* aContent) {
return GetDisplayPort(aContent, nullptr);
}
bool nsLayoutUtils::HasPaintedDisplayPort(nsIContent* aContent) {
DisplayPortPropertyData* rectData = nullptr;
DisplayPortMarginsPropertyData* marginsData = nullptr;
GetDisplayPortData(aContent, &rectData, &marginsData);
if (rectData) {
return rectData->mPainted;
}
if (marginsData) {
return marginsData->mPainted;
}
return false;
}
void nsLayoutUtils::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;
}
}
/* static */
bool nsLayoutUtils::GetDisplayPortForVisibilityTesting(
nsIContent* aContent, nsRect* aResult,
DisplayportRelativeTo
aRelativeTo /* = DisplayportRelativeTo::ScrollPort */) {
MOZ_ASSERT(aResult);
// Since the base rect might not have been updated very recently, it's
// possible to end up with an extra-large displayport at this point, if the
// zoom level is changed by a lot. Instead of using the default behaviour of
// asserting, we can just ignore the displayport if that happens, as this
// call site is best-effort.
bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, 1.0f,
MaxSizeExceededBehaviour::Drop);
if (usingDisplayPort && aRelativeTo == DisplayportRelativeTo::ScrollFrame) {
TranslateFromScrollPortToScrollFrame(aContent, aResult);
}
return usingDisplayPort;
}
void nsLayoutUtils::InvalidateForDisplayPortChange(
nsIContent* aContent, bool aHadDisplayPort, const nsRect& aOldDisplayPort,
const nsRect& aNewDisplayPort, RepaintMode aRepaintMode) {
if (aRepaintMode != RepaintMode::Repaint) {
return;
}
bool changed =
!aHadDisplayPort || !aOldDisplayPort.IsEqualEdges(aNewDisplayPort);
nsIFrame* frame = GetScrollFrameFromContent(aContent);
if (frame) {
frame = do_QueryFrame(frame->GetScrollTargetFrame());
}
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() ||
!nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(frame)) {
return;
}
bool found;
nsRect* rect = frame->GetProperty(
nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
if (!found) {
rect = new nsRect();
frame->AddProperty(
nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
frame->SetHasOverrideDirtyRegion(true);
nsIFrame* rootFrame = frame->PresShell()->GetRootFrame();
MOZ_ASSERT(rootFrame);
RetainedDisplayListData* data =
GetOrSetRetainedDisplayListData(rootFrame);
data->Flags(frame) |= RetainedDisplayListData::FrameFlags::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);
}
}
}
bool nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent,
PresShell* aPresShell,
const ScreenMargin& aMargins,
uint32_t aPriority,
RepaintMode aRepaintMode) {
MOZ_ASSERT(aContent);
MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument());
DisplayPortMarginsPropertyData* currentData =
static_cast<DisplayPortMarginsPropertyData*>(
aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
if (currentData && currentData->mPriority > aPriority) {
return false;
}
nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
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 = GetHighResolutionDisplayPort(aContent, &oldDisplayPort);
}
aContent->SetProperty(
nsGkAtoms::DisplayPortMargins,
new DisplayPortMarginsPropertyData(aMargins, aPriority, wasPainted),
nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
nsIScrollableFrame* scrollableFrame =
scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
if (!scrollableFrame) {
return true;
}
nsRect newDisplayPort;
DebugOnly<bool> hasDisplayPort =
GetHighResolutionDisplayPort(aContent, &newDisplayPort);
MOZ_ASSERT(hasDisplayPort);
if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
mozilla::layers::ScrollableLayerGuid::ViewID viewID =
mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
nsLayoutUtils::FindIDFor(aContent, &viewID);
if (!hadDisplayPort) {
MOZ_LOG(sDisplayportLog, LogLevel::Debug,
("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
Stringify(aMargins).c_str(), viewID,
Stringify(newDisplayPort).c_str()));
} else {
// Use verbose level logging for when an existing displayport got its
// margins updated.
MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
Stringify(aMargins).c_str(), viewID,
Stringify(newDisplayPort).c_str()));
}
}
InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort,
newDisplayPort, aRepaintMode);
scrollableFrame->TriggerDisplayPortExpiration();
// Display port margins changing means that the set of visible frames may
// have drastically changed. Check if we should schedule an update.
hadDisplayPort =
scrollableFrame->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();
}
return true;
}
void nsLayoutUtils::SetDisplayPortBase(nsIContent* aContent,
const nsRect& aBase) {
if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
ViewID viewId = FindOrCreateIDFor(aContent);
MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
("Setting base rect %s for scrollId=%" PRIu64 "\n",
Stringify(aBase).c_str(), viewId));
}
aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
nsINode::DeleteProperty<nsRect>);
}
void nsLayoutUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent,
const nsRect& aBase) {
if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) {
SetDisplayPortBase(aContent, aBase);
}
}
bool nsLayoutUtils::GetCriticalDisplayPort(nsIContent* aContent,
nsRect* aResult, bool* aOutPainted) {
if (StaticPrefs::layers_low_precision_buffer()) {
return GetDisplayPortImpl(aContent, aResult, 1.0f,
MaxSizeExceededBehaviour::Assert, aOutPainted);
}
return false;
}
bool nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent) {
return GetCriticalDisplayPort(aContent, nullptr);
}
bool nsLayoutUtils::GetHighResolutionDisplayPort(nsIContent* aContent,
nsRect* aResult,
bool* aOutPainted) {
if (StaticPrefs::layers_low_precision_buffer()) {
return GetCriticalDisplayPort(aContent, aResult, aOutPainted);
}
return GetDisplayPort(aContent, aResult, DisplayportRelativeTo::ScrollPort,
aOutPainted);
}
void nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent) {
aContent->RemoveProperty(nsGkAtoms::DisplayPort);
aContent->RemoveProperty(nsGkAtoms::DisplayPortMargins);
}
void nsLayoutUtils::NotifyPaintSkipTransaction(ViewID aScrollId) {
if (nsIScrollableFrame* scrollFrame =
nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
scrollFrame->NotifyApzTransaction();
}
}
nsContainerFrame* nsLayoutUtils::LastContinuationWithChild(
nsContainerFrame* aFrame) {
MOZ_ASSERT(aFrame, "NULL frame pointer");
for (auto f = aFrame->LastContinuation(); f; f = f->GetPrevContinuation()) {
for (const auto& childList : f->ChildLists()) {
if (MOZ_LIKELY(!childList.mList.IsEmpty())) {
return static_cast<nsContainerFrame*>(f);
}
}
}
return aFrame;
}
// static
FrameChildListID nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) {
nsIFrame::ChildListID id = nsIFrame::kPrincipalList;
MOZ_DIAGNOSTIC_ASSERT(!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
if (aChildFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
nsIFrame* pif = aChildFrame->GetPrevInFlow();
if (pif->GetParent() == aChildFrame->GetParent()) {
id = nsIFrame::kExcessOverflowContainersList;
} else {
id = nsIFrame::kOverflowContainersList;
}
} else {
LayoutFrameType childType = aChildFrame->Type();
if (LayoutFrameType::MenuPopup == childType) {
nsIFrame* parent = aChildFrame->GetParent();
MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame");
if (parent) {
if (parent->IsPopupSetFrame()) {
id = nsIFrame::kPopupList;
} else {
nsIFrame* firstPopup =
parent->GetChildList(nsIFrame::kPopupList).FirstChild();
MOZ_ASSERT(
!firstPopup || !firstPopup->GetNextSibling(),
"We assume popupList only has one child, but it has more.");
id = firstPopup == aChildFrame ? nsIFrame::kPopupList
: nsIFrame::kPrincipalList;
}
} else {
id = nsIFrame::kPrincipalList;
}
} else if (LayoutFrameType::TableColGroup == childType) {
id = nsIFrame::kColGroupList;
} else if (aChildFrame->IsTableCaption()) {
id = nsIFrame::kCaptionList;
} else {
id = nsIFrame::kPrincipalList;
}
}
#ifdef DEBUG
// Verify that the frame is actually in that child list or in the
// corresponding overflow list.
nsContainerFrame* parent = aChildFrame->GetParent();
bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
if (!found) {
found = parent->GetChildList(nsIFrame::kOverflowList)
.ContainsFrame(aChildFrame);
MOZ_ASSERT(found, "not in child list");
}
#endif
return id;
}
static Element* GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty) {
MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
aPseudoProperty == nsGkAtoms::afterPseudoProperty ||
aPseudoProperty == nsGkAtoms::markerPseudoProperty);
if (!aContent->MayHaveAnonymousChildren()) {
return nullptr;
}
return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
}
/*static*/
Element* nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) {
return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
}
/*static*/
nsIFrame* nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) {
Element* pseudo = GetBeforePseudo(aContent);
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
/*static*/
Element* nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) {
return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
}
/*static*/
nsIFrame* nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) {
Element* pseudo = GetAfterPseudo(aContent);
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
/*static*/
Element* nsLayoutUtils::GetMarkerPseudo(const nsIContent* aContent) {
return GetPseudo(aContent, nsGkAtoms::markerPseudoProperty);
}
/*static*/
nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) {
Element* pseudo = GetMarkerPseudo(aContent);
return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
}
// static
nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
LayoutFrameType aFrameType,
nsIFrame* aStopAt) {
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
if (frame->Type() == aFrameType) {
return frame;
}
if (frame == aStopAt) {
break;
}
}
return nullptr;
}
/* static */
nsIFrame* nsLayoutUtils::GetPageFrame(nsIFrame* aFrame) {
return GetClosestFrameOfType(aFrame, LayoutFrameType::Page);
}
/* static */
nsIFrame* nsLayoutUtils::GetStyleFrame(nsIFrame* aPrimaryFrame) {
if (aPrimaryFrame->IsTableWrapperFrame()) {
nsIFrame* inner = aPrimaryFrame->PrincipalChildList().FirstChild();
// inner may be null, if aPrimaryFrame is mid-destruction
return inner;
}
return aPrimaryFrame;
}
const nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIFrame* aPrimaryFrame) {
return nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aPrimaryFrame));
}
nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) {
nsIFrame* frame = aContent->GetPrimaryFrame();
if (!frame) {
return nullptr;
}
return nsLayoutUtils::GetStyleFrame(frame);
}
/* static */
nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(nsIFrame* aStyleFrame) {
nsIFrame* parent = aStyleFrame->GetParent();
return parent && parent->IsTableWrapperFrame() ? parent : aStyleFrame;
}
/* static */
const nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
const nsIFrame* aStyleFrame) {
return nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
const_cast<nsIFrame*>(aStyleFrame));
}
/*static*/
bool nsLayoutUtils::IsPrimaryStyleFrame(const nsIFrame* aFrame) {
if (aFrame->IsTableWrapperFrame()) {
return false;
}
const nsIFrame* parent = aFrame->GetParent();
if (parent && parent->IsTableWrapperFrame()) {
return parent->PrincipalChildList().FirstChild() == aFrame;
}
return aFrame->IsPrimaryFrame();
}
nsIFrame* nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
NS_ASSERTION(aFrame->IsPlaceholderFrame(), "Must have a placeholder here");
if (aFrame->HasAnyStateBits(PLACEHOLDER_FOR_FLOAT)) {
nsIFrame* outOfFlowFrame =
nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
NS_ASSERTION(outOfFlowFrame && outOfFlowFrame->IsFloating(),
"How did that happen?");
return outOfFlowFrame;
}
return nullptr;
}
// static
nsIFrame* nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
nsPoint* aExtraOffset) {
nsIFrame* p = aFrame->GetParent();
if (p) {
return p;
}
nsView* v = aFrame->GetView();
if (!v) {
return nullptr;
}
v = v->GetParent(); // anonymous inner view
if (!v) {
return nullptr;
}
v = v->GetParent(); // subdocumentframe's view
if (!v) {
return nullptr;
}
p = v->GetFrame();
if (p && aExtraOffset) {
nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(p);
MOZ_ASSERT(subdocumentFrame);
*aExtraOffset += subdocumentFrame->GetExtraOffset();
}
return p;
}
// static
bool nsLayoutUtils::IsProperAncestorFrameCrossDoc(
const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
const nsIFrame* aCommonAncestor) {
if (aFrame == aAncestorFrame) return false;
return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor);
}
// static
bool nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame,
const nsIFrame* aFrame,
const nsIFrame* aCommonAncestor) {
for (const nsIFrame* f = aFrame; f != aCommonAncestor;
f = GetCrossDocParentFrame(f)) {
if (f == aAncestorFrame) return true;
}
return aCommonAncestor == aAncestorFrame;
}
// static
bool nsLayoutUtils::IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
const nsIFrame* aFrame,
const nsIFrame* aCommonAncestor) {
if (aFrame == aAncestorFrame) return false;
for (const nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
if (f == aAncestorFrame) return true;
}
return aCommonAncestor == aAncestorFrame;
}
// static
int32_t nsLayoutUtils::DoCompareTreePosition(
nsIContent* aContent1, nsIContent* aContent2, int32_t aIf1Ancestor,
int32_t aIf2Ancestor, const nsIContent* aCommonAncestor) {
MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
MOZ_ASSERT(aContent1, "aContent1 must not be null");
MOZ_ASSERT(aContent2, "aContent2 must not be null");
AutoTArray<nsINode*, 32> content1Ancestors;
nsINode* c1;
for (c1 = aContent1; c1 && c1 != aCommonAncestor;
c1 = c1->GetParentOrShadowHostNode()) {
content1Ancestors.AppendElement(c1);
}
if (!c1 && aCommonAncestor) {
// So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
// Never mind. We can continue as if aCommonAncestor was null.
aCommonAncestor = nullptr;
}
AutoTArray<nsINode*, 32> content2Ancestors;
nsINode* c2;
for (c2 = aContent2; c2 && c2 != aCommonAncestor;
c2 = c2->GetParentOrShadowHostNode()) {
content2Ancestors.AppendElement(c2);
}
if (!c2 && aCommonAncestor) {
// So, it turns out aCommonAncestor was not an ancestor of c2.
// We need to retry with no common ancestor hint.
return DoCompareTreePosition(aContent1, aContent2, aIf1Ancestor,
aIf2Ancestor, nullptr);
}
int last1 = content1Ancestors.Length() - 1;
int last2 = content2Ancestors.Length() - 1;
nsINode* content1Ancestor = nullptr;
nsINode* content2Ancestor = nullptr;
while (last1 >= 0 && last2 >= 0 &&
((content1Ancestor = content1Ancestors.ElementAt(last1)) ==
(content2Ancestor = content2Ancestors.ElementAt(last2)))) {
last1--;
last2--;
}
if (last1 < 0) {
if (last2 < 0) {
NS_ASSERTION(aContent1 == aContent2, "internal error?");
return 0;
}
// aContent1 is an ancestor of aContent2
return aIf1Ancestor;
}
if (last2 < 0) {
// aContent2 is an ancestor of aContent1
return aIf2Ancestor;
}
// content1Ancestor != content2Ancestor, so they must be siblings with the
// same parent
nsINode* parent = content1Ancestor->GetParentOrShadowHostNode();
#ifdef DEBUG
// TODO: remove the uglyness, see bug 598468.
NS_ASSERTION(gPreventAssertInCompareTreePosition || parent,
"no common ancestor at all???");
#endif // DEBUG
if (!parent) { // different documents??
return 0;
}
int32_t index1 = parent->ComputeIndexOf(content1Ancestor);
int32_t index2 = parent->ComputeIndexOf(content2Ancestor);
// None of the nodes are anonymous, just do a regular comparison.
if (index1 >= 0 && index2 >= 0) {
return index1 - index2;
}
// Otherwise handle pseudo-element and anonymous content ordering.
//
// ::marker -> ::before -> anon siblings -> regular siblings -> ::after
auto PseudoIndex = [](const nsINode* aNode, int32_t aNodeIndex) -> int32_t {
if (aNodeIndex >= 0) {
return 1; // Not a pseudo.
}
if (aNode->IsContent()) {
if (aNode->AsContent()->IsGeneratedContentContainerForMarker()) {
return -2;
}
if (aNode->AsContent()->IsGeneratedContentContainerForBefore()) {
return -1;
}
if (aNode->AsContent()->IsGeneratedContentContainerForAfter()) {
return 2;
}
}
return 0;
};
return PseudoIndex(content1Ancestor, index1) -
PseudoIndex(content2Ancestor, index2);
}
// static
nsIFrame* nsLayoutUtils::FillAncestors(nsIFrame* aFrame,
nsIFrame* aStopAtAncestor,
nsTArray<nsIFrame*>* aAncestors) {
while (aFrame && aFrame != aStopAtAncestor) {
aAncestors->AppendElement(aFrame);
aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
}
return aFrame;
}
// Return true if aFrame1 is after aFrame2
static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) {
nsIFrame* f = aFrame2;
do {
f = f->GetNextSibling();
if (f == aFrame1) return true;
} while (f);
return false;
}
// static
int32_t nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
nsIFrame* aFrame2,
int32_t aIf1Ancestor,
int32_t aIf2Ancestor,
nsIFrame* aCommonAncestor) {
MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
AutoTArray<nsIFrame*, 20> frame2Ancestors;
nsIFrame* nonCommonAncestor =
FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors);
return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors, aIf1Ancestor,
aIf2Ancestor,
nonCommonAncestor ? aCommonAncestor : nullptr);
}
// static
int32_t nsLayoutUtils::DoCompareTreePosition(
nsIFrame* aFrame1, nsIFrame* aFrame2, nsTArray<nsIFrame*>& aFrame2Ancestors,
int32_t aIf1Ancestor, int32_t aIf2Ancestor, nsIFrame* aCommonAncestor) {
MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
nsPresContext* presContext = aFrame1->PresContext();
if (presContext != aFrame2->PresContext()) {
NS_ERROR("no common ancestor at all, different documents");
return 0;
}
AutoTArray<nsIFrame*, 20> frame1Ancestors;
if (aCommonAncestor &&
!FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) {
// We reached the root of the frame tree ... if aCommonAncestor was set,
// it is wrong
return DoCompareTreePosition(aFrame1, aFrame2, aIf1Ancestor, aIf2Ancestor,
nullptr);
}
int32_t last1 = int32_t(frame1Ancestors.Length()) - 1;
int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1;
while (last1 >= 0 && last2 >= 0 &&
frame1Ancestors[last1] == aFrame2Ancestors[last2]) {
last1--;
last2--;
}
if (last1 < 0) {
if (last2 < 0) {
NS_ASSERTION(aFrame1 == aFrame2, "internal error?");
return 0;
}
// aFrame1 is an ancestor of aFrame2
return aIf1Ancestor;
}
if (last2 < 0) {
// aFrame2 is an ancestor of aFrame1
return aIf2Ancestor;
}
nsIFrame* ancestor1 = frame1Ancestors[last1];
nsIFrame* ancestor2 = aFrame2Ancestors[last2];
// Now we should be able to walk sibling chains to find which one is first
if (IsFrameAfter(ancestor2, ancestor1)) return -1;
if (IsFrameAfter(ancestor1, ancestor2)) return 1;
NS_WARNING("Frames were in different child lists???");
return 0;
}
// static
nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
if (!aFrame) {
return nullptr;
}
nsIFrame* next;
while ((next = aFrame->GetNextSibling()) != nullptr) {
aFrame = next;
}
return aFrame;
}
// static
nsView* nsLayoutUtils::FindSiblingViewFor(nsView* aParentView,
nsIFrame* aFrame) {
nsIFrame* parentViewFrame = aParentView->GetFrame();
nsIContent* parentViewContent =
parentViewFrame ? parentViewFrame->GetContent() : nullptr;
for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore;
insertBefore = insertBefore->GetNextSibling()) {
nsIFrame* f = insertBefore->GetFrame();
if (!f) {
// this view could be some anonymous view attached to a meaningful parent
for (nsView* searchView = insertBefore->GetParent(); searchView;
searchView = searchView->GetParent()) {
f = searchView->GetFrame();
if (f) {
break;
}
}
NS_ASSERTION(f, "Can't find a frame anywhere!");
}
if (!f || !aFrame->GetContent() || !f->GetContent() ||
CompareTreePosition(aFrame->GetContent(), f->GetContent(),