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 "FrameLayerBuilder.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 "Layers.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/DisplayPortUtils.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/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ScopeExit.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/ToString.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;
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,
OverflowAreas& 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) {
OverflowAreas 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().InsertOrUpdate(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;
}
}
nsIFrame* nsLayoutUtils::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;
}
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;
}
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();
}
void nsLayoutUtils::NotifyPaintSkipTransaction(ViewID aScrollId) {
if (nsIScrollableFrame* scrollFrame =
nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
#ifdef DEBUG
nsIFrame* f = do_QueryFrame(scrollFrame);
MOZ_ASSERT(f && f->PresShell() && !f->PresShell()->IsResolutionUpdated());
#endif
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) {
MOZ_ASSERT(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);
}
CSSIntCoord nsLayoutUtils::UnthemedScrollbarSize(StyleScrollbarWidth aWidth) {
switch (aWidth) {
case StyleScrollbarWidth::Auto:
return 12;
case StyleScrollbarWidth::Thin:
return 6;
case StyleScrollbarWidth::None:
return 0;
}
return 0;
}
/* 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(),
parentViewContent) > 0) {
// aFrame's content is after f's content (or we just don't know),
// so put our view before f's view
return insertBefore;
}
}
return nullptr;
}
// static
nsIScrollableFrame* nsLayoutUtils::GetScrollableFrameFor(
const nsIFrame* aScrolledFrame) {
nsIFrame* frame = aScrolledFrame->GetParent();
nsIScrollableFrame* sf = do_QueryFrame(frame);
return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr;
}
/* static */
SideBits nsLayoutUtils::GetSideBitsForFixedPositionContent(
const nsIFrame* aFixedPosFrame) {
return GetSideBitsAndAdjustAnchorForFixedPositionContent(
nullptr, aFixedPosFrame, nullptr, nullptr);
}
/* static */
SideBits nsLayoutUtils::GetSideBitsAndAdjustAnchorForFixedPositionContent(
const nsIFrame* aViewportFrame, const nsIFrame* aFixedPosFrame,
LayerPoint* aAnchor, const Rect* aAnchorRect) {
SideBits sides = SideBits::eNone;
if (aFixedPosFrame != aViewportFrame) {
const nsStylePosition* position = aFixedPosFrame->StylePosition();
if (!position->mOffset.Get(eSideRight).IsAuto()) {
sides |= SideBits::eRight;
if (!position->mOffset.Get(eSideLeft).IsAuto()) {
sides |= SideBits::eLeft;
if (aAnchor) {
aAnchor->x = aAnchorRect->x + aAnchorRect->width / 2.f;
}
} else {
if (aAnchor) {
aAnchor->x = aAnchorRect->XMost();
}
}
} else if (!position->mOffset.Get(eSideLeft).IsAuto()) {
sides |= SideBits::eLeft;
}
if (!position->mOffset.Get(eSideBottom).IsAuto()) {
sides |= SideBits::eBottom;
if (!position->mOffset.Get(eSideTop).IsAuto()) {
sides |= SideBits::eTop;
if (aAnchor) {
aAnchor->y = aAnchorRect->y + aAnchorRect->height / 2.f;
}
} else {
if (aAnchor) {
aAnchor->y = aAnchorRect->YMost();
}
}
} else if (!position->mOffset.Get(eSideTop).IsAuto()) {
sides |= SideBits::eTop;
}
}
return sides;
}
/* static */
void nsLayoutUtils::SetFixedPositionLayerData(
Layer* aLayer, const nsIFrame* aViewportFrame, const nsRect& aAnchorRect,
const nsIFrame* aFixedPosFrame, nsPresContext* aPresContext,
const ContainerLayerParameters& aContainerParameters) {
// Find out the rect of the viewport frame relative to the reference frame.
// This, in conjunction with the container scale, will correspond to the
// coordinate-space of the built layer.
float factor = aPresContext->AppUnitsPerDevPixel();
Rect anchorRect(NSAppUnitsToFloatPixels(aAnchorRect.x, factor) *
aContainerParameters.mXScale,
NSAppUnitsToFloatPixels(aAnchorRect.y, factor) *
aContainerParameters.mYScale,
NSAppUnitsToFloatPixels(aAnchorRect.width, factor) *
aContainerParameters.mXScale,
NSAppUnitsToFloatPixels(aAnchorRect.height, factor) *
aContainerParameters.mYScale);
// Need to transform anchorRect from the container layer's coordinate system
// into aLayer's coordinate system.
Matrix transform2d;
if (aLayer->GetTransform().Is2D(&transform2d)) {
transform2d.Invert();
anchorRect = transform2d.TransformBounds(anchorRect);
} else {
NS_ERROR(
"3D transform found between fixedpos content and its viewport (should "
"never happen)");
anchorRect = Rect(0, 0, 0, 0);
}
// Work out the anchor point for this fixed position layer. We assume that
// any positioning set (left/top/right/bottom) indicates that the
// corresponding side of its container should be the anchor point,
// defaulting to top-left.
LayerPoint anchor(anchorRect.x, anchorRect.y);
SideBits sides = GetSideBitsAndAdjustAnchorForFixedPositionContent(
aViewportFrame, aFixedPosFrame, &anchor, &anchorRect);
ViewID id = ScrollIdForRootScrollFrame(aPresContext);
aLayer->SetFixedPositionData(id, anchor, sides);
}
ScrollableLayerGuid::ViewID nsLayoutUtils::ScrollIdForRootScrollFrame(
nsPresContext* aPresContext) {
ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
if (nsIFrame* rootScrollFrame =
aPresContext->PresShell()->GetRootScrollFrame()) {
if (nsIContent* content = rootScrollFrame->GetContent()) {
id = FindOrCreateIDFor(content);
}
}
return id;
}
// static
nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrameForDirection(
nsIFrame* aFrame, ScrollDirections aDirections) {
NS_ASSERTION(
aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
if (scrollableFrame) {
ScrollDirections directions =
scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
if (aDirections.contains(ScrollDirection::eVertical)) {
if (directions.contains(ScrollDirection::eVertical)) {
return scrollableFrame;
}
}
if (aDirections.contains(ScrollDirection::eHorizontal)) {
if (directions.contains(ScrollDirection::eHorizontal)) {
return scrollableFrame;
}
}
}
}
return nullptr;
}
static nsIFrame* GetNearestScrollableOrOverflowClipFrame(
nsIFrame* aFrame, uint32_t aFlags,
const std::function<bool(const nsIFrame* aCurrentFrame)>& aClipFrameCheck =
nullptr) {
MOZ_ASSERT(
aFrame,
"GetNearestScrollableOrOverflowClipFrame expects a non-null frame");
auto GetNextFrame = [aFlags](const nsIFrame* aFrame) -> nsIFrame* {
if (aFlags & nsLayoutUtils::SCROLLABLE_FOLLOW_OOF_TO_PLACEHOLDER) {
return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
? nsLayoutUtils::GetParentOrPlaceholderFor(aFrame)
: nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(aFrame);
}
return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
? aFrame->GetParent()
: nsLayoutUtils::GetCrossDocParentFrame(aFrame);
};
for (nsIFrame* f = aFrame; f; f = GetNextFrame(f)) {
if (aClipFrameCheck && aClipFrameCheck(f)) {
return f;
}
if ((aFlags & nsLayoutUtils::SCROLLABLE_STOP_AT_PAGE) && f->IsPageFrame()) {
break;
}
if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(f)) {
if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) {
if (scrollableFrame->WantAsyncScroll()) {
return f;
}
} else {
ScrollStyles ss = scrollableFrame->GetScrollStyles();
if ((aFlags & nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) ||
ss.mVertical != StyleOverflow::Hidden ||
ss.mHorizontal != StyleOverflow::Hidden) {
return f;
}
}
if (aFlags & nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT) {
PresShell* presShell = f->PresShell();
if (presShell->GetRootScrollFrame() == f && presShell->GetDocument() &&
presShell->GetDocument()->IsRootDisplayDocument()) {
return f;
}
}
}
if ((aFlags & nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(f)) {
return f->PresShell()->GetRootScrollFrame();
}
}
return nullptr;
}
// static
nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame,
uint32_t aFlags) {
nsIFrame* found = GetNearestScrollableOrOverflowClipFrame(aFrame, aFlags);
if (!found) {
return nullptr;
}
return do_QueryFrame(found);
}
// static
nsIFrame* nsLayoutUtils::GetNearestOverflowClipFrame(nsIFrame* aFrame) {
return GetNearestScrollableOrOverflowClipFrame(
aFrame, SCROLLABLE_SAME_DOC | SCROLLABLE_INCLUDE_HIDDEN,
[](const nsIFrame* currentFrame) -> bool {
// In cases of SVG Inner/Outer frames it basically clips descendants
// unless overflow: visible is explicitly specified.
LayoutFrameType type = currentFrame->Type();
return ((type == LayoutFrameType::SVGOuterSVG ||
type == LayoutFrameType::SVGInnerSVG) &&
(currentFrame->StyleDisplay()->mOverflowX !=
StyleOverflow::Visible &&
currentFrame->StyleDisplay()->mOverflowY !=
StyleOverflow::Visible));
});
}
// static
nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
const nsRect& aScrolledFrameOverflowArea,
const nsSize& aScrollPortSize,
StyleDirection aDirection) {
WritingMode wm = aScrolledFrame->GetWritingMode();
// Potentially override the frame's direction to use the direction found
// by ScrollFrameHelper::GetScrolledFrameDir()
wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl ? 1 : 0);
nscoord x1 = aScrolledFrameOverflowArea.x,
x2 = aScrolledFrameOverflowArea.XMost(),
y1 = aScrolledFrameOverflowArea.y,
y2 = aScrolledFrameOverflowArea.YMost();
const bool isHorizontalWM = !wm.IsVertical();
const bool isVerticalWM = wm.IsVertical();
bool isInlineFlowFromTopOrLeft = !wm.IsInlineReversed();
bool isBlockFlowFromTopOrLeft = isHorizontalWM || wm.IsVerticalLR();
if (aScrolledFrame->IsFlexContainerFrame()) {
// In a flex container, the children flow (and overflow) along the flex
// container's main axis and cross axis. These are analogous to the
// inline/block axes, and by default they correspond exactly to those axes;
// but the flex container's CSS (e.g. flex-direction: column-reverse) may
// have swapped and/or reversed them, and we need to account for that here.
FlexboxAxisInfo info(aScrolledFrame);
if (info.mIsRowOriented) {
// The flex container's inline axis is the main axis.
isInlineFlowFromTopOrLeft =
isInlineFlowFromTopOrLeft == !info.mIsMainAxisReversed;
isBlockFlowFromTopOrLeft =
isBlockFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
} else {
// The flex container's block axis is the main axis.
isBlockFlowFromTopOrLeft =
isBlockFlowFromTopOrLeft == !info.mIsMainAxisReversed;
isInlineFlowFromTopOrLeft =
isInlineFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
}
}
// Clamp the horizontal start-edge (x1 or x2, depending whether the logical
// axis that corresponds to horizontal progresses from left-to-right or
// right-to-left).
if ((isHorizontalWM && isInlineFlowFromTopOrLeft) ||
(isVerticalWM && isBlockFlowFromTopOrLeft)) {
if (x1 < 0) {
x1 = 0;
}
} else {
if (x2 > aScrollPortSize.width) {
x2 = aScrollPortSize.width;
}
// When the scrolled frame chooses a size larger than its available width
// (because its padding alone is larger than the available width), we need
// to keep the start-edge of the scroll frame anchored to the start-edge of
// the scrollport.
// When the scrolled frame is RTL, this means moving it in our left-based
// coordinate system, so we need to compensate for its extra width here by
// effectively repositioning the frame.
nscoord extraWidth =
std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
x2 += extraWidth;
}
// Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
// logical axis that corresponds to vertical progresses from top-to-bottom or
// buttom-to-top).
if ((isHorizontalWM && isBlockFlowFromTopOrLeft) ||
(isVerticalWM && isInlineFlowFromTopOrLeft)) {
if (y1 < 0) {
y1 = 0;
}
} else {
if (y2 > aScrollPortSize.height) {
y2 = aScrollPortSize.height;
}
nscoord extraHeight =
std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
y2 += extraHeight;
}
return nsRect(x1, y1, x2 - x1, y2 - y1);
}
// static
bool nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
ComputedStyle* aComputedStyle,
PseudoStyleType aPseudoElement,
nsPresContext* aPresContext) {
MOZ_ASSERT(aPresContext, "Must have a prescontext");
RefPtr<ComputedStyle> pseudoContext;
if (aContent) {
pseudoContext = aPresContext->StyleSet()->ProbePseudoElementStyle(
*aContent->AsElement(), aPseudoElement, aComputedStyle);
}
return pseudoContext != nullptr;
}
nsPoint nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(Event* aDOMEvent,
nsIFrame* aFrame) {
if (!aDOMEvent) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
WidgetEvent* event = aDOMEvent->WidgetEventPtr();
if (!event) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
return GetEventCoordinatesRelativeTo(event, RelativeTo{aFrame});
}
nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
RelativeTo aFrame) {
if (!aEvent || (aEvent->mClass != eMouseEventClass &&
aEvent->mClass != eMouseScrollEventClass &&
aEvent->mClass != eWheelEventClass &&
aEvent->mClass != eDragEventClass &&
aEvent->mClass != eSimpleGestureEventClass &&
aEvent->mClass != ePointerEventClass &&
aEvent->mClass != eGestureNotifyEventClass &&
aEvent->mClass != eTouchEventClass &&
aEvent->mClass != eQueryContentEventClass))
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
return GetEventCoordinatesRelativeTo(aEvent, aEvent->AsGUIEvent()->mRefPoint,
aFrame);
}
nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
const WidgetEvent* aEvent, const LayoutDeviceIntPoint& aPoint,
RelativeTo aFrame) {
if (!aFrame.mFrame) {
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
}
nsIWidget* widget = aEvent->AsGUIEvent()->mWidget;
if (!widget) {
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
}
return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame);
}
nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
const LayoutDeviceIntPoint& aPoint,
RelativeTo aFrame) {
const nsIFrame* frame = aFrame.mFrame;
if (!frame || !aWidget) {
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
}
nsView* view = frame->GetView();
if (view) {
nsIWidget* frameWidget = view->GetWidget();
if (frameWidget && frameWidget == aWidget) {
// Special case this cause it happens a lot.
// This also fixes bug 664707, events in the extra-special case of select
// dropdown popups that are transformed.
nsPresContext* presContext = frame->PresContext();
nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
presContext->DevPixelsToAppUnits(aPoint.y));
return pt - view->ViewToWidgetOffset();
}
}
/* If we walk up the frame tree and discover that any of the frames are
* transformed, we need to do extra work to convert from the global
* space to the local space.
*/
const nsIFrame* rootFrame = frame;
bool transformFound = false;
for (const nsIFrame* f = frame; f;
f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
if (f->