From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- layout/base/nsLayoutUtils.cpp | 9186 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 9186 insertions(+) create mode 100644 layout/base/nsLayoutUtils.cpp (limited to 'layout/base/nsLayoutUtils.cpp') diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp new file mode 100644 index 000000000..ed34f39ce --- /dev/null +++ b/layout/base/nsLayoutUtils.cpp @@ -0,0 +1,9186 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* 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 "mozilla/ArrayUtils.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EffectSet.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/layers/PAPZ.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Unused.h" +#include "nsCharTraits.h" +#include "nsFontMetrics.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsHtml5Atoms.h" +#include "nsIAtom.h" +#include "nsCaret.h" +#include "nsCSSPseudoElements.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSColorUtils.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsPlaceholderFrame.h" +#include "nsIScrollableFrame.h" +#include "nsIDOMEvent.h" +#include "nsDisplayList.h" +#include "nsRegion.h" +#include "nsFrameManager.h" +#include "nsBlockFrame.h" +#include "nsBidiPresUtils.h" +#include "imgIContainer.h" +#include "ImageOps.h" +#include "ImageRegion.h" +#include "gfxRect.h" +#include "gfxContext.h" +#include "nsRenderingContext.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsCSSRendering.h" +#include "nsTextFragment.h" +#include "nsThemeConstants.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIWidget.h" +#include "gfxMatrix.h" +#include "gfxPrefs.h" +#include "gfxTypes.h" +#include "nsTArray.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "nsICanvasRenderingContextInternal.h" +#include "gfxPlatform.h" +#include +#include +#include "mozilla/dom/HTMLVideoElement.h" +#include "mozilla/dom/HTMLImageElement.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/DOMStringList.h" +#include "mozilla/dom/KeyframeEffectReadOnly.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "imgIRequest.h" +#include "nsIImageLoadingContent.h" +#include "nsCOMPtr.h" +#include "nsCSSProps.h" +#include "nsListControlFrame.h" +#include "mozilla/dom/Element.h" +#include "nsCanvasFrame.h" +#include "gfxDrawable.h" +#include "gfxEnv.h" +#include "gfxUtils.h" +#include "nsDataHashtable.h" +#include "nsTableWrapperFrame.h" +#include "nsTextFrame.h" +#include "nsFontFaceList.h" +#include "nsFontInflationData.h" +#include "nsSVGUtils.h" +#include "SVGImageContext.h" +#include "SVGTextFrame.h" +#include "nsStyleStructInlines.h" +#include "nsStyleTransformMatrix.h" +#include "nsIFrameInlines.h" +#include "ImageContainer.h" +#include "nsComputedDOMStyle.h" +#include "ActiveLayerTracker.h" +#include "mozilla/gfx/2D.h" +#include "gfx2DGlue.h" +#include "mozilla/LookAndFeel.h" +#include "UnitTransforms.h" +#include "TiledLayerBuffer.h" // For TILEDLAYERBUFFER_TILE_SIZE +#include "ClientLayerManager.h" +#include "nsRefreshDriver.h" +#include "nsIContentViewer.h" +#include "LayersLogging.h" +#include "mozilla/Preferences.h" +#include "nsFrameSelection.h" +#include "FrameLayerBuilder.h" +#include "mozilla/layers/APZCTreeManager.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/Telemetry.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/RuleNodeCacheConditions.h" +#include "mozilla/StyleSetHandle.h" +#include "mozilla/StyleSetHandleInlines.h" +#include "RegionBuilder.h" + +#ifdef MOZ_XUL +#include "nsXULPopupManager.h" +#endif + +#include "GeckoProfiler.h" +#include "nsAnimationManager.h" +#include "nsTransitionManager.h" +#include "mozilla/RestyleManagerHandle.h" +#include "mozilla/RestyleManagerHandleInlines.h" +#include "LayoutLogging.h" + +// Make sure getpid() works. +#ifdef XP_WIN +#include +#define getpid _getpid +#else +#include +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::image; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::gfx; + +#define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" +#define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled" +#define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit" +#define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled" +#define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled" +#define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled" +#define BG_CLIP_TEXT_ENABLED_PREF_NAME "layout.css.background-clip-text.enabled" + +// The time in number of frames that we estimate for a refresh driver +// to be quiescent +#define DEFAULT_QUIESCENT_FRAMES 2 +// The time (milliseconds) we estimate is needed between the end of an +// idle time and the next Tick. +#define DEFAULT_IDLE_PERIOD_TIME_LIMIT 1.0f + +#ifdef DEBUG +// TODO: remove, see bug 598468. +bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false; +#endif // DEBUG + +typedef FrameMetrics::ViewID ViewID; +typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; + +/* static */ uint32_t nsLayoutUtils::sFontSizeInflationEmPerLine; +/* static */ uint32_t nsLayoutUtils::sFontSizeInflationMinTwips; +/* static */ uint32_t nsLayoutUtils::sFontSizeInflationLineThreshold; +/* static */ int32_t nsLayoutUtils::sFontSizeInflationMappingIntercept; +/* static */ uint32_t nsLayoutUtils::sFontSizeInflationMaxRatio; +/* static */ bool nsLayoutUtils::sFontSizeInflationForceEnabled; +/* static */ bool nsLayoutUtils::sFontSizeInflationDisabledInMasterProcess; +/* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled; +/* static */ bool nsLayoutUtils::sCSSVariablesEnabled; +/* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled; +/* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled; +/* static */ bool nsLayoutUtils::sTextCombineUprightDigitsEnabled; +#ifdef MOZ_STYLO +/* static */ bool nsLayoutUtils::sStyloEnabled; +#endif +/* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit; +/* static */ uint32_t nsLayoutUtils::sQuiescentFramesBeforeIdlePeriod; + +static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID; + +typedef nsDataHashtable ContentMap; +static ContentMap* sContentMap = nullptr; +static ContentMap& GetContentMap() { + if (!sContentMap) { + sContentMap = new ContentMap(); + } + return *sContentMap; +} + +// When the pref "layout.css.grid.enabled" changes, this function is invoked +// to let us update kDisplayKTable, to selectively disable or restore the +// entries for "grid" and "inline-grid" in that table. +static void +GridEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + MOZ_ASSERT(strncmp(aPrefName, GRID_ENABLED_PREF_NAME, + ArrayLength(GRID_ENABLED_PREF_NAME)) == 0, + "We only registered this callback for a single pref, so it " + "should only be called for that pref"); + + static int32_t sIndexOfGridInDisplayTable; + static int32_t sIndexOfInlineGridInDisplayTable; + static bool sAreGridKeywordIndicesInitialized; // initialized to false + + bool isGridEnabled = + Preferences::GetBool(GRID_ENABLED_PREF_NAME, false); + if (!sAreGridKeywordIndicesInitialized) { + // First run: find the position of "grid" and "inline-grid" in + // kDisplayKTable. + sIndexOfGridInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_grid, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfGridInDisplayTable >= 0, + "Couldn't find grid in kDisplayKTable"); + sIndexOfInlineGridInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_grid, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfInlineGridInDisplayTable >= 0, + "Couldn't find inline-grid in kDisplayKTable"); + sAreGridKeywordIndicesInitialized = true; + } + + // OK -- now, stomp on or restore the "grid" entries in kDisplayKTable, + // depending on whether the grid pref is enabled vs. disabled. + if (sIndexOfGridInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfGridInDisplayTable].mKeyword = + isGridEnabled ? eCSSKeyword_grid : eCSSKeyword_UNKNOWN; + } + if (sIndexOfInlineGridInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfInlineGridInDisplayTable].mKeyword = + isGridEnabled ? eCSSKeyword_inline_grid : eCSSKeyword_UNKNOWN; + } +} + +// When the pref "layout.css.prefixes.webkit" changes, this function is invoked +// to let us update kDisplayKTable, to selectively disable or restore the +// entries for "-webkit-box" and "-webkit-inline-box" in that table. +static void +WebkitPrefixEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + MOZ_ASSERT(strncmp(aPrefName, WEBKIT_PREFIXES_ENABLED_PREF_NAME, + ArrayLength(WEBKIT_PREFIXES_ENABLED_PREF_NAME)) == 0, + "We only registered this callback for a single pref, so it " + "should only be called for that pref"); + + static int32_t sIndexOfWebkitBoxInDisplayTable; + static int32_t sIndexOfWebkitInlineBoxInDisplayTable; + static int32_t sIndexOfWebkitFlexInDisplayTable; + static int32_t sIndexOfWebkitInlineFlexInDisplayTable; + + static bool sAreKeywordIndicesInitialized; // initialized to false + + bool isWebkitPrefixSupportEnabled = + Preferences::GetBool(WEBKIT_PREFIXES_ENABLED_PREF_NAME, false); + if (!sAreKeywordIndicesInitialized) { + // First run: find the position of the keywords in kDisplayKTable. + sIndexOfWebkitBoxInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_box, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfWebkitBoxInDisplayTable >= 0, + "Couldn't find -webkit-box in kDisplayKTable"); + sIndexOfWebkitInlineBoxInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_inline_box, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfWebkitInlineBoxInDisplayTable >= 0, + "Couldn't find -webkit-inline-box in kDisplayKTable"); + + sIndexOfWebkitFlexInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_flex, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfWebkitFlexInDisplayTable >= 0, + "Couldn't find -webkit-flex in kDisplayKTable"); + sIndexOfWebkitInlineFlexInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword__webkit_inline_flex, + nsCSSProps::kDisplayKTable); + MOZ_ASSERT(sIndexOfWebkitInlineFlexInDisplayTable >= 0, + "Couldn't find -webkit-inline-flex in kDisplayKTable"); + sAreKeywordIndicesInitialized = true; + } + + // OK -- now, stomp on or restore the "-webkit-{box|flex}" entries in + // kDisplayKTable, depending on whether the webkit prefix pref is enabled + // vs. disabled. + if (sIndexOfWebkitBoxInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfWebkitBoxInDisplayTable].mKeyword = + isWebkitPrefixSupportEnabled ? + eCSSKeyword__webkit_box : eCSSKeyword_UNKNOWN; + } + if (sIndexOfWebkitInlineBoxInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfWebkitInlineBoxInDisplayTable].mKeyword = + isWebkitPrefixSupportEnabled ? + eCSSKeyword__webkit_inline_box : eCSSKeyword_UNKNOWN; + } + if (sIndexOfWebkitFlexInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfWebkitFlexInDisplayTable].mKeyword = + isWebkitPrefixSupportEnabled ? + eCSSKeyword__webkit_flex : eCSSKeyword_UNKNOWN; + } + if (sIndexOfWebkitInlineFlexInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfWebkitInlineFlexInDisplayTable].mKeyword = + isWebkitPrefixSupportEnabled ? + eCSSKeyword__webkit_inline_flex : eCSSKeyword_UNKNOWN; + } +} + +// When the pref "layout.css.display-contents.enabled" changes, this function is +// invoked to let us update kDisplayKTable, to selectively disable or restore +// the entries for "contents" in that table. +static void +DisplayContentsEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + NS_ASSERTION(strcmp(aPrefName, DISPLAY_CONTENTS_ENABLED_PREF_NAME) == 0, + "Did you misspell " DISPLAY_CONTENTS_ENABLED_PREF_NAME " ?"); + + static bool sIsDisplayContentsKeywordIndexInitialized; + static int32_t sIndexOfContentsInDisplayTable; + bool isDisplayContentsEnabled = + Preferences::GetBool(DISPLAY_CONTENTS_ENABLED_PREF_NAME, false); + + if (!sIsDisplayContentsKeywordIndexInitialized) { + // First run: find the position of "contents" in kDisplayKTable. + sIndexOfContentsInDisplayTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_contents, + nsCSSProps::kDisplayKTable); + sIsDisplayContentsKeywordIndexInitialized = true; + } + + // OK -- now, stomp on or restore the "contents" entry in kDisplayKTable, + // depending on whether the pref is enabled vs. disabled. + if (sIndexOfContentsInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfContentsInDisplayTable].mKeyword = + isDisplayContentsEnabled ? eCSSKeyword_contents : eCSSKeyword_UNKNOWN; + } +} + +// When the pref "layout.css.text-align-unsafe-value.enabled" changes, this +// function is called to let us update kTextAlignKTable & kTextAlignLastKTable, +// to selectively disable or restore the entries for "unsafe" in those tables. +static void +TextAlignUnsafeEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + NS_ASSERTION(strcmp(aPrefName, TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME) == 0, + "Did you misspell " TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME " ?"); + + static bool sIsInitialized; + static int32_t sIndexOfUnsafeInTextAlignTable; + static int32_t sIndexOfUnsafeInTextAlignLastTable; + bool isTextAlignUnsafeEnabled = + Preferences::GetBool(TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME, false); + + if (!sIsInitialized) { + // First run: find the position of "unsafe" in kTextAlignKTable. + sIndexOfUnsafeInTextAlignTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_unsafe, + nsCSSProps::kTextAlignKTable); + // First run: find the position of "unsafe" in kTextAlignLastKTable. + sIndexOfUnsafeInTextAlignLastTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_unsafe, + nsCSSProps::kTextAlignLastKTable); + sIsInitialized = true; + } + + // OK -- now, stomp on or restore the "unsafe" entry in the keyword tables, + // depending on whether the pref is enabled vs. disabled. + MOZ_ASSERT(sIndexOfUnsafeInTextAlignTable >= 0); + nsCSSProps::kTextAlignKTable[sIndexOfUnsafeInTextAlignTable].mKeyword = + isTextAlignUnsafeEnabled ? eCSSKeyword_unsafe : eCSSKeyword_UNKNOWN; + MOZ_ASSERT(sIndexOfUnsafeInTextAlignLastTable >= 0); + nsCSSProps::kTextAlignLastKTable[sIndexOfUnsafeInTextAlignLastTable].mKeyword = + isTextAlignUnsafeEnabled ? eCSSKeyword_unsafe : eCSSKeyword_UNKNOWN; +} + +// When the pref "layout.css.float-logical-values.enabled" changes, this +// function is called to let us update kFloatKTable & kClearKTable, +// to selectively disable or restore the entries for logical values +// (inline-start and inline-end) in those tables. +static void +FloatLogicalValuesEnabledPrefChangeCallback(const char* aPrefName, + void* aClosure) +{ + NS_ASSERTION(strcmp(aPrefName, FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME) == 0, + "Did you misspell " FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME " ?"); + + static bool sIsInitialized; + static int32_t sIndexOfInlineStartInFloatTable; + static int32_t sIndexOfInlineEndInFloatTable; + static int32_t sIndexOfInlineStartInClearTable; + static int32_t sIndexOfInlineEndInClearTable; + bool isFloatLogicalValuesEnabled = + Preferences::GetBool(FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME, false); + + if (!sIsInitialized) { + // First run: find the position of "inline-start" in kFloatKTable. + sIndexOfInlineStartInFloatTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_start, + nsCSSProps::kFloatKTable); + // First run: find the position of "inline-end" in kFloatKTable. + sIndexOfInlineEndInFloatTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_end, + nsCSSProps::kFloatKTable); + // First run: find the position of "inline-start" in kClearKTable. + sIndexOfInlineStartInClearTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_start, + nsCSSProps::kClearKTable); + // First run: find the position of "inline-end" in kClearKTable. + sIndexOfInlineEndInClearTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_inline_end, + nsCSSProps::kClearKTable); + sIsInitialized = true; + } + + // OK -- now, stomp on or restore the logical entries in the keyword tables, + // depending on whether the pref is enabled vs. disabled. + MOZ_ASSERT(sIndexOfInlineStartInFloatTable >= 0); + nsCSSProps::kFloatKTable[sIndexOfInlineStartInFloatTable].mKeyword = + isFloatLogicalValuesEnabled ? eCSSKeyword_inline_start : eCSSKeyword_UNKNOWN; + MOZ_ASSERT(sIndexOfInlineEndInFloatTable >= 0); + nsCSSProps::kFloatKTable[sIndexOfInlineEndInFloatTable].mKeyword = + isFloatLogicalValuesEnabled ? eCSSKeyword_inline_end : eCSSKeyword_UNKNOWN; + MOZ_ASSERT(sIndexOfInlineStartInClearTable >= 0); + nsCSSProps::kClearKTable[sIndexOfInlineStartInClearTable].mKeyword = + isFloatLogicalValuesEnabled ? eCSSKeyword_inline_start : eCSSKeyword_UNKNOWN; + MOZ_ASSERT(sIndexOfInlineEndInClearTable >= 0); + nsCSSProps::kClearKTable[sIndexOfInlineEndInClearTable].mKeyword = + isFloatLogicalValuesEnabled ? eCSSKeyword_inline_end : eCSSKeyword_UNKNOWN; +} + + +// When the pref "layout.css.background-clip-text.enabled" changes, this +// function is invoked to let us update kBackgroundClipKTable, to selectively +// disable or restore the entries for "text" in that table. +static void +BackgroundClipTextEnabledPrefChangeCallback(const char* aPrefName, + void* aClosure) +{ + NS_ASSERTION(strcmp(aPrefName, BG_CLIP_TEXT_ENABLED_PREF_NAME) == 0, + "Did you misspell " BG_CLIP_TEXT_ENABLED_PREF_NAME " ?"); + + static bool sIsBGClipKeywordIndexInitialized; + static int32_t sIndexOfTextInBGClipTable; + bool isBGClipTextEnabled = + Preferences::GetBool(BG_CLIP_TEXT_ENABLED_PREF_NAME, false); + + if (!sIsBGClipKeywordIndexInitialized) { + // First run: find the position of "text" in kBackgroundClipKTable. + sIndexOfTextInBGClipTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_text, + nsCSSProps::kBackgroundClipKTable); + + sIsBGClipKeywordIndexInitialized = true; + } + + // OK -- now, stomp on or restore the "text" entry in kBackgroundClipKTable, + // depending on whether the pref is enabled vs. disabled. + if (sIndexOfTextInBGClipTable >= 0) { + nsCSSProps::kBackgroundClipKTable[sIndexOfTextInBGClipTable].mKeyword = + isBGClipTextEnabled ? eCSSKeyword_text : eCSSKeyword_UNKNOWN; + } +} + +template +static bool +HasMatchingAnimations(const nsIFrame* aFrame, TestType&& aTest) +{ + EffectSet* effects = EffectSet::GetEffectSet(aFrame); + if (!effects) { + return false; + } + + for (KeyframeEffectReadOnly* effect : *effects) { + if (aTest(*effect)) { + return true; + } + } + + return false; +} + +bool +nsLayoutUtils::HasCurrentTransitions(const nsIFrame* aFrame) +{ + return HasMatchingAnimations(aFrame, + [](KeyframeEffectReadOnly& aEffect) + { + // Since |aEffect| is current, it must have an associated Animation + // so we don't need to null-check the result of GetAnimation(). + return aEffect.IsCurrent() && aEffect.GetAnimation()->AsCSSTransition(); + } + ); +} + +bool +nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame, + nsCSSPropertyID aProperty) +{ + return HasMatchingAnimations(aFrame, + [&aProperty](KeyframeEffectReadOnly& aEffect) + { + return (aEffect.IsInEffect() || aEffect.IsCurrent()) && + aEffect.HasAnimationOfProperty(aProperty); + } + ); +} + +bool +nsLayoutUtils::HasEffectiveAnimation(const nsIFrame* aFrame, + nsCSSPropertyID aProperty) +{ + return HasMatchingAnimations(aFrame, + [&aProperty](KeyframeEffectReadOnly& aEffect) + { + return (aEffect.IsInEffect() || aEffect.IsCurrent()) && + aEffect.HasEffectiveAnimationOfProperty(aProperty); + } + ); +} + +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); +} + +static void +GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame, + nsTArray>& + aAnimations, + gfxSize& aMaxScale, + gfxSize& aMinScale) +{ + 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()); + + dom::KeyframeEffectReadOnly* effect = + anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr; + MOZ_ASSERT(effect, "A playing animation should have a keyframe effect"); + for (size_t propIdx = effect->Properties().Length(); propIdx-- != 0; ) { + const AnimationProperty& prop = effect->Properties()[propIdx]; + if (prop.mProperty == eCSSProperty_transform) { + for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) { + const AnimationPropertySegment& segment = prop.mSegments[segIdx]; + gfxSize from = segment.mFromValue.GetScaleValue(aFrame); + aMaxScale.width = std::max(aMaxScale.width, from.width); + aMaxScale.height = std::max(aMaxScale.height, from.height); + aMinScale.width = std::min(aMinScale.width, from.width); + aMinScale.height = std::min(aMinScale.height, from.height); + gfxSize to = segment.mToValue.GetScaleValue(aFrame); + aMaxScale.width = std::max(aMaxScale.width, to.width); + aMaxScale.height = std::max(aMaxScale.height, to.height); + aMinScale.width = std::min(aMinScale.width, to.width); + aMinScale.height = std::min(aMinScale.height, to.height); + } + } + } + } +} + +gfxSize +nsLayoutUtils::ComputeSuitableScaleForAnimation(const nsIFrame* aFrame, + const nsSize& aVisibleSize, + const nsSize& aDisplaySize) +{ + gfxSize maxScale(std::numeric_limits::min(), + std::numeric_limits::min()); + gfxSize minScale(std::numeric_limits::max(), + std::numeric_limits::max()); + + nsTArray> compositorAnimations = + EffectCompositor::GetAnimationsForCompositor(aFrame, + eCSSProperty_transform); + GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations, + maxScale, minScale); + + if (maxScale.width == std::numeric_limits::min()) { + // We didn't encounter a transform + return gfxSize(1.0, 1.0); + } + + return gfxSize(GetSuitableScale(maxScale.width, minScale.width, + aVisibleSize.width, aDisplaySize.width), + GetSuitableScale(maxScale.height, minScale.height, + aVisibleSize.height, aDisplaySize.height)); +} + +bool +nsLayoutUtils::AreAsyncAnimationsEnabled() +{ + static bool sAreAsyncAnimationsEnabled; + static bool sAsyncPrefCached = false; + + if (!sAsyncPrefCached) { + sAsyncPrefCached = true; + Preferences::AddBoolVarCache(&sAreAsyncAnimationsEnabled, + "layers.offmainthreadcomposition.async-animations"); + } + + return sAreAsyncAnimationsEnabled && + gfxPlatform::OffMainThreadCompositingEnabled(); +} + +bool +nsLayoutUtils::IsAnimationLoggingEnabled() +{ + static bool sShouldLog; + static bool sShouldLogPrefCached; + + if (!sShouldLogPrefCached) { + sShouldLogPrefCached = true; + Preferences::AddBoolVarCache(&sShouldLog, + "layers.offmainthreadcomposition.log-animations"); + } + + return sShouldLog; +} + +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; +} + +bool +nsLayoutUtils::AnimatedImageLayersEnabled() +{ + static bool sAnimatedImageLayersEnabled; + static bool sAnimatedImageLayersPrefCached = false; + + if (!sAnimatedImageLayersPrefCached) { + sAnimatedImageLayersPrefCached = true; + Preferences::AddBoolVarCache(&sAnimatedImageLayersEnabled, + "layout.animated-image-layers.enabled", + false); + } + + return sAnimatedImageLayersEnabled; +} + +bool +nsLayoutUtils::CSSFiltersEnabled() +{ + static bool sCSSFiltersEnabled; + static bool sCSSFiltersPrefCached = false; + + if (!sCSSFiltersPrefCached) { + sCSSFiltersPrefCached = true; + Preferences::AddBoolVarCache(&sCSSFiltersEnabled, + "layout.css.filters.enabled", + false); + } + + return sCSSFiltersEnabled; +} + +bool +nsLayoutUtils::CSSClipPathShapesEnabled() +{ + static bool sCSSClipPathShapesEnabled; + static bool sCSSClipPathShapesPrefCached = false; + + if (!sCSSClipPathShapesPrefCached) { + sCSSClipPathShapesPrefCached = true; + Preferences::AddBoolVarCache(&sCSSClipPathShapesEnabled, + "layout.css.clip-path-shapes.enabled", + false); + } + + return sCSSClipPathShapesEnabled; +} + +bool +nsLayoutUtils::UnsetValueEnabled() +{ + static bool sUnsetValueEnabled; + static bool sUnsetValuePrefCached = false; + + if (!sUnsetValuePrefCached) { + sUnsetValuePrefCached = true; + Preferences::AddBoolVarCache(&sUnsetValueEnabled, + "layout.css.unset-value.enabled", + false); + } + + return sUnsetValueEnabled; +} + +bool +nsLayoutUtils::IsGridTemplateSubgridValueEnabled() +{ + static bool sGridTemplateSubgridValueEnabled; + static bool sGridTemplateSubgridValueEnabledPrefCached = false; + + if (!sGridTemplateSubgridValueEnabledPrefCached) { + sGridTemplateSubgridValueEnabledPrefCached = true; + Preferences::AddBoolVarCache(&sGridTemplateSubgridValueEnabled, + GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME, + false); + } + + return sGridTemplateSubgridValueEnabled; +} + +bool +nsLayoutUtils::IsTextAlignUnsafeValueEnabled() +{ + static bool sTextAlignUnsafeValueEnabled; + static bool sTextAlignUnsafeValueEnabledPrefCached = false; + + if (!sTextAlignUnsafeValueEnabledPrefCached) { + sTextAlignUnsafeValueEnabledPrefCached = true; + Preferences::AddBoolVarCache(&sTextAlignUnsafeValueEnabled, + TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME, + false); + } + + return sTextAlignUnsafeValueEnabled; +} + +void +nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame, + nsOverflowAreas& aOverflowAreas, + FrameChildListIDs aSkipChildLists) +{ + // Iterate over all children except pop-ups. + FrameChildListIDs skip = aSkipChildLists | + nsIFrame::kSelectPopupList | nsIFrame::kPopupList; + for (nsIFrame::ChildListIterator childLists(aFrame); + !childLists.IsDone(); childLists.Next()) { + if (skip.Contains(childLists.CurrentID())) { + continue; + } + + nsFrameList children = childLists.CurrentList(); + for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) { + nsIFrame* child = e.get(); + nsOverflowAreas childOverflow = + child->GetOverflowAreas() + child->GetPosition(); + aOverflowAreas.UnionWith(childOverflow); + } + } +} + +static void DestroyViewID(void* aObject, nsIAtom* aPropertyName, + void* aPropertyValue, void* aData) +{ + ViewID* id = static_cast(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(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 != FrameMetrics::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* +GetScrollFrameFromContent(nsIContent* aContent) +{ + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (aContent->OwnerDoc()->GetRootElement() == aContent) { + nsIPresShell* presShell = frame ? frame->PresContext()->PresShell() : nullptr; + if (!presShell) { + presShell = aContent->OwnerDoc()->GetShell(); + } + // 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(ViewID aId) +{ + nsIContent* content = FindContentFor(aId); + if (!content) { + return nullptr; + } + + nsIFrame* scrollFrame = GetScrollFrameFromContent(content); + return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr; +} + +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(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; + } + + nsIFrame *frame = nsLayoutUtils::GetDisplayRootFrame(aFrame); + nsIWidget* widget = frame->GetNearestWidget(); + if (!widget) { + return false; + } + return widget->AsyncPanZoomEnabled(); +} + +float +nsLayoutUtils::GetCurrentAPZResolutionScale(nsIPresShell* aShell) { + return aShell ? aShell->GetCumulativeNonRootScaleResolution() : 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) +{ + MOZ_ASSERT(!gfxPrefs::LayersTilesEnabled(), "Do not clamp displayports if tiling is enabled"); + + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (!frame) { + return nscoord_MAX; + } + frame = nsLayoutUtils::GetDisplayRootFrame(frame); + + nsIWidget* widget = frame->GetNearestWidget(); + if (!widget) { + return nscoord_MAX; + } + LayerManager* lm = widget->GetLayerManager(); + if (!lm) { + return nscoord_MAX; + } + nsPresContext* presContext = frame->PresContext(); + + int32_t maxSizeInDevPixels = lm->GetMaxTextureSize(); + if (maxSizeInDevPixels < 0 || maxSizeInDevPixels == INT_MAX) { + return nscoord_MAX; + } + 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(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; + + if (APZCCallbackHelper::IsDisplayportSuppressed()) { + alignment = ScreenSize(1, 1); + } else if (gfxPrefs::LayersTilesEnabled()) { + // 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 (gfxPrefs::LayersTilesEnabled()) { + // 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); + if (maxSizeAppUnits == nscoord_MAX) { + // Pick a safe maximum displayport size for sanity purposes. This is the + // lowest maximum texture size on tileless-platforms (Windows, D3D10). + maxSizeAppUnits = presContext->DevPixelsToAppUnits(8192); + } + + // 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 = std::min(1.0f, 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 = std::min(1.0f, 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; + + // Round-out the display port to the nearest alignment (tiles) + 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 +ShouldDisableApzForElement(nsIContent* aContent) +{ + if (gfxPrefs::APZDisableForScrollLinkedEffects() && aContent) { + nsIDocument* doc = aContent->GetComposedDoc(); + return (doc && doc->HasScrollLinkedEffect()); + } + return false; +} + +static bool +GetDisplayPortImpl(nsIContent* aContent, nsRect *aResult, float aMultiplier) +{ + DisplayPortPropertyData* rectData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPort)); + DisplayPortMarginsPropertyData* marginsData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); + + if (!rectData && !marginsData) { + // This content element has no displayport data at all + 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; + } + + if (rectData && marginsData) { + // choose margins if equal priority + if (rectData->mPriority > marginsData->mPriority) { + marginsData = nullptr; + } else { + rectData = nullptr; + } + } + + NS_ASSERTION((rectData == nullptr) != (marginsData == nullptr), + "Only one of rectData or marginsData should be set!"); + + nsRect result; + if (rectData) { + result = GetDisplayPortFromRectData(aContent, rectData, aMultiplier); + } else if (APZCCallbackHelper::IsDisplayportSuppressed() || ShouldDisableApzForElement(aContent)) { + DisplayPortMarginsPropertyData noMargins(ScreenMargin(), 1); + result = GetDisplayPortFromMarginsData(aContent, &noMargins, aMultiplier); + } else { + result = GetDisplayPortFromMarginsData(aContent, marginsData, aMultiplier); + } + + if (!gfxPrefs::LayersTilesEnabled()) { + // Either we should have gotten a valid rect directly from the displayport + // base, or we should have computed a valid rect from the margins. + NS_ASSERTION(result.width <= GetMaxDisplayPortSize(aContent), + "Displayport must be a valid texture size"); + NS_ASSERTION(result.height <= GetMaxDisplayPortSize(aContent), + "Displayport must be a valid texture size"); + } + + *aResult = result; + return true; +} + +void +TranslateFromScrollPortToScrollFrame(nsIContent* aContent, nsRect* aRect) +{ + MOZ_ASSERT(aRect); + nsIFrame* frame = GetScrollFrameFromContent(aContent); + nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr; + if (scrollableFrame) { + *aRect += scrollableFrame->GetScrollPortRect().TopLeft(); + } +} + +bool +nsLayoutUtils::GetDisplayPort(nsIContent* aContent, nsRect *aResult, + RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */) +{ + float multiplier = + gfxPrefs::UseLowPrecisionBuffer() ? 1.0f / gfxPrefs::LowPrecisionResolution() : 1.0f; + bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, multiplier); + if (aResult && usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) { + TranslateFromScrollPortToScrollFrame(aContent, aResult); + } + return usingDisplayPort; +} + +bool +nsLayoutUtils::HasDisplayPort(nsIContent* aContent) { + return GetDisplayPort(aContent, nullptr); +} + +/* static */ bool +nsLayoutUtils::GetDisplayPortForVisibilityTesting( + nsIContent* aContent, + nsRect* aResult, + RelativeTo aRelativeTo /* = RelativeTo::ScrollPort */) +{ + MOZ_ASSERT(aResult); + bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, 1.0f); + if (usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) { + TranslateFromScrollPortToScrollFrame(aContent, aResult); + } + return usingDisplayPort; +} + +bool +nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent, + nsIPresShell* aPresShell, + const ScreenMargin& aMargins, + uint32_t aPriority, + RepaintMode aRepaintMode) +{ + MOZ_ASSERT(aContent); + MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument()); + + DisplayPortMarginsPropertyData* currentData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); + if (currentData && currentData->mPriority > aPriority) { + return false; + } + + nsRect oldDisplayPort; + bool hadDisplayPort = GetHighResolutionDisplayPort(aContent, &oldDisplayPort); + + aContent->SetProperty(nsGkAtoms::DisplayPortMargins, + new DisplayPortMarginsPropertyData( + aMargins, aPriority), + nsINode::DeleteProperty); + + nsRect newDisplayPort; + DebugOnly hasDisplayPort = GetHighResolutionDisplayPort(aContent, &newDisplayPort); + MOZ_ASSERT(hasDisplayPort); + + bool changed = !hadDisplayPort || + !oldDisplayPort.IsEqualEdges(newDisplayPort); + + if (gfxPrefs::LayoutUseContainersForRootFrames()) { + nsIFrame* rootScrollFrame = aPresShell->GetRootScrollFrame(); + if (rootScrollFrame && + aContent == rootScrollFrame->GetContent() && + nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame)) + { + // We are setting a root displayport for a document. + // If we have APZ, then set a special flag on the pres shell so + // that we don't get scrollbars drawn. + aPresShell->SetIgnoreViewportScrolling(true); + } + } + + if (changed && aRepaintMode == RepaintMode::Repaint) { + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (frame) { + frame->SchedulePaint(); + } + } + + nsIFrame* frame = GetScrollFrameFromContent(aContent); + nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr; + if (!scrollableFrame) { + return true; + } + + 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(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) +{ + aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase), + nsINode::DeleteProperty); +} + +void +nsLayoutUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent, const nsRect& aBase) +{ + if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) { + SetDisplayPortBase(aContent, aBase); + } +} + +bool +nsLayoutUtils::GetCriticalDisplayPort(nsIContent* aContent, nsRect* aResult) +{ + if (gfxPrefs::UseLowPrecisionBuffer()) { + return GetDisplayPortImpl(aContent, aResult, 1.0f); + } + return false; +} + +bool +nsLayoutUtils::HasCriticalDisplayPort(nsIContent* aContent) +{ + return GetCriticalDisplayPort(aContent, nullptr); +} + +bool +nsLayoutUtils::GetHighResolutionDisplayPort(nsIContent* aContent, nsRect* aResult) +{ + if (gfxPrefs::UseLowPrecisionBuffer()) { + return GetCriticalDisplayPort(aContent, aResult); + } + return GetDisplayPort(aContent, aResult); +} + +void +nsLayoutUtils::RemoveDisplayPort(nsIContent* aContent) +{ + aContent->DeleteProperty(nsGkAtoms::DisplayPort); + aContent->DeleteProperty(nsGkAtoms::DisplayPortMargins); +} + +nsContainerFrame* +nsLayoutUtils::LastContinuationWithChild(nsContainerFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + nsIFrame* f = aFrame->LastContinuation(); + while (!f->PrincipalChildList().FirstChild() && f->GetPrevContinuation()) { + f = f->GetPrevContinuation(); + } + return static_cast(f); +} + +//static +FrameChildListID +nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) +{ + nsIFrame::ChildListID id = nsIFrame::kPrincipalList; + + if (aChildFrame->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { + nsIFrame* pif = aChildFrame->GetPrevInFlow(); + if (pif->GetParent() == aChildFrame->GetParent()) { + id = nsIFrame::kExcessOverflowContainersList; + } + else { + id = nsIFrame::kOverflowContainersList; + } + } + // See if the frame is moved out of the flow + else if (aChildFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { + // Look at the style information to tell + const nsStyleDisplay* disp = aChildFrame->StyleDisplay(); + + if (NS_STYLE_POSITION_ABSOLUTE == disp->mPosition) { + id = nsIFrame::kAbsoluteList; + } else if (NS_STYLE_POSITION_FIXED == disp->mPosition) { + if (nsLayoutUtils::IsReallyFixedPos(aChildFrame)) { + id = nsIFrame::kFixedList; + } else { + id = nsIFrame::kAbsoluteList; + } +#ifdef MOZ_XUL + } else if (StyleDisplay::Popup == disp->mDisplay) { + // Out-of-flows that are DISPLAY_POPUP must be kids of the root popup set +#ifdef DEBUG + nsIFrame* parent = aChildFrame->GetParent(); + NS_ASSERTION(parent && parent->GetType() == nsGkAtoms::popupSetFrame, + "Unexpected parent"); +#endif // DEBUG + + id = nsIFrame::kPopupList; +#endif // MOZ_XUL + } else { + NS_ASSERTION(aChildFrame->IsFloating(), "not a floated frame"); + id = nsIFrame::kFloatList; + } + + } else { + nsIAtom* childType = aChildFrame->GetType(); + if (nsGkAtoms::menuPopupFrame == childType) { + nsIFrame* parent = aChildFrame->GetParent(); + MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame"); + if (parent) { + if (parent->GetType() == nsGkAtoms::popupSetFrame) { + 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 (nsGkAtoms::tableColGroupFrame == 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) { + if (!(aChildFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { + found = parent->GetChildList(nsIFrame::kOverflowList) + .ContainsFrame(aChildFrame); + } + else if (aChildFrame->IsFloating()) { + found = parent->GetChildList(nsIFrame::kOverflowOutOfFlowList) + .ContainsFrame(aChildFrame); + if (!found) { + found = parent->GetChildList(nsIFrame::kPushedFloatsList) + .ContainsFrame(aChildFrame); + } + } + // else it's positioned and should have been on the 'id' child list. + NS_POSTCONDITION(found, "not in child list"); + } +#endif + + return id; +} + +/*static*/ nsIFrame* +nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame, + const nsIContent* aContent) +{ + // We need to call GetGenConPseudos() on the first continuation/ib-split. + // Find it, for symmetry with GetAfterFrameForContent. + nsContainerFrame* genConParentFrame = + FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame(); + if (!genConParentFrame) { + return nullptr; + } + nsTArray* prop = genConParentFrame->GetGenConPseudos(); + if (prop) { + const nsTArray& pseudos(*prop); + for (uint32_t i = 0; i < pseudos.Length(); ++i) { + if (pseudos[i]->GetParent() == aContent && + pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) { + return pseudos[i]->GetPrimaryFrame(); + } + } + } + // If the first child frame is a pseudo-frame, then try that. + // Note that the frame we create for the generated content is also a + // pseudo-frame and so don't drill down in that case. + nsIFrame* childFrame = genConParentFrame->PrincipalChildList().FirstChild(); + if (childFrame && + childFrame->IsPseudoFrame(aContent) && + !childFrame->IsGeneratedContentFrame()) { + return GetBeforeFrameForContent(childFrame, aContent); + } + return nullptr; +} + +/*static*/ nsIFrame* +nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame) +{ + return GetBeforeFrameForContent(aFrame, aFrame->GetContent()); +} + +/*static*/ nsIFrame* +nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame, + const nsIContent* aContent) +{ + // We need to call GetGenConPseudos() on the first continuation, + // but callers are likely to pass the last. + nsContainerFrame* genConParentFrame = + FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame(); + if (!genConParentFrame) { + return nullptr; + } + nsTArray* prop = genConParentFrame->GetGenConPseudos(); + if (prop) { + const nsTArray& pseudos(*prop); + for (uint32_t i = 0; i < pseudos.Length(); ++i) { + if (pseudos[i]->GetParent() == aContent && + pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) { + return pseudos[i]->GetPrimaryFrame(); + } + } + } + // If the last child frame is a pseudo-frame, then try that. + // Note that the frame we create for the generated content is also a + // pseudo-frame and so don't drill down in that case. + genConParentFrame = aFrame->GetContentInsertionFrame(); + if (!genConParentFrame) { + return nullptr; + } + nsIFrame* lastParentContinuation = + LastContinuationWithChild(static_cast( + LastContinuationOrIBSplitSibling(genConParentFrame))); + nsIFrame* childFrame = + lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild(); + if (childFrame && + childFrame->IsPseudoFrame(aContent) && + !childFrame->IsGeneratedContentFrame()) { + return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent); + } + return nullptr; +} + +/*static*/ nsIFrame* +nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) +{ + return GetAfterFrameForContent(aFrame, aFrame->GetContent()); +} + +// static +nsIFrame* +nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, + nsIAtom* aFrameType, + nsIFrame* aStopAt) +{ + for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { + if (frame->GetType() == aFrameType) { + return frame; + } + if (frame == aStopAt) { + break; + } + } + return nullptr; +} + +// static +nsIFrame* +nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame) +{ + if (aFrame->GetType() == nsGkAtoms::tableWrapperFrame) { + nsIFrame* inner = aFrame->PrincipalChildList().FirstChild(); + // inner may be null, if aFrame is mid-destruction + return inner; + } + + return aFrame; +} + +nsIFrame* +nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) +{ + nsIFrame *frame = aContent->GetPrimaryFrame(); + if (!frame) { + return nullptr; + } + + return nsLayoutUtils::GetStyleFrame(frame); +} + +/* static */ nsIFrame* +nsLayoutUtils::GetRealPrimaryFrameFor(const nsIContent* aContent) +{ + nsIFrame *frame = aContent->GetPrimaryFrame(); + if (!frame) { + return nullptr; + } + + return nsPlaceholderFrame::GetRealFrameFor(frame); +} + +nsIFrame* +nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) { + NS_ASSERTION(nsGkAtoms::placeholderFrame == aFrame->GetType(), + "Must have a placeholder here"); + if (aFrame->GetStateBits() & PLACEHOLDER_FOR_FLOAT) { + nsIFrame *outOfFlowFrame = + nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame); + NS_ASSERTION(outOfFlowFrame->IsFloating(), + "How did that happen?"); + return outOfFlowFrame; + } + + return nullptr; +} + +// static +bool +nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent, + nsIFrame* aFrame, + nsIAtom* aPseudoElement) +{ + NS_PRECONDITION(aFrame, "Must have a frame"); + NS_PRECONDITION(aPseudoElement, "Must have a pseudo name"); + + if (!aFrame->IsGeneratedContentFrame()) { + return false; + } + nsIFrame* parent = aFrame->GetParent(); + NS_ASSERTION(parent, "Generated content can't be root frame"); + if (parent->IsGeneratedContentFrame()) { + // Not the root of the generated content + return false; + } + + if (aContent && parent->GetContent() != aContent) { + return false; + } + + return (aFrame->GetContent()->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) == + (aPseudoElement == nsCSSPseudoElements::before); +} + +// 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; + if (aExtraOffset) { + *aExtraOffset += v->GetPosition(); + } + v = v->GetParent(); // subdocumentframe's view + return v ? v->GetFrame() : nullptr; +} + +// static +bool +nsLayoutUtils::IsProperAncestorFrameCrossDoc(nsIFrame* aAncestorFrame, nsIFrame* aFrame, + 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(nsIFrame* aAncestorFrame, nsIFrame* aFrame, + nsIFrame* aCommonAncestor) +{ + if (aFrame == aAncestorFrame) + return false; + for (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) +{ + NS_PRECONDITION(aContent1, "aContent1 must not be null"); + NS_PRECONDITION(aContent2, "aContent2 must not be null"); + + AutoTArray content1Ancestors; + nsINode* c1; + for (c1 = aContent1; c1 && c1 != aCommonAncestor; c1 = c1->GetParentNode()) { + 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 content2Ancestors; + nsINode* c2; + for (c2 = aContent2; c2 && c2 != aCommonAncestor; c2 = c2->GetParentNode()) { + 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->GetParentNode(); +#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->IndexOf(content1Ancestor); + int32_t index2 = parent->IndexOf(content2Ancestor); + if (index1 < 0 || index2 < 0) { + // one of them must be anonymous; we can't determine the order + return 0; + } + + return index1 - index2; +} + +// static +nsIFrame* +nsLayoutUtils::FillAncestors(nsIFrame* aFrame, + nsIFrame* aStopAtAncestor, + nsTArray* 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) +{ + NS_PRECONDITION(aFrame1, "aFrame1 must not be null"); + NS_PRECONDITION(aFrame2, "aFrame2 must not be null"); + + AutoTArray 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& aFrame2Ancestors, + int32_t aIf1Ancestor, + int32_t aIf2Ancestor, + nsIFrame* aCommonAncestor) +{ + NS_PRECONDITION(aFrame1, "aFrame1 must not be null"); + NS_PRECONDITION(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 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 */ 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); + + int32_t sides = eSideBitsNone; + if (aFixedPosFrame != aViewportFrame) { + const nsStylePosition* position = aFixedPosFrame->StylePosition(); + if (position->mOffset.GetRightUnit() != eStyleUnit_Auto) { + sides |= eSideBitsRight; + if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) { + sides |= eSideBitsLeft; + anchor.x = anchorRect.x + anchorRect.width / 2.f; + } else { + anchor.x = anchorRect.XMost(); + } + } + if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto) { + sides |= eSideBitsBottom; + if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) { + sides |= eSideBitsTop; + anchor.y = anchorRect.y + anchorRect.height / 2.f; + } else { + anchor.y = anchorRect.YMost(); + } + } + } + + ViewID id = FrameMetrics::NULL_SCROLL_ID; + if (nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame()) { + if (nsIContent* content = rootScrollFrame->GetContent()) { + id = FindOrCreateIDFor(content); + } + } + aLayer->SetFixedPositionData(id, anchor, sides); +} + +bool +nsLayoutUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) +{ + nsIFrame* rootScrollFrame = + aPresContext->PresShell()->GetRootScrollFrame(); + return rootScrollFrame && + nsLayoutUtils::HasDisplayPort(rootScrollFrame->GetContent()); +} + +bool +nsLayoutUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) +{ + // Fixed-pos frames are parented by the viewport frame or the page content frame. + // We'll assume that printing/print preview don't have displayports for their + // pages! + nsIFrame* parent = aFrame->GetParent(); + if (!parent || parent->GetParent() || + aFrame->StyleDisplay()->mPosition != NS_STYLE_POSITION_FIXED) { + return false; + } + return ViewportHasDisplayPort(aFrame->PresContext()); +} + +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ScrollbarThumbLayerized, bool) + +/* static */ void +nsLayoutUtils::SetScrollbarThumbLayerization(nsIFrame* aThumbFrame, bool aLayerize) +{ + aThumbFrame->Properties().Set(ScrollbarThumbLayerized(), aLayerize); +} + +bool +nsLayoutUtils::IsScrollbarThumbLayerized(nsIFrame* aThumbFrame) +{ + return aThumbFrame->Properties().Get(ScrollbarThumbLayerized()); +} + +// static +nsIScrollableFrame* +nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame, + Direction aDirection) +{ + 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) { + ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles(); + uint32_t directions = scrollableFrame->GetPerceivedScrollingDirections(); + if (aDirection == eVertical ? + (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN && + (directions & nsIScrollableFrame::VERTICAL)) : + (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN && + (directions & nsIScrollableFrame::HORIZONTAL))) + return scrollableFrame; + } + } + return nullptr; +} + +// static +nsIScrollableFrame* +nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame, uint32_t aFlags) +{ + NS_ASSERTION(aFrame, "GetNearestScrollableFrame expects a non-null frame"); + for (nsIFrame* f = aFrame; f; f = (aFlags & SCROLLABLE_SAME_DOC) ? + f->GetParent() : nsLayoutUtils::GetCrossDocParentFrame(f)) { + nsIScrollableFrame* scrollableFrame = do_QueryFrame(f); + if (scrollableFrame) { + if (aFlags & SCROLLABLE_ONLY_ASYNC_SCROLLABLE) { + if (scrollableFrame->WantAsyncScroll()) { + return scrollableFrame; + } + } else { + ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles(); + if ((aFlags & SCROLLABLE_INCLUDE_HIDDEN) || + ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || + ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) { + return scrollableFrame; + } + } + if (aFlags & SCROLLABLE_ALWAYS_MATCH_ROOT) { + nsIPresShell* ps = f->PresContext()->PresShell(); + if (ps->GetRootScrollFrame() == f && + ps->GetDocument() && ps->GetDocument()->IsRootDisplayDocument()) { + return scrollableFrame; + } + } + } + if ((aFlags & SCROLLABLE_FIXEDPOS_FINDS_ROOT) && + f->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED && + nsLayoutUtils::IsReallyFixedPos(f)) { + return f->PresContext()->PresShell()->GetRootScrollFrameAsScrollable(); + } + } + return nullptr; +} + +// static +nsRect +nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame, + const nsRect& aScrolledFrameOverflowArea, + const nsSize& aScrollPortSize, + uint8_t aDirection) +{ + WritingMode wm = aScrolledFrame->GetWritingMode(); + // Potentially override the frame's direction to use the direction found + // by ScrollFrameHelper::GetScrolledFrameDir() + wm.SetDirectionFromBidiLevel(aDirection == NS_STYLE_DIRECTION_RTL ? 1 : 0); + + nscoord x1 = aScrolledFrameOverflowArea.x, + x2 = aScrolledFrameOverflowArea.XMost(), + y1 = aScrolledFrameOverflowArea.y, + y2 = aScrolledFrameOverflowArea.YMost(); + + bool horizontal = !wm.IsVertical(); + + // Clamp the horizontal start-edge (x1 or x2, depending whether the logical + // axis that corresponds to horizontal progresses from L-R or R-L). + // In horizontal writing mode, we need to check IsInlineReversed() to see + // which side to clamp; in vertical mode, it depends on the block direction. + if ((horizontal && !wm.IsInlineReversed()) || wm.IsVerticalLR()) { + 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. + // In horizontal writing mode, the block direction is always top-to-bottom; + // in vertical writing mode, we need to check IsInlineReversed(). + if (horizontal || !wm.IsInlineReversed()) { + 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, + nsStyleContext* aStyleContext, + CSSPseudoElementType aPseudoElement, + nsPresContext* aPresContext) +{ + NS_PRECONDITION(aPresContext, "Must have a prescontext"); + + RefPtr pseudoContext; + if (aContent) { + pseudoContext = aPresContext->StyleSet()-> + ProbePseudoElementStyle(aContent->AsElement(), aPseudoElement, + aStyleContext); + } + return pseudoContext != nullptr; +} + +nsPoint +nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(nsIDOMEvent* 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, aFrame); +} + +nsPoint +nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, + nsIFrame* 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, + nsIFrame* aFrame) +{ + if (!aFrame) { + 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 +nsLayoutUtils::GetEventCoordinatesRelativeTo(nsIWidget* aWidget, + const LayoutDeviceIntPoint& aPoint, + nsIFrame* aFrame) +{ + if (!aFrame || !aWidget) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + nsView* view = aFrame->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 = aFrame->PresContext(); + nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x), + presContext->DevPixelsToAppUnits(aPoint.y)); + pt = pt - view->ViewToWidgetOffset(); + pt = pt.RemoveResolution(GetCurrentAPZResolutionScale(presContext->PresShell())); + return pt; + } + } + + /* 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. + */ + nsIFrame* rootFrame = aFrame; + bool transformFound = false; + for (nsIFrame* f = aFrame; f; f = GetCrossDocParentFrame(f)) { + if (f->IsTransformed()) { + transformFound = true; + } + + rootFrame = f; + } + + nsView* rootView = rootFrame->GetView(); + if (!rootView) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + nsPoint widgetToView = TranslateWidgetToView(rootFrame->PresContext(), + aWidget, aPoint, rootView); + + if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + // Convert from root document app units to app units of the document aFrame + // is in. + int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel(); + int32_t localAPD = aFrame->PresContext()->AppUnitsPerDevPixel(); + widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD); + nsIPresShell* shell = aFrame->PresContext()->PresShell(); + + // XXX Bug 1224748 - Update nsLayoutUtils functions to correctly handle nsPresShell resolution + widgetToView = widgetToView.RemoveResolution(GetCurrentAPZResolutionScale(shell)); + + /* If we encountered a transform, we can't do simple arithmetic to figure + * out how to convert back to aFrame's coordinates and must use the CTM. + */ + if (transformFound || aFrame->IsSVGText()) { + return TransformRootPointToFrame(aFrame, widgetToView); + } + + /* Otherwise, all coordinate systems are translations of one another, + * so we can just subtract out the difference. + */ + return widgetToView - aFrame->GetOffsetToCrossDoc(rootFrame); +} + +nsIFrame* +nsLayoutUtils::GetPopupFrameForEventCoordinates(nsPresContext* aPresContext, + const WidgetEvent* aEvent) +{ +#ifdef MOZ_XUL + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) { + return nullptr; + } + nsTArray popups; + pm->GetVisiblePopups(popups); + uint32_t i; + // Search from top to bottom + for (i = 0; i < popups.Length(); i++) { + nsIFrame* popup = popups[i]; + if (popup->PresContext()->GetRootPresContext() == aPresContext && + popup->GetScrollableOverflowRect().Contains( + GetEventCoordinatesRelativeTo(aEvent, popup))) { + return popup; + } + } +#endif + return nullptr; +} + +static void ConstrainToCoordValues(float& aStart, float& aSize) +{ + MOZ_ASSERT(aSize >= 0); + + // Here we try to make sure that the resulting nsRect will continue to cover + // as much of the area that was covered by the original gfx Rect as possible. + + // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since + // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this + // range: + float end = aStart + aSize; + aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX)); + end = clamped(end, float(nscoord_MIN), float(nscoord_MAX)); + + aSize = end - aStart; + + // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height() + // can't return a value greater than nscoord_MAX. If aSize is greater than + // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect + // centered: + if (aSize > nscoord_MAX) { + float excess = aSize - nscoord_MAX; + excess /= 2; + aStart += excess; + aSize = nscoord_MAX; + } +} + +/** + * Given a gfxFloat, constrains its value to be between nscoord_MIN and nscoord_MAX. + * + * @param aVal The value to constrain (in/out) + */ +static void ConstrainToCoordValues(gfxFloat& aVal) +{ + if (aVal <= nscoord_MIN) + aVal = nscoord_MIN; + else if (aVal >= nscoord_MAX) + aVal = nscoord_MAX; +} + +static void ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize) +{ + gfxFloat max = aStart + aSize; + + // Clamp the end points to within nscoord range + ConstrainToCoordValues(aStart); + ConstrainToCoordValues(max); + + aSize = max - aStart; + // If the width if still greater than the max nscoord, then bring both + // endpoints in by the same amount until it fits. + if (aSize > nscoord_MAX) { + gfxFloat excess = aSize - nscoord_MAX; + excess /= 2; + + aStart += excess; + aSize = nscoord_MAX; + } else if (aSize < nscoord_MIN) { + gfxFloat excess = aSize - nscoord_MIN; + excess /= 2; + + aStart -= excess; + aSize = nscoord_MIN; + } +} + +nsRect +nsLayoutUtils::RoundGfxRectToAppRect(const Rect &aRect, float aFactor) +{ + /* Get a new Rect whose units are app units by scaling by the specified factor. */ + Rect scaledRect = aRect; + scaledRect.ScaleRoundOut(aFactor); + + /* We now need to constrain our results to the max and min values for coords. */ + ConstrainToCoordValues(scaledRect.x, scaledRect.width); + ConstrainToCoordValues(scaledRect.y, scaledRect.height); + + /* Now typecast everything back. This is guaranteed to be safe. */ + return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), + nscoord(scaledRect.Width()), nscoord(scaledRect.Height())); +} + +nsRect +nsLayoutUtils::RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor) +{ + /* Get a new gfxRect whose units are app units by scaling by the specified factor. */ + gfxRect scaledRect = aRect; + scaledRect.ScaleRoundOut(aFactor); + + /* We now need to constrain our results to the max and min values for coords. */ + ConstrainToCoordValues(scaledRect.x, scaledRect.width); + ConstrainToCoordValues(scaledRect.y, scaledRect.height); + + /* Now typecast everything back. This is guaranteed to be safe. */ + return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()), + nscoord(scaledRect.Width()), nscoord(scaledRect.Height())); +} + + +nsRegion +nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect, + const nscoord aRadii[8], + const nsRect& aContainedRect) +{ + // rectFullHeight and rectFullWidth together will approximately contain + // the total area of the frame minus the rounded corners. + nsRect rectFullHeight = aRoundedRect; + nscoord xDiff = std::max(aRadii[NS_CORNER_TOP_LEFT_X], aRadii[NS_CORNER_BOTTOM_LEFT_X]); + rectFullHeight.x += xDiff; + rectFullHeight.width -= std::max(aRadii[NS_CORNER_TOP_RIGHT_X], + aRadii[NS_CORNER_BOTTOM_RIGHT_X]) + xDiff; + nsRect r1; + r1.IntersectRect(rectFullHeight, aContainedRect); + + nsRect rectFullWidth = aRoundedRect; + nscoord yDiff = std::max(aRadii[NS_CORNER_TOP_LEFT_Y], aRadii[NS_CORNER_TOP_RIGHT_Y]); + rectFullWidth.y += yDiff; + rectFullWidth.height -= std::max(aRadii[NS_CORNER_BOTTOM_LEFT_Y], + aRadii[NS_CORNER_BOTTOM_RIGHT_Y]) + yDiff; + nsRect r2; + r2.IntersectRect(rectFullWidth, aContainedRect); + + nsRegion result; + result.Or(r1, r2); + return result; +} + +nsIntRegion +nsLayoutUtils::RoundedRectIntersectIntRect(const nsIntRect& aRoundedRect, + const RectCornerRadii& aCornerRadii, + const nsIntRect& aContainedRect) +{ + // rectFullHeight and rectFullWidth together will approximately contain + // the total area of the frame minus the rounded corners. + nsIntRect rectFullHeight = aRoundedRect; + uint32_t xDiff = std::max(aCornerRadii.TopLeft().width, + aCornerRadii.BottomLeft().width); + rectFullHeight.x += xDiff; + rectFullHeight.width -= std::max(aCornerRadii.TopRight().width, + aCornerRadii.BottomRight().width) + xDiff; + nsIntRect r1; + r1.IntersectRect(rectFullHeight, aContainedRect); + + nsIntRect rectFullWidth = aRoundedRect; + uint32_t yDiff = std::max(aCornerRadii.TopLeft().height, + aCornerRadii.TopRight().height); + rectFullWidth.y += yDiff; + rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height, + aCornerRadii.BottomRight().height) + yDiff; + nsIntRect r2; + r2.IntersectRect(rectFullWidth, aContainedRect); + + nsIntRegion result; + result.Or(r1, r2); + return result; +} + +// Helper for RoundedRectIntersectsRect. +static bool +CheckCorner(nscoord aXOffset, nscoord aYOffset, + nscoord aXRadius, nscoord aYRadius) +{ + MOZ_ASSERT(aXOffset > 0 && aYOffset > 0, + "must not pass nonpositives to CheckCorner"); + MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0, + "must not pass negatives to CheckCorner"); + + // Avoid floating point math unless we're either (1) within the + // quarter-ellipse area at the rounded corner or (2) outside the + // rounding. + if (aXOffset >= aXRadius || aYOffset >= aYRadius) + return true; + + // Convert coordinates to a unit circle with (0,0) as the center of + // curvature, and see if we're inside the circle or outside. + float scaledX = float(aXRadius - aXOffset) / float(aXRadius); + float scaledY = float(aYRadius - aYOffset) / float(aYRadius); + return scaledX * scaledX + scaledY * scaledY < 1.0f; +} + +bool +nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect, + const nscoord aRadii[8], + const nsRect& aTestRect) +{ + if (!aTestRect.Intersects(aRoundedRect)) + return false; + + // distances from this edge of aRoundedRect to opposite edge of aTestRect, + // which we know are positive due to the Intersects check above. + nsMargin insets; + insets.top = aTestRect.YMost() - aRoundedRect.y; + insets.right = aRoundedRect.XMost() - aTestRect.x; + insets.bottom = aRoundedRect.YMost() - aTestRect.y; + insets.left = aTestRect.XMost() - aRoundedRect.x; + + // Check whether the bottom-right corner of aTestRect is inside the + // top left corner of aBounds when rounded by aRadii, etc. If any + // corner is not, then fail; otherwise succeed. + return CheckCorner(insets.left, insets.top, + aRadii[NS_CORNER_TOP_LEFT_X], + aRadii[NS_CORNER_TOP_LEFT_Y]) && + CheckCorner(insets.right, insets.top, + aRadii[NS_CORNER_TOP_RIGHT_X], + aRadii[NS_CORNER_TOP_RIGHT_Y]) && + CheckCorner(insets.right, insets.bottom, + aRadii[NS_CORNER_BOTTOM_RIGHT_X], + aRadii[NS_CORNER_BOTTOM_RIGHT_Y]) && + CheckCorner(insets.left, insets.bottom, + aRadii[NS_CORNER_BOTTOM_LEFT_X], + aRadii[NS_CORNER_BOTTOM_LEFT_Y]); +} + +nsRect +nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds, + const Matrix4x4 &aMatrix, float aFactor) +{ + RectDouble image = RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor), + NSAppUnitsToDoublePixels(aBounds.y, aFactor), + NSAppUnitsToDoublePixels(aBounds.width, aFactor), + NSAppUnitsToDoublePixels(aBounds.height, aFactor)); + + RectDouble maxBounds = RectDouble(double(nscoord_MIN) / aFactor * 0.5, + double(nscoord_MIN) / aFactor * 0.5, + double(nscoord_MAX) / aFactor, + double(nscoord_MAX) / aFactor); + + image = aMatrix.TransformAndClipBounds(image, maxBounds); + + return RoundGfxRectToAppRect(ThebesRect(image), aFactor); +} + +nsPoint +nsLayoutUtils::MatrixTransformPoint(const nsPoint &aPoint, + const Matrix4x4 &aMatrix, float aFactor) +{ + gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor), + NSAppUnitsToFloatPixels(aPoint.y, aFactor)); + image.Transform(aMatrix); + return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor), + NSFloatPixelsToAppUnits(float(image.y), aFactor)); +} + +void +nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin, float aAppUnitsPerPixel, bool aRounded) +{ + Point3D gfxOrigin = + Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel), + 0.0f); + if (aRounded) { + gfxOrigin.x = NS_round(gfxOrigin.x); + gfxOrigin.y = NS_round(gfxOrigin.y); + } + aTransform.PostTranslate(gfxOrigin); +} + +Matrix4x4 +nsLayoutUtils::GetTransformToAncestor(nsIFrame *aFrame, const nsIFrame *aAncestor) +{ + nsIFrame* parent; + Matrix4x4 ctm; + if (aFrame == aAncestor) { + return ctm; + } + ctm = aFrame->GetTransformMatrix(aAncestor, &parent); + while (parent && parent != aAncestor) { + if (!parent->Extend3DContext()) { + ctm.ProjectTo2D(); + } + ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent); + } + return ctm; +} + +gfxSize +nsLayoutUtils::GetTransformToAncestorScale(nsIFrame* aFrame) +{ + Matrix4x4 transform = GetTransformToAncestor(aFrame, + nsLayoutUtils::GetDisplayRootFrame(aFrame)); + Matrix transform2D; + if (transform.Is2D(&transform2D)) { + return ThebesMatrix(transform2D).ScaleFactors(true); + } + return gfxSize(1, 1); +} + +static Matrix4x4 +GetTransformToAncestorExcludingAnimated(nsIFrame* aFrame, + const nsIFrame* aAncestor) +{ + nsIFrame* parent; + Matrix4x4 ctm; + if (aFrame == aAncestor) { + return ctm; + } + if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) { + return ctm; + } + ctm = aFrame->GetTransformMatrix(aAncestor, &parent); + while (parent && parent != aAncestor) { + if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) { + return Matrix4x4(); + } + if (!parent->Extend3DContext()) { + ctm.ProjectTo2D(); + } + ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent); + } + return ctm; +} + +gfxSize +nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(nsIFrame* aFrame) +{ + Matrix4x4 transform = GetTransformToAncestorExcludingAnimated(aFrame, + nsLayoutUtils::GetDisplayRootFrame(aFrame)); + Matrix transform2D; + if (transform.Is2D(&transform2D)) { + return ThebesMatrix(transform2D).ScaleFactors(true); + } + return gfxSize(1, 1); +} + +nsIFrame* +nsLayoutUtils::FindNearestCommonAncestorFrame(nsIFrame* aFrame1, nsIFrame* aFrame2) +{ + AutoTArray ancestors1; + AutoTArray ancestors2; + nsIFrame* commonAncestor = nullptr; + if (aFrame1->PresContext() == aFrame2->PresContext()) { + commonAncestor = aFrame1->PresContext()->PresShell()->GetRootFrame(); + } + for (nsIFrame* f = aFrame1; f != commonAncestor; + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { + ancestors1.AppendElement(f); + } + for (nsIFrame* f = aFrame2; f != commonAncestor; + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { + ancestors2.AppendElement(f); + } + uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length()); + for (uint32_t i = 1; i <= minLengths; ++i) { + if (ancestors1[ancestors1.Length() - i] == ancestors2[ancestors2.Length() - i]) { + commonAncestor = ancestors1[ancestors1.Length() - i]; + } else { + break; + } + } + return commonAncestor; +} + +nsLayoutUtils::TransformResult +nsLayoutUtils::TransformPoints(nsIFrame* aFromFrame, nsIFrame* aToFrame, + uint32_t aPointCount, CSSPoint* aPoints) +{ + nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); + if (!nearestCommonAncestor) { + return NO_COMMON_ANCESTOR; + } + Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); + if (downToDest.IsSingular()) { + return NONINVERTIBLE_TRANSFORM; + } + downToDest.Invert(); + Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); + CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame = + aFromFrame->PresContext()->CSSToDevPixelScale(); + CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame = + aToFrame->PresContext()->CSSToDevPixelScale(); + for (uint32_t i = 0; i < aPointCount; ++i) { + LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame; + // What should the behaviour be if some of the points aren't invertible + // and others are? Just assume all points are for now. + Point toDevPixels = downToDest.ProjectPoint( + (upToAncestor.TransformPoint(Point(devPixels.x, devPixels.y)))).As2DPoint(); + // Divide here so that when the devPixelsPerCSSPixels are the same, we get the correct + // answer instead of some inaccuracy multiplying a number by its reciprocal. + aPoints[i] = LayoutDevicePoint(toDevPixels.x, toDevPixels.y) / + devPixelsPerCSSPixelToFrame; + } + return TRANSFORM_SUCCEEDED; +} + +nsLayoutUtils::TransformResult +nsLayoutUtils::TransformPoint(nsIFrame* aFromFrame, nsIFrame* aToFrame, + nsPoint& aPoint) +{ + nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); + if (!nearestCommonAncestor) { + return NO_COMMON_ANCESTOR; + } + Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); + if (downToDest.IsSingular()) { + return NONINVERTIBLE_TRANSFORM; + } + downToDest.Invert(); + Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); + + float devPixelsPerAppUnitFromFrame = + 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel(); + float devPixelsPerAppUnitToFrame = + 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel(); + Point4D toDevPixels = downToDest.ProjectPoint( + upToAncestor.TransformPoint(Point(aPoint.x * devPixelsPerAppUnitFromFrame, + aPoint.y * devPixelsPerAppUnitFromFrame))); + if (!toDevPixels.HasPositiveWCoord()) { + // Not strictly true, but we failed to get a valid point in this + // coordinate space. + return NONINVERTIBLE_TRANSFORM; + } + aPoint.x = NSToCoordRound(toDevPixels.x / devPixelsPerAppUnitToFrame); + aPoint.y = NSToCoordRound(toDevPixels.y / devPixelsPerAppUnitToFrame); + return TRANSFORM_SUCCEEDED; +} + +nsLayoutUtils::TransformResult +nsLayoutUtils::TransformRect(nsIFrame* aFromFrame, nsIFrame* aToFrame, + nsRect& aRect) +{ + nsIFrame* nearestCommonAncestor = FindNearestCommonAncestorFrame(aFromFrame, aToFrame); + if (!nearestCommonAncestor) { + return NO_COMMON_ANCESTOR; + } + Matrix4x4 downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); + if (downToDest.IsSingular()) { + return NONINVERTIBLE_TRANSFORM; + } + downToDest.Invert(); + Matrix4x4 upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); + + float devPixelsPerAppUnitFromFrame = + 1.0f / aFromFrame->PresContext()->AppUnitsPerDevPixel(); + float devPixelsPerAppUnitToFrame = + 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel(); + gfx::Rect toDevPixels = downToDest.ProjectRectBounds( + upToAncestor.ProjectRectBounds( + gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame, + aRect.y * devPixelsPerAppUnitFromFrame, + aRect.width * devPixelsPerAppUnitFromFrame, + aRect.height * devPixelsPerAppUnitFromFrame), + Rect(-std::numeric_limits::max() * 0.5f, + -std::numeric_limits::max() * 0.5f, + std::numeric_limits::max(), + std::numeric_limits::max())), + Rect(-std::numeric_limits::max() * devPixelsPerAppUnitFromFrame * 0.5f, + -std::numeric_limits::max() * devPixelsPerAppUnitFromFrame * 0.5f, + std::numeric_limits::max() * devPixelsPerAppUnitFromFrame, + std::numeric_limits::max() * devPixelsPerAppUnitFromFrame)); + aRect.x = toDevPixels.x / devPixelsPerAppUnitToFrame; + aRect.y = toDevPixels.y / devPixelsPerAppUnitToFrame; + aRect.width = toDevPixels.width / devPixelsPerAppUnitToFrame; + aRect.height = toDevPixels.height / devPixelsPerAppUnitToFrame; + return TRANSFORM_SUCCEEDED; +} + +nsRect +nsLayoutUtils::GetRectRelativeToFrame(Element* aElement, nsIFrame* aFrame) +{ + if (!aElement || !aFrame) { + return nsRect(); + } + + nsIFrame* frame = aElement->GetPrimaryFrame(); + if (!frame) { + return nsRect(); + } + + nsRect rect = frame->GetRectRelativeToSelf(); + nsLayoutUtils::TransformResult rv = + nsLayoutUtils::TransformRect(frame, aFrame, rect); + if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) { + return nsRect(); + } + + return rect; +} + +bool +nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint, + nscoord aInflateSize) +{ + nsRect rect = aRect; + rect.Inflate(aInflateSize); + return rect.Contains(aPoint); +} + +nsRect +nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame, const nsRect& aRect) +{ + nsIFrame* closestScrollFrame = + nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame); + + nsRect resultRect = aRect; + + while (closestScrollFrame) { + nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame); + + nsRect scrollPortRect = sf->GetScrollPortRect(); + nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect); + + resultRect = resultRect.Intersect(scrollPortRect); + + // Check whether aRect is visible in the scroll frame or not. + if (resultRect.IsEmpty()) { + break; + } + + // Get next ancestor scroll frame. + closestScrollFrame = + nsLayoutUtils::GetClosestFrameOfType(closestScrollFrame->GetParent(), + nsGkAtoms::scrollFrame); + } + + return resultRect; +} + +bool +nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame, + Matrix4x4* aTransform) +{ + // FIXME/bug 796690: we can sometimes compute a transform in these + // cases, it just increases complexity considerably. Punt for now. + if (aFrame->Extend3DContext() || aFrame->HasTransformGetter()) { + return false; + } + + nsIFrame* root = nsLayoutUtils::GetDisplayRootFrame(aFrame); + if (root->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) { + // Content may have been invalidated, so we can't reliably compute + // the "layer transform" in general. + return false; + } + // If the caller doesn't care about the value, early-return to skip + // overhead below. + if (!aTransform) { + return true; + } + + nsDisplayListBuilder builder(root, + nsDisplayListBuilderMode::TRANSFORM_COMPUTATION, + false/*don't build caret*/); + nsDisplayList list; + nsDisplayTransform* item = + new (&builder) nsDisplayTransform(&builder, aFrame, &list, nsRect()); + + *aTransform = item->GetTransform(); + item->~nsDisplayTransform(); + + return true; +} + +static bool +TransformGfxPointFromAncestor(nsIFrame *aFrame, + const Point &aPoint, + nsIFrame *aAncestor, + Point* aOut) +{ + Matrix4x4 ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); + ctm.Invert(); + Point4D point = ctm.ProjectPoint(aPoint); + if (!point.HasPositiveWCoord()) { + return false; + } + *aOut = point.As2DPoint(); + return true; +} + +static Rect +TransformGfxRectToAncestor(nsIFrame *aFrame, + const Rect &aRect, + const nsIFrame *aAncestor, + bool* aPreservesAxisAlignedRectangles = nullptr, + Maybe* aMatrixCache = nullptr) +{ + Matrix4x4 ctm; + if (aMatrixCache && *aMatrixCache) { + // We are given a matrix to use, so use it + ctm = aMatrixCache->value(); + } else { + // Else, compute it + ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); + if (aMatrixCache) { + // and put it in the cache, if provided + *aMatrixCache = Some(ctm); + } + } + // Fill out the axis-alignment flag + if (aPreservesAxisAlignedRectangles) { + Matrix matrix2d; + *aPreservesAxisAlignedRectangles = + ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles(); + } + Rect maxBounds = Rect(-std::numeric_limits::max() * 0.5, + -std::numeric_limits::max() * 0.5, + std::numeric_limits::max(), + std::numeric_limits::max()); + return ctm.TransformAndClipBounds(aRect, maxBounds); +} + +static SVGTextFrame* +GetContainingSVGTextFrame(nsIFrame* aFrame) +{ + if (!aFrame->IsSVGText()) { + return nullptr; + } + + return static_cast + (nsLayoutUtils::GetClosestFrameOfType(aFrame->GetParent(), + nsGkAtoms::svgTextFrame)); +} + +nsPoint +nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame, + const nsPoint& aPoint, + nsIFrame* aAncestor) +{ + SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + Point result(NSAppUnitsToFloatPixels(aPoint.x, factor), + NSAppUnitsToFloatPixels(aPoint.y, factor)); + + if (text) { + if (!TransformGfxPointFromAncestor(text, result, aAncestor, &result)) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + result = text->TransformFramePointToTextChild(result, aFrame); + } else { + if (!TransformGfxPointFromAncestor(aFrame, result, nullptr, &result)) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + } + + return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor), + NSFloatPixelsToAppUnits(float(result.y), factor)); +} + +nsRect +nsLayoutUtils::TransformFrameRectToAncestor(nsIFrame* aFrame, + const nsRect& aRect, + const nsIFrame* aAncestor, + bool* aPreservesAxisAlignedRectangles /* = nullptr */, + Maybe* aMatrixCache /* = nullptr */) +{ + SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); + + float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + Rect result; + + if (text) { + result = ToRect(text->TransformFrameRectFromTextChild(aRect, aFrame)); + result = TransformGfxRectToAncestor(text, result, aAncestor, nullptr, aMatrixCache); + // TransformFrameRectFromTextChild could involve any kind of transform, we + // could drill down into it to get an answer out of it but we don't yet. + if (aPreservesAxisAlignedRectangles) + *aPreservesAxisAlignedRectangles = false; + } else { + result = Rect(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); + result = TransformGfxRectToAncestor(aFrame, result, aAncestor, aPreservesAxisAlignedRectangles, aMatrixCache); + } + + float destAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel(); + return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel), + NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel), + NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel), + NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel)); +} + +static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget, nsIWidget*& aRootWidget) { + LayoutDeviceIntPoint offset(0, 0); + while ((aWidget->WindowType() == eWindowType_child || + aWidget->IsPlugin())) { + nsIWidget* parent = aWidget->GetParent(); + if (!parent) { + break; + } + LayoutDeviceIntRect bounds = aWidget->GetBounds(); + offset += bounds.TopLeft(); + aWidget = parent; + } + aRootWidget = aWidget; + return offset; +} + +static LayoutDeviceIntPoint +WidgetToWidgetOffset(nsIWidget* aFrom, nsIWidget* aTo) { + nsIWidget* fromRoot; + LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot); + nsIWidget* toRoot; + LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot); + + if (fromRoot == toRoot) { + return fromOffset - toOffset; + } + return aFrom->WidgetToScreenOffset() - aTo->WidgetToScreenOffset(); +} + +nsPoint +nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext, + nsIWidget* aWidget, const LayoutDeviceIntPoint& aPt, + nsView* aView) +{ + nsPoint viewOffset; + nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); + if (!viewWidget) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + LayoutDeviceIntPoint widgetPoint = aPt + WidgetToWidgetOffset(aWidget, viewWidget); + nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x), + aPresContext->DevPixelsToAppUnits(widgetPoint.y)); + return widgetAppUnits - viewOffset; +} + +LayoutDeviceIntPoint +nsLayoutUtils::TranslateViewToWidget(nsPresContext* aPresContext, + nsView* aView, nsPoint aPt, + nsIWidget* aWidget) +{ + nsPoint viewOffset; + nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); + if (!viewWidget) { + return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + nsPoint pt = (aPt + + viewOffset).ApplyResolution(GetCurrentAPZResolutionScale(aPresContext->PresShell())); + LayoutDeviceIntPoint relativeToViewWidget(aPresContext->AppUnitsToDevPixels(pt.x), + aPresContext->AppUnitsToDevPixels(pt.y)); + return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget); +} + +// Combine aNewBreakType with aOrigBreakType, but limit the break types +// to StyleClear::Left, Right, Both. +StyleClear +nsLayoutUtils::CombineBreakType(StyleClear aOrigBreakType, + StyleClear aNewBreakType) +{ + StyleClear breakType = aOrigBreakType; + switch(breakType) { + case StyleClear::Left: + if (StyleClear::Right == aNewBreakType || + StyleClear::Both == aNewBreakType) { + breakType = StyleClear::Both; + } + break; + case StyleClear::Right: + if (StyleClear::Left == aNewBreakType || + StyleClear::Both == aNewBreakType) { + breakType = StyleClear::Both; + } + break; + case StyleClear::None: + if (StyleClear::Left == aNewBreakType || + StyleClear::Right == aNewBreakType || + StyleClear::Both == aNewBreakType) { + breakType = aNewBreakType; + } + break; + default: + break; + } + return breakType; +} + +#ifdef MOZ_DUMP_PAINTING +#include + +static bool gDumpEventList = false; + +// nsLayoutUtils::PaintFrame() can call itself recursively, so rather than +// maintaining a single paint count, we need a stack. +StaticAutoPtr> gPaintCountStack; + +struct AutoNestedPaintCount { + AutoNestedPaintCount() { + gPaintCountStack->AppendElement(0); + } + ~AutoNestedPaintCount() { + gPaintCountStack->RemoveElementAt(gPaintCountStack->Length() - 1); + } +}; + +#endif + +nsIFrame* +nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt, uint32_t aFlags) +{ + PROFILER_LABEL("nsLayoutUtils", "GetFrameForPoint", + js::ProfileEntry::Category::GRAPHICS); + + nsresult rv; + AutoTArray outFrames; + rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return outFrames.Length() ? outFrames.ElementAt(0) : nullptr; +} + +nsresult +nsLayoutUtils::GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect, + nsTArray &aOutFrames, + uint32_t aFlags) +{ + PROFILER_LABEL("nsLayoutUtils", "GetFramesForArea", + js::ProfileEntry::Category::GRAPHICS); + + nsDisplayListBuilder builder(aFrame, + nsDisplayListBuilderMode::EVENT_DELIVERY, + false); + nsDisplayList list; + + if (aFlags & IGNORE_PAINT_SUPPRESSION) { + builder.IgnorePaintSuppression(); + } + + if (aFlags & IGNORE_ROOT_SCROLL_FRAME) { + nsIFrame* rootScrollFrame = + aFrame->PresContext()->PresShell()->GetRootScrollFrame(); + if (rootScrollFrame) { + builder.SetIgnoreScrollFrame(rootScrollFrame); + } + } + if (aFlags & IGNORE_CROSS_DOC) { + builder.SetDescendIntoSubdocuments(false); + } + + builder.EnterPresShell(aFrame); + aFrame->BuildDisplayListForStackingContext(&builder, aRect, &list); + builder.LeavePresShell(aFrame, nullptr); + +#ifdef MOZ_DUMP_PAINTING + if (gDumpEventList) { + fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y); + + std::stringstream ss; + nsFrame::PrintDisplayList(&builder, list, ss); + print_stderr(ss); + } +#endif + + nsDisplayItem::HitTestState hitTestState; + list.HitTest(&builder, aRect, &hitTestState, &aOutFrames); + list.DeleteAll(); + return NS_OK; +} + +// aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame +FrameMetrics +nsLayoutUtils::CalculateBasicFrameMetrics(nsIScrollableFrame* aScrollFrame) { + nsIFrame* frame = do_QueryFrame(aScrollFrame); + MOZ_ASSERT(frame); + + // Calculate the metrics necessary for calculating the displayport. + // This code has a lot in common with the code in ComputeFrameMetrics(); + // we may want to refactor this at some point. + FrameMetrics metrics; + nsPresContext* presContext = frame->PresContext(); + nsIPresShell* presShell = presContext->PresShell(); + CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale(); + float resolution = 1.0f; + if (frame == presShell->GetRootScrollFrame()) { + // Only the root scrollable frame for a given presShell should pick up + // the presShell's resolution. All the other frames are 1.0. + resolution = presShell->GetResolution(); + } + // Note: unlike in ComputeFrameMetrics(), we don't know the full cumulative + // resolution including FrameMetrics::mExtraResolution, because layout hasn't + // chosen a resolution to paint at yet. However, the display port calculation + // divides out mExtraResolution anyways, so we get the correct result by + // setting the mCumulativeResolution to everything except the extra resolution + // and leaving mExtraResolution at 1. + LayoutDeviceToLayerScale2D cumulativeResolution( + presShell->GetCumulativeResolution() + * nsLayoutUtils::GetTransformToAncestorScale(frame)); + + LayerToParentLayerScale layerToParentLayerScale(1.0f); + metrics.SetDevPixelsPerCSSPixel(deviceScale); + metrics.SetPresShellResolution(resolution); + metrics.SetCumulativeResolution(cumulativeResolution); + metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale); + + // Only the size of the composition bounds is relevant to the + // displayport calculation, not its origin. + nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(frame); + LayoutDeviceToParentLayerScale2D compBoundsScale; + if (frame == presShell->GetRootScrollFrame() && presContext->IsRootContentDocument()) { + if (presContext->GetParentPresContext()) { + float res = presContext->GetParentPresContext()->PresShell()->GetCumulativeResolution(); + compBoundsScale = LayoutDeviceToParentLayerScale2D( + LayoutDeviceToParentLayerScale(res)); + } + } else { + compBoundsScale = cumulativeResolution * layerToParentLayerScale; + } + metrics.SetCompositionBounds( + LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize), + presContext->AppUnitsPerDevPixel()) + * compBoundsScale); + + metrics.SetRootCompositionSize( + nsLayoutUtils::CalculateRootCompositionSize(frame, false, metrics)); + + metrics.SetScrollOffset(CSSPoint::FromAppUnits( + aScrollFrame->GetScrollPosition())); + + metrics.SetScrollableRect(CSSRect::FromAppUnits( + nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr))); + + return metrics; +} + +bool +nsLayoutUtils::CalculateAndSetDisplayPortMargins(nsIScrollableFrame* aScrollFrame, + RepaintMode aRepaintMode) { + nsIFrame* frame = do_QueryFrame(aScrollFrame); + MOZ_ASSERT(frame); + nsIContent* content = frame->GetContent(); + MOZ_ASSERT(content); + + FrameMetrics metrics = CalculateBasicFrameMetrics(aScrollFrame); + ScreenMargin displayportMargins = APZCTreeManager::CalculatePendingDisplayPort( + metrics, ParentLayerPoint(0.0f, 0.0f)); + nsIPresShell* presShell = frame->PresContext()->GetPresShell(); + return nsLayoutUtils::SetDisplayPortMargins( + content, presShell, displayportMargins, 0, aRepaintMode); +} + +void +nsLayoutUtils::MaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder, + nsIFrame* aScrollFrame) { + nsIContent* content = aScrollFrame->GetContent(); + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame); + if (!content || !scrollableFrame) { + return; + } + + bool haveDisplayPort = HasDisplayPort(content); + + // We perform an optimization where we ensure that at least one + // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a displayport. + // If that's not the case yet, and we are async-scrollable, we will get a + // displayport. + if (aBuilder.IsPaintingToWindow() && + nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame) && + !aBuilder.HaveScrollableDisplayPort() && + scrollableFrame->WantAsyncScroll()) { + + // If we don't already have a displayport, calculate and set one. + if (!haveDisplayPort) { + CalculateAndSetDisplayPortMargins(scrollableFrame, nsLayoutUtils::RepaintMode::DoNotRepaint); +#ifdef DEBUG + haveDisplayPort = HasDisplayPort(content); + MOZ_ASSERT(haveDisplayPort, "should have a displayport after having just set it"); +#endif + } + + // Record that the we now have a scrollable display port. + aBuilder.SetHaveScrollableDisplayPort(); + } +} + +nsIScrollableFrame* +nsLayoutUtils::GetAsyncScrollableAncestorFrame(nsIFrame* aTarget) +{ + uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT + | nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE + | nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT; + return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags); +} + +void +nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(nsIFrame* aFrame, + RepaintMode aRepaintMode) +{ + nsIFrame* frame = aFrame; + while (frame) { + frame = nsLayoutUtils::GetCrossDocParentFrame(frame); + if (!frame) { + break; + } + nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame); + if (!scrollAncestor) { + break; + } + frame = do_QueryFrame(scrollAncestor); + MOZ_ASSERT(frame); + MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || + frame->PresContext()->PresShell()->GetRootScrollFrame() == frame); + if (nsLayoutUtils::AsyncPanZoomEnabled(frame) && + !nsLayoutUtils::HasDisplayPort(frame->GetContent())) { + nsLayoutUtils::SetDisplayPortMargins( + frame->GetContent(), frame->PresContext()->PresShell(), ScreenMargin(), 0, + aRepaintMode); + } + } +} + +void +nsLayoutUtils::ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame) +{ + nsIFrame* frame = aFrame; + while (frame) { + frame = nsLayoutUtils::GetCrossDocParentFrame(frame); + if (!frame) { + break; + } + nsIScrollableFrame* scrollAncestor = GetAsyncScrollableAncestorFrame(frame); + if (!scrollAncestor) { + break; + } + frame = do_QueryFrame(scrollAncestor); + MOZ_ASSERT(frame); + if (!frame) { + break; + } + MOZ_ASSERT(scrollAncestor->WantAsyncScroll() || + frame->PresContext()->PresShell()->GetRootScrollFrame() == frame); + if (nsLayoutUtils::HasDisplayPort(frame->GetContent())) { + scrollAncestor->TriggerDisplayPortExpiration(); + // Stop after the first trigger. If it failed, there's no point in + // continuing because all the rest of the frames we encounter are going + // to be ancestors of |scrollAncestor| which will keep its displayport. + // If the trigger succeeded, we stop because when the trigger executes + // it will call this function again to trigger the next ancestor up the + // chain. + break; + } + } +} + +nsresult +nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame, + const nsRegion& aDirtyRegion, nscolor aBackstop, + nsDisplayListBuilderMode aBuilderMode, + PaintFrameFlags aFlags) +{ + PROFILER_LABEL("nsLayoutUtils", "PaintFrame", + js::ProfileEntry::Category::GRAPHICS); + +#ifdef MOZ_DUMP_PAINTING + if (!gPaintCountStack) { + gPaintCountStack = new nsTArray(); + ClearOnShutdown(&gPaintCountStack); + + gPaintCountStack->AppendElement(0); + } + ++gPaintCountStack->LastElement(); + AutoNestedPaintCount nestedPaintCount; +#endif + + if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { + nsView* view = aFrame->GetView(); + if (!(view && view->GetWidget() && GetDisplayRootFrame(aFrame) == aFrame)) { + aFlags &= ~PaintFrameFlags::PAINT_WIDGET_LAYERS; + NS_ASSERTION(aRenderingContext, "need a rendering context"); + } + } + + nsPresContext* presContext = aFrame->PresContext(); + nsIPresShell* presShell = presContext->PresShell(); + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (!rootPresContext) { + return NS_OK; + } + + TimeStamp startBuildDisplayList = TimeStamp::Now(); + nsDisplayListBuilder builder(aFrame, aBuilderMode, + !(aFlags & PaintFrameFlags::PAINT_HIDE_CARET)); + if (aFlags & PaintFrameFlags::PAINT_IN_TRANSFORM) { + builder.SetInTransform(true); + } + if (aFlags & PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES) { + builder.SetSyncDecodeImages(true); + } + if (aFlags & (PaintFrameFlags::PAINT_WIDGET_LAYERS | + PaintFrameFlags::PAINT_TO_WINDOW)) { + builder.SetPaintingToWindow(true); + } + if (aFlags & PaintFrameFlags::PAINT_IGNORE_SUPPRESSION) { + builder.IgnorePaintSuppression(); + } + + nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); + if (rootScrollFrame && !aFrame->GetParent()) { + nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); + MOZ_ASSERT(rootScrollableFrame); + nsRect displayPortBase = aFrame->GetVisualOverflowRectRelativeToSelf(); + Unused << rootScrollableFrame->DecideScrollableLayer(&builder, &displayPortBase, + /* aAllowCreateDisplayPort = */ true); + } + + nsRegion visibleRegion; + if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { + // This layer tree will be reused, so we'll need to calculate it + // for the whole "visible" area of the window + // + // |ignoreViewportScrolling| and |usingDisplayPort| are persistent + // document-rendering state. We rely on PresShell to flush + // retained layers as needed when that persistent state changes. + visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); + } else { + visibleRegion = aDirtyRegion; + } + + nsDisplayList list; + + // If the root has embedded plugins, flag the builder so we know we'll need + // to update plugin geometry after painting. + if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && + !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) && + rootPresContext->NeedToComputePluginGeometryUpdates()) { + builder.SetWillComputePluginGeometry(true); + } + + nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize()); + bool ignoreViewportScrolling = + aFrame->GetParent() ? false : presShell->IgnoringViewportScrolling(); + if (ignoreViewportScrolling && rootScrollFrame) { + nsIScrollableFrame* rootScrollableFrame = + presShell->GetRootScrollFrameAsScrollable(); + if (aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE) { + // Make visibleRegion and aRenderingContext relative to the + // scrolled frame instead of the root frame. + nsPoint pos = rootScrollableFrame->GetScrollPosition(); + visibleRegion.MoveBy(-pos); + if (aRenderingContext) { + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(pos, + presContext->AppUnitsPerDevPixel()); + aRenderingContext->ThebesContext()->SetMatrix( + aRenderingContext->ThebesContext()->CurrentMatrix().Translate(devPixelOffset)); + } + } + builder.SetIgnoreScrollFrame(rootScrollFrame); + + nsCanvasFrame* canvasFrame = + do_QueryFrame(rootScrollableFrame->GetScrolledFrame()); + if (canvasFrame) { + // Use UnionRect here to ensure that areas where the scrollbars + // were are still filled with the background color. + canvasArea.UnionRect(canvasArea, + canvasFrame->CanvasArea() + builder.ToReferenceFrame(canvasFrame)); + } + } + + builder.EnterPresShell(aFrame); + nsRect dirtyRect = visibleRegion.GetBounds(); + { + // If a scrollable container layer is created in nsDisplayList::PaintForFrame, + // it will be the scroll parent for display items that are built in the + // BuildDisplayListForStackingContext call below. We need to set the scroll + // parent on the display list builder while we build those items, so that they + // can pick up their scroll parent's id. + ViewID id = FrameMetrics::NULL_SCROLL_ID; + if (ignoreViewportScrolling && presContext->IsRootContentDocument()) { + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + if (nsIContent* content = rootScrollFrame->GetContent()) { + id = nsLayoutUtils::FindOrCreateIDFor(content); + } + } + } + else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument() + && !presShell->GetRootScrollFrame()) { + // In cases where the root document is a XUL document, we want to take + // the ViewID from the root element, as that will be the ViewID of the + // root APZC in the tree. Skip doing this in cases where we know + // nsGfxScrollFrame::BuilDisplayList will do it instead. + if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) { + id = nsLayoutUtils::FindOrCreateIDFor(element); + } + } + + nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id); + + PROFILER_LABEL("nsLayoutUtils", "PaintFrame::BuildDisplayList", + js::ProfileEntry::Category::GRAPHICS); + + + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList); + aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list); + } + + nsIAtom* frameType = aFrame->GetType(); + + // For the viewport frame in print preview/page layout we want to paint + // the grey background behind the page, not the canvas color. + if (frameType == nsGkAtoms::viewportFrame && + nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) { + nsRect bounds = nsRect(builder.ToReferenceFrame(aFrame), + aFrame->GetSize()); + nsDisplayListBuilder::AutoBuildingDisplayList + buildingDisplayList(&builder, aFrame, bounds, false); + presShell->AddPrintPreviewBackgroundItem(builder, list, aFrame, bounds); + } else if (frameType != nsGkAtoms::pageFrame) { + // For printing, this function is first called on an nsPageFrame, which + // creates a display list with a PageContent item. The PageContent item's + // paint function calls this function on the nsPageFrame's child which is + // an nsPageContentFrame. We only want to add the canvas background color + // item once, for the nsPageContentFrame. + + // Add the canvas background color to the bottom of the list. This + // happens after we've built the list so that AddCanvasBackgroundColorItem + // can monkey with the contents if necessary. + canvasArea.IntersectRect(canvasArea, visibleRegion.GetBounds()); + nsDisplayListBuilder::AutoBuildingDisplayList + buildingDisplayList(&builder, aFrame, canvasArea, false); + presShell->AddCanvasBackgroundColorItem( + builder, list, aFrame, canvasArea, aBackstop); + } + + builder.LeavePresShell(aFrame, &list); + Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME, + startBuildDisplayList); + + bool profilerNeedsDisplayList = profiler_feature_active("displaylistdump"); + bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint(); +#ifdef MOZ_DUMP_PAINTING + FILE* savedDumpFile = gfxUtils::sDumpPaintFile; +#endif + + UniquePtr ss; + if (consoleNeedsDisplayList || profilerNeedsDisplayList) { + ss = MakeUnique(); +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpPaintToFile()) { + nsCString string("dump-"); + // Include the process ID in the dump file name, to make sure that in an + // e10s setup different processes don't clobber each other's dump files. + string.AppendInt(getpid()); + for (int paintCount : *gPaintCountStack) { + string.AppendLiteral("-"); + string.AppendInt(paintCount); + } + string.AppendLiteral(".html"); + gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w"); + } else { + gfxUtils::sDumpPaintFile = stderr; + } + if (gfxEnv::DumpPaintToFile()) { + *ss << ""; + } +#endif + *ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n", + dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height).get(); + nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile()); + + if (gfxEnv::DumpPaint() || gfxEnv::DumpPaintItems()) { + // Flush stream now to avoid reordering dump output relative to + // messages dumped by PaintRoot below. + if (profilerNeedsDisplayList && !consoleNeedsDisplayList) { + profiler_log(ss->str().c_str()); + } else { + // Send to the console which will send to the profiler if required. + fprint_stderr(gfxUtils::sDumpPaintFile, *ss); + } + ss = MakeUnique(); + } + } + + uint32_t flags = nsDisplayList::PAINT_DEFAULT; + if (aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) { + flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS; + if (!(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) { + nsIWidget *widget = aFrame->GetNearestWidget(); + if (widget) { + // If we're finished building display list items for painting of the outermost + // pres shell, notify the widget about any toolbars we've encountered. + widget->UpdateThemeGeometries(builder.GetThemeGeometries()); + } + } + } + if (aFlags & PaintFrameFlags::PAINT_EXISTING_TRANSACTION) { + flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION; + } + if (aFlags & PaintFrameFlags::PAINT_NO_COMPOSITE) { + flags |= nsDisplayList::PAINT_NO_COMPOSITE; + } + if (aFlags & PaintFrameFlags::PAINT_COMPRESSED) { + flags |= nsDisplayList::PAINT_COMPRESSED; + } + + TimeStamp paintStart = TimeStamp::Now(); + RefPtr layerManager + = list.PaintRoot(&builder, aRenderingContext, flags); + Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, + paintStart); + + if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) { + TimeStamp now = TimeStamp::Now(); + float rasterizeTime = (now - paintStart).ToMilliseconds(); + uint32_t pixelCount = layerManager->GetAndClearPaintedPixelCount(); + static std::vector> history; + if (pixelCount) { + history.push_back(std::make_pair(now, pixelCount)); + } + uint32_t paintedInLastSecond = 0; + for (auto i = history.begin(); i != history.end(); i++) { + if ((now - i->first).ToMilliseconds() > 1000.0f) { + // more than 1000ms ago, don't count it + continue; + } + if (paintedInLastSecond == 0) { + // This is the first one in the last 1000ms, so drop everything earlier + history.erase(history.begin(), i); + i = history.begin(); + } + paintedInLastSecond += i->second; + MOZ_ASSERT(paintedInLastSecond); // all historical pixel counts are > 0 + } + printf_stderr("Painted %u pixels in %fms (%u in the last 1000ms)\n", + pixelCount, rasterizeTime, paintedInLastSecond); + } + + if (consoleNeedsDisplayList || profilerNeedsDisplayList) { + *ss << "Painting --- after optimization:\n"; + nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile()); + + *ss << "Painting --- layer tree:\n"; + if (layerManager) { + FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss, + gfxEnv::DumpPaintToFile()); + } + + if (profilerNeedsDisplayList && !consoleNeedsDisplayList) { + profiler_log(ss->str().c_str()); + } else { + // Send to the console which will send to the profiler if required. + fprint_stderr(gfxUtils::sDumpPaintFile, *ss); + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpPaintToFile()) { + *ss << ""; + } + if (gfxEnv::DumpPaintToFile()) { + fclose(gfxUtils::sDumpPaintFile); + } + gfxUtils::sDumpPaintFile = savedDumpFile; +#endif + + std::stringstream lsStream; + nsFrame::PrintDisplayList(&builder, list, lsStream); + layerManager->GetRoot()->SetDisplayListLog(lsStream.str().c_str()); + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxPrefs::DumpClientLayers()) { + std::stringstream ss; + FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, false); + print_stderr(ss); + } +#endif + + // Update the widget's opaque region information. This sets + // glass boundaries on Windows. Also set up the window dragging region + // and plugin clip regions and bounds. + if ((aFlags & PaintFrameFlags::PAINT_WIDGET_LAYERS) && + !(aFlags & PaintFrameFlags::PAINT_DOCUMENT_RELATIVE)) { + nsIWidget *widget = aFrame->GetNearestWidget(); + if (widget) { + nsRegion opaqueRegion; + opaqueRegion.And(builder.GetWindowExcludeGlassRegion(), builder.GetWindowOpaqueRegion()); + widget->UpdateOpaqueRegion( + LayoutDeviceIntRegion::FromUnknownRegion( + opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel()))); + + widget->UpdateWindowDraggingRegion(builder.GetWindowDraggingRegion()); + } + } + + if (builder.WillComputePluginGeometry()) { + // For single process compute and apply plugin geometry updates to plugin + // windows, then request composition. For content processes skip eveything + // except requesting composition. Geometry updates were calculated and + // shipped to the chrome process in nsDisplayList when the layer + // transaction completed. + if (XRE_IsParentProcess()) { + rootPresContext->ComputePluginGeometryUpdates(aFrame, &builder, &list); + // We're not going to get a WillPaintWindow event here if we didn't do + // widget invalidation, so just apply the plugin geometry update here + // instead. We could instead have the compositor send back an equivalent + // to WillPaintWindow, but it should be close enough to now not to matter. + if (layerManager && !layerManager->NeedsWidgetInvalidation()) { + rootPresContext->ApplyPluginGeometryUpdates(); + } + } + + // We told the compositor thread not to composite when it received the + // transaction because we wanted to update plugins first. Schedule the + // composite now. + if (layerManager) { + layerManager->Composite(); + } + } + + + // Flush the list so we don't trigger the IsEmpty-on-destruction assertion + list.DeleteAll(); + return NS_OK; +} + +/** + * Uses a binary search for find where the cursor falls in the line of text + * It also keeps track of the part of the string that has already been measured + * so it doesn't have to keep measuring the same text over and over + * + * @param "aBaseWidth" contains the width in twips of the portion + * of the text that has already been measured, and aBaseInx contains + * the index of the text that has already been measured. + * + * @param aTextWidth returns the (in twips) the length of the text that falls + * before the cursor aIndex contains the index of the text where the cursor falls + */ +bool +nsLayoutUtils::BinarySearchForPosition(DrawTarget* aDrawTarget, + nsFontMetrics& aFontMetrics, + const char16_t* aText, + int32_t aBaseWidth, + int32_t aBaseInx, + int32_t aStartInx, + int32_t aEndInx, + int32_t aCursorPos, + int32_t& aIndex, + int32_t& aTextWidth) +{ + int32_t range = aEndInx - aStartInx; + if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) { + aIndex = aStartInx + aBaseInx; + aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex, + aFontMetrics, aDrawTarget); + return true; + } + + int32_t inx = aStartInx + (range / 2); + + // Make sure we don't leave a dangling low surrogate + if (NS_IS_HIGH_SURROGATE(aText[inx-1])) + inx++; + + int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(aText, inx, + aFontMetrics, + aDrawTarget); + + int32_t fullWidth = aBaseWidth + textWidth; + if (fullWidth == aCursorPos) { + aTextWidth = textWidth; + aIndex = inx; + return true; + } else if (aCursorPos < fullWidth) { + aTextWidth = aBaseWidth; + if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth, + aBaseInx, aStartInx, inx, aCursorPos, aIndex, + aTextWidth)) { + return true; + } + } else { + aTextWidth = fullWidth; + if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth, + aBaseInx, inx, aEndInx, aCursorPos, aIndex, + aTextWidth)) { + return true; + } + } + return false; +} + +static void +AddBoxesForFrame(nsIFrame* aFrame, + nsLayoutUtils::BoxCallback* aCallback) +{ + nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo(); + + if (pseudoType == nsCSSAnonBoxes::tableWrapper) { + AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback); + if (aCallback->mIncludeCaptionBoxForTable) { + nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); + if (kid) { + AddBoxesForFrame(kid, aCallback); + } + } + } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock || + pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) { + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + AddBoxesForFrame(kid, aCallback); + } + } else { + aCallback->AddBox(aFrame); + } +} + +void +nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback) +{ + while (aFrame) { + AddBoxesForFrame(aFrame, aCallback); + aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); + } +} + +nsIFrame* +nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) +{ + while (aFrame) { + nsIAtom* pseudoType = aFrame->StyleContext()->GetPseudo(); + + if (pseudoType == nsCSSAnonBoxes::tableWrapper) { + nsIFrame* f = GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild()); + if (f) { + return f; + } + nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild(); + if (kid) { + f = GetFirstNonAnonymousFrame(kid); + if (f) { + return f; + } + } + } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock || + pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) { + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + nsIFrame* f = GetFirstNonAnonymousFrame(kid); + if (f) { + return f; + } + } + } else { + return aFrame; + } + + aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); + } + return nullptr; +} + +struct BoxToRect : public nsLayoutUtils::BoxCallback { + nsIFrame* mRelativeTo; + nsLayoutUtils::RectCallback* mCallback; + uint32_t mFlags; + + BoxToRect(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback, + uint32_t aFlags) + : mRelativeTo(aRelativeTo), mCallback(aCallback), mFlags(aFlags) {} + + virtual void AddBox(nsIFrame* aFrame) override { + nsRect r; + nsIFrame* outer = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r); + if (!outer) { + outer = aFrame; + switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) { + case nsLayoutUtils::RECTS_USE_CONTENT_BOX: + r = aFrame->GetContentRectRelativeToSelf(); + break; + case nsLayoutUtils::RECTS_USE_PADDING_BOX: + r = aFrame->GetPaddingRectRelativeToSelf(); + break; + case nsLayoutUtils::RECTS_USE_MARGIN_BOX: + r = aFrame->GetMarginRectRelativeToSelf(); + break; + default: // Use the border box + r = aFrame->GetRectRelativeToSelf(); + } + } + if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) { + r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo); + } else { + r += outer->GetOffsetTo(mRelativeTo); + } + mCallback->AddRect(r); + } +}; + +struct BoxToRectAndText : public BoxToRect { + mozilla::dom::DOMStringList* mTextList; + + BoxToRectAndText(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback, + mozilla::dom::DOMStringList* aTextList, uint32_t aFlags) + : BoxToRect(aRelativeTo, aCallback, aFlags), mTextList(aTextList) {} + + virtual void AddBox(nsIFrame* aFrame) override { + BoxToRect::AddBox(aFrame); + if (mTextList) { + nsIContent* content = aFrame->GetContent(); + nsAutoString textContent; + mozilla::ErrorResult err; // ignored + content->GetTextContent(textContent, err); + mTextList->Add(textContent); + } + } +}; + +void +nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo, + RectCallback* aCallback, uint32_t aFlags) +{ + BoxToRect converter(aRelativeTo, aCallback, aFlags); + GetAllInFlowBoxes(aFrame, &converter); +} + +void +nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo, + RectCallback* aCallback, + mozilla::dom::DOMStringList* aTextList, + uint32_t aFlags) +{ + BoxToRectAndText converter(aRelativeTo, aCallback, aTextList, aFlags); + GetAllInFlowBoxes(aFrame, &converter); +} + +nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {} + +void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) { + mResultRect.UnionRect(mResultRect, aRect); + if (!mSeenFirstRect) { + mSeenFirstRect = true; + mFirstRect = aRect; + } +} + +nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList) + : mRectList(aList) +{ +} + +void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) { + RefPtr rect = new DOMRect(mRectList); + + rect->SetLayoutRect(aRect); + mRectList->Append(rect); +} + +nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) +{ + return aFrame->PresContext()->PresShell()->GetRootFrame(); +} + +nsRect +nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo, + uint32_t aFlags) { + RectAccumulator accumulator; + GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags); + return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect + : accumulator.mResultRect; +} + +nsRect +nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect, + nsIFrame* aFrame, + uint32_t aFlags) +{ + const nsStyleText* textStyle = aFrame->StyleText(); + if (!textStyle->HasTextShadow()) + return aTextAndDecorationsRect; + + nsRect resultRect = aTextAndDecorationsRect; + int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel(); + for (uint32_t i = 0; i < textStyle->mTextShadow->Length(); ++i) { + nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i); + nsMargin blur = nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D); + if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0)) + continue; + + nsRect tmpRect(aTextAndDecorationsRect); + + tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset)); + tmpRect.Inflate(blur); + + resultRect.UnionRect(resultRect, tmpRect); + } + return resultRect; +} + +enum ObjectDimensionType { eWidth, eHeight }; +static nscoord +ComputeMissingDimension(const nsSize& aDefaultObjectSize, + const nsSize& aIntrinsicRatio, + const Maybe& aSpecifiedWidth, + const Maybe& aSpecifiedHeight, + ObjectDimensionType aDimensionToCompute) +{ + // The "default sizing algorithm" computes the missing dimension as follows: + // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing ) + + // 1. "If the object has an intrinsic aspect ratio, the missing dimension of + // the concrete object size is calculated using the intrinsic aspect + // ratio and the present dimension." + if (aIntrinsicRatio.width > 0 && aIntrinsicRatio.height > 0) { + // Fill in the missing dimension using the intrinsic aspect ratio. + nscoord knownDimensionSize; + float ratio; + if (aDimensionToCompute == eWidth) { + knownDimensionSize = *aSpecifiedHeight; + ratio = aIntrinsicRatio.width / aIntrinsicRatio.height; + } else { + knownDimensionSize = *aSpecifiedWidth; + ratio = aIntrinsicRatio.height / aIntrinsicRatio.width; + } + return NSCoordSaturatingNonnegativeMultiply(knownDimensionSize, ratio); + } + + // 2. "Otherwise, if the missing dimension is present in the object’s + // intrinsic dimensions, [...]" + // NOTE: *Skipping* this case, because we already know it's not true -- we're + // in this function because the missing dimension is *not* present in + // the object's intrinsic dimensions. + + // 3. "Otherwise, the missing dimension of the concrete object size is taken + // from the default object size. " + return (aDimensionToCompute == eWidth) ? + aDefaultObjectSize.width : aDefaultObjectSize.height; +} + +/* + * This computes & returns the concrete object size of replaced content, if + * that content were to be rendered with "object-fit: none". (Or, if the + * element has neither an intrinsic height nor width, this method returns an + * empty Maybe<> object.) + * + * As specced... + * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none + * ..we use "the default sizing algorithm with no specified size, + * and a default object size equal to the replaced element's used width and + * height." + * + * The default sizing algorithm is described here: + * http://dev.w3.org/csswg/css-images-3/#default-sizing + * Quotes in the function-impl are taken from that ^ spec-text. + * + * Per its final bulleted section: since there's no specified size, + * we run the default sizing algorithm using the object's intrinsic size in + * place of the specified size. But if the object has neither an intrinsic + * height nor an intrinsic width, then we instead return without populating our + * outparam, and we let the caller figure out the size (using a contain + * constraint). + */ +static Maybe +MaybeComputeObjectFitNoneSize(const nsSize& aDefaultObjectSize, + const IntrinsicSize& aIntrinsicSize, + const nsSize& aIntrinsicRatio) +{ + // "If the object has an intrinsic height or width, its size is resolved as + // if its intrinsic dimensions were given as the specified size." + // + // So, first we check if we have an intrinsic height and/or width: + Maybe specifiedWidth; + if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) { + specifiedWidth.emplace(aIntrinsicSize.width.GetCoordValue()); + } + + Maybe specifiedHeight; + if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { + specifiedHeight.emplace(aIntrinsicSize.height.GetCoordValue()); + } + + Maybe noneSize; // (the value we'll return) + if (specifiedWidth || specifiedHeight) { + // We have at least one specified dimension; use whichever dimension is + // specified, and compute the other one using our intrinsic ratio, or (if + // no valid ratio) using the default object size. + noneSize.emplace(); + + noneSize->width = specifiedWidth ? + *specifiedWidth : + ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio, + specifiedWidth, specifiedHeight, + eWidth); + + noneSize->height = specifiedHeight ? + *specifiedHeight : + ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio, + specifiedWidth, specifiedHeight, + eHeight); + } + // [else:] "Otherwise [if there's neither an intrinsic height nor width], its + // size is resolved as a contain constraint against the default object size." + // We'll let our caller do that, to share code & avoid redundant + // computations; so, we return w/out populating noneSize. + return noneSize; +} + +// Computes the concrete object size to render into, as described at +// http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution +static nsSize +ComputeConcreteObjectSize(const nsSize& aConstraintSize, + const IntrinsicSize& aIntrinsicSize, + const nsSize& aIntrinsicRatio, + uint8_t aObjectFit) +{ + // Handle default behavior (filling the container) w/ fast early return. + // (Also: if there's no valid intrinsic ratio, then we have the "fill" + // behavior & just use the constraint size.) + if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) || + aIntrinsicRatio.width == 0 || + aIntrinsicRatio.height == 0) { + return aConstraintSize; + } + + // The type of constraint to compute (cover/contain), if needed: + Maybe fitType; + + Maybe noneSize; + if (aObjectFit == NS_STYLE_OBJECT_FIT_NONE || + aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) { + noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize, + aIntrinsicRatio); + if (!noneSize || aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) { + // Need to compute a 'CONTAIN' constraint (either for the 'none' size + // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.) + fitType.emplace(nsImageRenderer::CONTAIN); + } + } else if (aObjectFit == NS_STYLE_OBJECT_FIT_COVER) { + fitType.emplace(nsImageRenderer::COVER); + } else if (aObjectFit == NS_STYLE_OBJECT_FIT_CONTAIN) { + fitType.emplace(nsImageRenderer::CONTAIN); + } + + Maybe constrainedSize; + if (fitType) { + constrainedSize.emplace( + nsImageRenderer::ComputeConstrainedSize(aConstraintSize, + aIntrinsicRatio, + *fitType)); + } + + // Now, we should have all the sizing information that we need. + switch (aObjectFit) { + // skipping NS_STYLE_OBJECT_FIT_FILL; we handled it w/ early-return. + case NS_STYLE_OBJECT_FIT_CONTAIN: + case NS_STYLE_OBJECT_FIT_COVER: + MOZ_ASSERT(constrainedSize); + return *constrainedSize; + + case NS_STYLE_OBJECT_FIT_NONE: + if (noneSize) { + return *noneSize; + } + MOZ_ASSERT(constrainedSize); + return *constrainedSize; + + case NS_STYLE_OBJECT_FIT_SCALE_DOWN: + MOZ_ASSERT(constrainedSize); + if (noneSize) { + constrainedSize->width = + std::min(constrainedSize->width, noneSize->width); + constrainedSize->height = + std::min(constrainedSize->height, noneSize->height); + } + return *constrainedSize; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'"); + return aConstraintSize; // fall back to (default) 'fill' behavior + } +} + +// (Helper for HasInitialObjectFitAndPosition, to check +// each "object-position" coord.) +static bool +IsCoord50Pct(const mozilla::Position::Coord& aCoord) +{ + return (aCoord.mLength == 0 && + aCoord.mHasPercent && + aCoord.mPercent == 0.5f); +} + +// Indicates whether the given nsStylePosition has the initial values +// for the "object-fit" and "object-position" properties. +static bool +HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos) +{ + const mozilla::Position& objectPos = aStylePos->mObjectPosition; + + return aStylePos->mObjectFit == NS_STYLE_OBJECT_FIT_FILL && + IsCoord50Pct(objectPos.mXPosition) && + IsCoord50Pct(objectPos.mYPosition); +} + +/* static */ nsRect +nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect, + const IntrinsicSize& aIntrinsicSize, + const nsSize& aIntrinsicRatio, + const nsStylePosition* aStylePos, + nsPoint* aAnchorPoint) +{ + // Step 1: Figure out our "concrete object size" + // (the size of the region we'll actually draw our image's pixels into). + nsSize concreteObjectSize = + ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize, + aIntrinsicRatio, aStylePos->mObjectFit); + + // Step 2: Figure out how to align that region in the element's content-box. + nsPoint imageTopLeftPt, imageAnchorPt; + nsImageRenderer::ComputeObjectAnchorPoint(aStylePos->mObjectPosition, + aConstraintRect.Size(), + concreteObjectSize, + &imageTopLeftPt, &imageAnchorPt); + // Right now, we're with respect to aConstraintRect's top-left point. We add + // that point here, to convert to the same broader coordinate space that + // aConstraintRect is in. + imageTopLeftPt += aConstraintRect.TopLeft(); + imageAnchorPt += aConstraintRect.TopLeft(); + + if (aAnchorPoint) { + // Special-case: if our "object-fit" and "object-position" properties have + // their default values ("object-fit: fill; object-position:50% 50%"), then + // we'll override the calculated imageAnchorPt, and instead use the + // object's top-left corner. + // + // This special case is partly for backwards compatibility (since + // traditionally we've pixel-aligned the top-left corner of e.g. + // elements), and partly because ComputeSnappedDrawingParameters produces + // less error if the anchor point is at the top-left corner. So, all other + // things being equal, we prefer that code path with less error. + if (HasInitialObjectFitAndPosition(aStylePos)) { + *aAnchorPoint = imageTopLeftPt; + } else { + *aAnchorPoint = imageAnchorPt; + } + } + return nsRect(imageTopLeftPt, concreteObjectSize); +} + +already_AddRefed +nsLayoutUtils::GetFontMetricsForFrame(const nsIFrame* aFrame, float aInflation) +{ + nsStyleContext* styleContext = aFrame->StyleContext(); + uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL; + if (styleContext->IsTextCombined()) { + MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::textFrame); + auto textFrame = static_cast(aFrame); + auto clusters = textFrame->CountGraphemeClusters(); + if (clusters == 2) { + variantWidth = NS_FONT_VARIANT_WIDTH_HALF; + } else if (clusters == 3) { + variantWidth = NS_FONT_VARIANT_WIDTH_THIRD; + } else if (clusters == 4) { + variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER; + } + } + return GetFontMetricsForStyleContext(styleContext, aInflation, variantWidth); +} + +already_AddRefed +nsLayoutUtils::GetFontMetricsForStyleContext(nsStyleContext* aStyleContext, + float aInflation, + uint8_t aVariantWidth) +{ + nsPresContext* pc = aStyleContext->PresContext(); + + WritingMode wm(aStyleContext); + const nsStyleFont* styleFont = aStyleContext->StyleFont(); + nsFontMetrics::Params params; + params.language = styleFont->mLanguage; + params.explicitLanguage = styleFont->mExplicitLanguage; + params.orientation = + wm.IsVertical() && !wm.IsSideways() ? gfxFont::eVertical + : gfxFont::eHorizontal; + // pass the user font set object into the device context to + // pass along to CreateFontGroup + params.userFontSet = pc->GetUserFontSet(); + params.textPerf = pc->GetTextPerfMetrics(); + + // When aInflation is 1.0 and we don't require width variant, avoid + // making a local copy of the nsFont. + // This also avoids running font.size through floats when it is large, + // which would be lossy. Fortunately, in such cases, aInflation is + // guaranteed to be 1.0f. + if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) { + return pc->DeviceContext()->GetMetricsFor(styleFont->mFont, params); + } + + nsFont font = styleFont->mFont; + font.size = NSToCoordRound(font.size * aInflation); + font.variantWidth = aVariantWidth; + return pc->DeviceContext()->GetMetricsFor(font, params); +} + +nsIFrame* +nsLayoutUtils::FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame) +{ + nsIFrame* result = aDescendantFrame; + + while (result) { + nsIFrame* parent = result->GetParent(); + if (parent == aParent) { + break; + } + + // The frame is not an immediate child of aParent so walk up another level + result = parent; + } + + return result; +} + +nsBlockFrame* +nsLayoutUtils::GetAsBlock(nsIFrame* aFrame) +{ + nsBlockFrame* block = do_QueryFrame(aFrame); + return block; +} + +nsBlockFrame* +nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame) +{ + nsIFrame* nextAncestor; + for (nextAncestor = aFrame->GetParent(); nextAncestor; + nextAncestor = nextAncestor->GetParent()) { + nsBlockFrame* block = GetAsBlock(nextAncestor); + if (block) + return block; + } + return nullptr; +} + +nsIFrame* +nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame) +{ + if (!(aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) + return aFrame; + + nsIFrame* f = aFrame; + do { + f = GetParentOrPlaceholderFor(f); + } while (f->GetStateBits() & NS_FRAME_GENERATED_CONTENT); + return f; +} + +nsIFrame* +nsLayoutUtils::GetParentOrPlaceholderFor(nsIFrame* aFrame) +{ + if ((aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) + && !aFrame->GetPrevInFlow()) { + return aFrame->PresContext()->PresShell()->FrameManager()-> + GetPlaceholderFrameFor(aFrame); + } + return aFrame->GetParent(); +} + +nsIFrame* +nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(nsIFrame* aFrame) +{ + nsIFrame* f = GetParentOrPlaceholderFor(aFrame); + if (f) + return f; + return GetCrossDocParentFrame(aFrame); +} + +nsIFrame* +nsLayoutUtils::GetNextContinuationOrIBSplitSibling(nsIFrame *aFrame) +{ + nsIFrame *result = aFrame->GetNextContinuation(); + if (result) + return result; + + if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) != 0) { + // We only store the ib-split sibling annotation with the first + // frame in the continuation chain. Walk back to find that frame now. + aFrame = aFrame->FirstContinuation(); + + return aFrame->Properties().Get(nsIFrame::IBSplitSibling()); + } + + return nullptr; +} + +nsIFrame* +nsLayoutUtils::FirstContinuationOrIBSplitSibling(nsIFrame *aFrame) +{ + nsIFrame *result = aFrame->FirstContinuation(); + if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { + while (true) { + nsIFrame* f = + result->Properties().Get(nsIFrame::IBSplitPrevSibling()); + if (!f) + break; + result = f; + } + } + + return result; +} + +nsIFrame* +nsLayoutUtils::LastContinuationOrIBSplitSibling(nsIFrame *aFrame) +{ + nsIFrame *result = aFrame->FirstContinuation(); + if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { + while (true) { + nsIFrame* f = + result->Properties().Get(nsIFrame::IBSplitSibling()); + if (!f) + break; + result = f; + } + } + + result = result->LastContinuation(); + + return result; +} + +bool +nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(nsIFrame *aFrame) +{ + if (aFrame->GetPrevContinuation()) { + return false; + } + if ((aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) && + aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling())) { + return false; + } + + return true; +} + +bool +nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame) +{ + if (!aFrame) + return false; + + nsIFrame* rootScrollFrame = + aFrame->PresContext()->PresShell()->GetRootScrollFrame(); + if (!rootScrollFrame) + return false; + + nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame); + NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null"); + + if (!IsProperAncestorFrame(rootScrollFrame, aFrame)) + return false; + + nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame(); + return !(rootScrolledFrame == aFrame || + IsProperAncestorFrame(rootScrolledFrame, aFrame)); +} + +// Use only for widths/heights (or their min/max), since it clamps +// negative calc() results to 0. +static bool GetAbsoluteCoord(const nsStyleCoord& aStyle, nscoord& aResult) +{ + if (aStyle.IsCalcUnit()) { + if (aStyle.CalcHasPercent()) { + return false; + } + // If it has no percents, we can pass 0 for the percentage basis. + aResult = nsRuleNode::ComputeComputedCalc(aStyle, 0); + if (aResult < 0) + aResult = 0; + return true; + } + + if (eStyleUnit_Coord != aStyle.GetUnit()) + return false; + + aResult = aStyle.GetCoordValue(); + NS_ASSERTION(aResult >= 0, "negative widths not allowed"); + return true; +} + +static nscoord +GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, + nsIFrame* aFrame, + bool aHorizontalAxis, + bool aIgnorePadding); + +// Only call on style coords for which GetAbsoluteCoord returned false. +static bool +GetPercentBSize(const nsStyleCoord& aStyle, + nsIFrame* aFrame, + bool aHorizontalAxis, + nscoord& aResult) +{ + if (eStyleUnit_Percent != aStyle.GetUnit() && + !aStyle.IsCalcUnit()) + return false; + + MOZ_ASSERT(!aStyle.IsCalcUnit() || aStyle.CalcHasPercent(), + "GetAbsoluteCoord should have handled this"); + + // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses + // SetComputedHeight on the reflow state for its child to propagate its + // computed height to the scrolled content. So here we skip to the scroll + // frame that contains this scrolled content in order to get the same + // behavior as layout when computing percentage heights. + nsIFrame *f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); + if (!f) { + NS_NOTREACHED("top of frame tree not a containing block"); + return false; + } + + WritingMode wm = f->GetWritingMode(); + + const nsStylePosition *pos = f->StylePosition(); + const nsStyleCoord& bSizeCoord = pos->BSize(wm); + nscoord h; + if (!GetAbsoluteCoord(bSizeCoord, h) && + !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) { + NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto || + bSizeCoord.HasPercent(), + "unknown block-size unit"); + nsIAtom* fType = f->GetType(); + if (fType != nsGkAtoms::viewportFrame && fType != nsGkAtoms::canvasFrame && + fType != nsGkAtoms::pageContentFrame) { + // There's no basis for the percentage height, so it acts like auto. + // Should we consider a max-height < min-height pair a basis for + // percentage heights? The spec is somewhat unclear, and not doing + // so is simpler and avoids troubling discontinuities in behavior, + // so I'll choose not to. -LDB + return false; + } + + NS_ASSERTION(bSizeCoord.GetUnit() == eStyleUnit_Auto, + "Unexpected block-size unit for viewport or canvas or page-content"); + // For the viewport, canvas, and page-content kids, the percentage + // basis is just the parent block-size. + h = f->BSize(wm); + if (h == NS_UNCONSTRAINEDSIZE) { + // We don't have a percentage basis after all + return false; + } + } + + const nsStyleCoord& maxBSizeCoord = pos->MaxBSize(wm); + + nscoord maxh; + if (GetAbsoluteCoord(maxBSizeCoord, maxh) || + GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) { + if (maxh < h) + h = maxh; + } else { + NS_ASSERTION(maxBSizeCoord.GetUnit() == eStyleUnit_None || + maxBSizeCoord.HasPercent(), + "unknown max block-size unit"); + } + + const nsStyleCoord& minBSizeCoord = pos->MinBSize(wm); + + nscoord minh; + if (GetAbsoluteCoord(minBSizeCoord, minh) || + GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) { + if (minh > h) + h = minh; + } else { + NS_ASSERTION(minBSizeCoord.HasPercent() || + minBSizeCoord.GetUnit() == eStyleUnit_Auto, + "unknown min block-size unit"); + } + + // Now adjust h for box-sizing styles on the parent. We never ignore padding + // here. That could conceivably cause some problems with fieldsets (which are + // the one place that wants to ignore padding), but solving that here without + // hardcoding a check for f being a fieldset-content frame is a bit of a pain. + nscoord bSizeTakenByBoxSizing = + GetBSizeTakenByBoxSizing(pos->mBoxSizing, f, aHorizontalAxis, false); + h = std::max(0, h - bSizeTakenByBoxSizing); + + if (aStyle.IsCalcUnit()) { + aResult = std::max(nsRuleNode::ComputeComputedCalc(aStyle, h), 0); + return true; + } + + aResult = NSToCoordRound(aStyle.GetPercentValue() * h); + return true; +} + +// Get the amount of vertical space taken out of aFrame's content area due to +// its borders and paddings given the box-sizing value in aBoxSizing. We don't +// get aBoxSizing from the frame because some callers want to compute this for +// specific box-sizing values. aHorizontalAxis is true if our inline direction +// is horisontal and our block direction is vertical. aIgnorePadding is true if +// padding should be ignored. +static nscoord +GetBSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing, + nsIFrame* aFrame, + bool aHorizontalAxis, + bool aIgnorePadding) +{ + nscoord bSizeTakenByBoxSizing = 0; + if (aBoxSizing == StyleBoxSizing::Border) { + const nsStyleBorder* styleBorder = aFrame->StyleBorder(); + bSizeTakenByBoxSizing += + aHorizontalAxis ? styleBorder->GetComputedBorder().TopBottom() + : styleBorder->GetComputedBorder().LeftRight(); + if (!aIgnorePadding) { + const nsStyleSides& stylePadding = + aFrame->StylePadding()->mPadding; + const nsStyleCoord& paddingStart = + stylePadding.Get(aHorizontalAxis ? NS_SIDE_TOP : NS_SIDE_LEFT); + const nsStyleCoord& paddingEnd = + stylePadding.Get(aHorizontalAxis ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT); + nscoord pad; + // XXXbz Calling GetPercentBSize on padding values looks bogus, since + // percent padding is always a percentage of the inline-size of the + // containing block. We should perhaps just treat non-absolute paddings + // here as 0 instead, except that in some cases the width may in fact be + // known. See bug 1231059. + if (GetAbsoluteCoord(paddingStart, pad) || + GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) { + bSizeTakenByBoxSizing += pad; + } + if (GetAbsoluteCoord(paddingEnd, pad) || + GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) { + bSizeTakenByBoxSizing += pad; + } + } + } + return bSizeTakenByBoxSizing; +} + +// Handles only -moz-max-content and -moz-min-content, and +// -moz-fit-content for min-width and max-width, since the others +// (-moz-fit-content for width, and -moz-available) have no effect on +// intrinsic widths. +enum eWidthProperty { PROP_WIDTH, PROP_MAX_WIDTH, PROP_MIN_WIDTH }; +static bool +GetIntrinsicCoord(const nsStyleCoord& aStyle, + nsRenderingContext* aRenderingContext, + nsIFrame* aFrame, + eWidthProperty aProperty, + nscoord& aResult) +{ + NS_PRECONDITION(aProperty == PROP_WIDTH || aProperty == PROP_MAX_WIDTH || + aProperty == PROP_MIN_WIDTH, "unexpected property"); + if (aStyle.GetUnit() != eStyleUnit_Enumerated) + return false; + int32_t val = aStyle.GetIntValue(); + NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT || + val == NS_STYLE_WIDTH_MIN_CONTENT || + val == NS_STYLE_WIDTH_FIT_CONTENT || + val == NS_STYLE_WIDTH_AVAILABLE, + "unexpected enumerated value for width property"); + if (val == NS_STYLE_WIDTH_AVAILABLE) + return false; + if (val == NS_STYLE_WIDTH_FIT_CONTENT) { + if (aProperty == PROP_WIDTH) + return false; // handle like 'width: auto' + if (aProperty == PROP_MAX_WIDTH) + // constrain large 'width' values down to -moz-max-content + val = NS_STYLE_WIDTH_MAX_CONTENT; + else + // constrain small 'width' or 'max-width' values up to -moz-min-content + val = NS_STYLE_WIDTH_MIN_CONTENT; + } + + NS_ASSERTION(val == NS_STYLE_WIDTH_MAX_CONTENT || + val == NS_STYLE_WIDTH_MIN_CONTENT, + "should have reduced everything remaining to one of these"); + + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + if (val == NS_STYLE_WIDTH_MAX_CONTENT) + aResult = aFrame->GetPrefISize(aRenderingContext); + else + aResult = aFrame->GetMinISize(aRenderingContext); + return true; +} + +#undef DEBUG_INTRINSIC_WIDTH + +#ifdef DEBUG_INTRINSIC_WIDTH +static int32_t gNoiseIndent = 0; +#endif + +// Return true for form controls whose minimum intrinsic inline-size +// shrinks to 0 when they have a percentage inline-size (but not +// percentage max-inline-size). (Proper replaced elements, whose +// intrinsic minimium inline-size shrinks to 0 for both percentage +// inline-size and percentage max-inline-size, are handled elsewhere.) +inline static bool +FormControlShrinksForPercentISize(nsIFrame* aFrame) +{ + if (!aFrame->IsFrameOfType(nsIFrame::eReplaced)) { + // Quick test to reject most frames. + return false; + } + + nsIAtom* fType = aFrame->GetType(); + if (fType == nsGkAtoms::meterFrame || fType == nsGkAtoms::progressFrame) { + // progress and meter do have this shrinking behavior + // FIXME: Maybe these should be nsIFormControlFrame? + return true; + } + + if (!static_cast(do_QueryFrame(aFrame))) { + // Not a form control. This includes fieldsets, which do not + // shrink. + return false; + } + + if (fType == nsGkAtoms::gfxButtonControlFrame || + fType == nsGkAtoms::HTMLButtonControlFrame) { + // Buttons don't have this shrinking behavior. (Note that color + // inputs do, even though they inherit from button, so we can't use + // do_QueryFrame here.) + return false; + } + + return true; +} + +/** + * Add aOffsets which describes what to add on outside of the content box + * aContentSize (controlled by 'box-sizing') and apply min/max properties. + * We have to account for these properties after getting all the offsets + * (margin, border, padding) because percentages do not operate linearly. + * Doing this is ok because although percentages aren't handled linearly, + * they are handled monotonically. + * + * @param aContentSize the content size calculated so far + (@see IntrinsicForContainer) + * @param aContentMinSize ditto min content size + * @param aStyleSize a 'width' or 'height' property value + * @param aFixedMinSize if aStyleMinSize is a definite size then this points to + * the value, otherwise nullptr + * @param aStyleMinSize a 'min-width' or 'min-height' property value + * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to + * the value, otherwise nullptr + * @param aStyleMaxSize a 'max-width' or 'max-height' property value + * @param aFlags same as for IntrinsicForContainer + * @param aContainerWM the container's WM + */ +static nscoord +AddIntrinsicSizeOffset(nsRenderingContext* aRenderingContext, + nsIFrame* aFrame, + const nsIFrame::IntrinsicISizeOffsetData& aOffsets, + nsLayoutUtils::IntrinsicISizeType aType, + StyleBoxSizing aBoxSizing, + nscoord aContentSize, + nscoord aContentMinSize, + const nsStyleCoord& aStyleSize, + const nscoord* aFixedMinSize, + const nsStyleCoord& aStyleMinSize, + const nscoord* aFixedMaxSize, + const nsStyleCoord& aStyleMaxSize, + uint32_t aFlags, + PhysicalAxis aAxis) +{ + nscoord result = aContentSize; + nscoord min = aContentMinSize; + nscoord coordOutsideSize = 0; + float pctOutsideSize = 0; + float pctTotal = 0.0f; + + if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) { + coordOutsideSize += aOffsets.hPadding; + pctOutsideSize += aOffsets.hPctPadding; + } + + coordOutsideSize += aOffsets.hBorder; + + if (aBoxSizing == StyleBoxSizing::Border) { + min += coordOutsideSize; + result = NSCoordSaturatingAdd(result, coordOutsideSize); + pctTotal += pctOutsideSize; + + coordOutsideSize = 0; + pctOutsideSize = 0.0f; + } + + coordOutsideSize += aOffsets.hMargin; + pctOutsideSize += aOffsets.hPctMargin; + + min += coordOutsideSize; + result = NSCoordSaturatingAdd(result, coordOutsideSize); + pctTotal += pctOutsideSize; + + const bool shouldAddPercent = aType == nsLayoutUtils::PREF_ISIZE || + (aFlags & nsLayoutUtils::ADD_PERCENTS); + nscoord size; + if (aType == nsLayoutUtils::MIN_ISIZE && + (((aStyleSize.HasPercent() || aStyleMaxSize.HasPercent()) && + aFrame->IsFrameOfType(nsIFrame::eReplacedSizing)) || + (aStyleSize.HasPercent() && + FormControlShrinksForPercentISize(aFrame)))) { + // A percentage width or max-width on replaced elements means they + // can shrink to 0. + // This is also true for percentage widths (but not max-widths) on + // text inputs. + // Note that if this is max-width, this overrides the fixed-width + // rule in the next condition. + result = 0; // let |min| handle padding/border/margin + } else if (GetAbsoluteCoord(aStyleSize, size) || + GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame, + PROP_WIDTH, size)) { + result = size + coordOutsideSize; + if (shouldAddPercent) { + result = nsLayoutUtils::AddPercents(result, pctOutsideSize); + } + } else { + // NOTE: We could really do a lot better for percents and for some + // cases of calc() containing percent (certainly including any where + // the coefficient on the percent is positive and there are no max() + // expressions). However, doing better for percents wouldn't be + // backwards compatible. + if (shouldAddPercent) { + result = nsLayoutUtils::AddPercents(result, pctTotal); + } + } + + nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0; + if (aFixedMaxSize || + GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame, + PROP_MAX_WIDTH, maxSize)) { + maxSize += coordOutsideSize; + if (shouldAddPercent) { + maxSize = nsLayoutUtils::AddPercents(maxSize, pctOutsideSize); + } + if (result > maxSize) { + result = maxSize; + } + } + + nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0; + if (aFixedMinSize || + GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame, + PROP_MIN_WIDTH, minSize)) { + minSize += coordOutsideSize; + if (shouldAddPercent) { + minSize = nsLayoutUtils::AddPercents(minSize, pctOutsideSize); + } + if (result < minSize) { + result = minSize; + } + } + + if (shouldAddPercent) { + min = nsLayoutUtils::AddPercents(min, pctTotal); + } + if (result < min) { + result = min; + } + + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + if (aFrame->IsThemed(disp)) { + LayoutDeviceIntSize devSize; + bool canOverride = true; + nsPresContext* pc = aFrame->PresContext(); + pc->GetTheme()->GetMinimumWidgetSize(pc, aFrame, disp->mAppearance, + &devSize, &canOverride); + nscoord themeSize = + pc->DevPixelsToAppUnits(aAxis == eAxisVertical ? devSize.height + : devSize.width); + // GetMinimumWidgetSize() returns a border-box width. + themeSize += aOffsets.hMargin; + if (shouldAddPercent) { + themeSize = nsLayoutUtils::AddPercents(themeSize, aOffsets.hPctMargin); + } + if (themeSize > result || !canOverride) { + result = themeSize; + } + } + return result; +} + +static void +AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit) +{ + for (nsIFrame* f = aFrame; f; f = f->GetParent()) { + if (f->HasAnyStateBits(aBit)) { + break; + } + f->AddStateBits(aBit); + } +} + +/* static */ nscoord +nsLayoutUtils::IntrinsicForAxis(PhysicalAxis aAxis, + nsRenderingContext* aRenderingContext, + nsIFrame* aFrame, + IntrinsicISizeType aType, + uint32_t aFlags, + nscoord aMarginBoxMinSizeClamp) +{ + NS_PRECONDITION(aFrame, "null frame"); + NS_PRECONDITION(aFrame->GetParent(), + "IntrinsicForAxis called on frame not in tree"); + NS_PRECONDITION(aType == MIN_ISIZE || aType == PREF_ISIZE, "bad type"); + + const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal); +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s %s intrinsic size for container:\n", + aType == MIN_ISIZE ? "min" : "pref", + horizontalAxis ? "horizontal" : "vertical"); +#endif + + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + // We want the size this frame will contribute to the parent's inline-size, + // so we work in the parent's writing mode; but if aFrame is orthogonal to + // its parent, we'll need to look at its BSize instead of min/pref-ISize. + const nsStylePosition* stylePos = aFrame->StylePosition(); + StyleBoxSizing boxSizing = stylePos->mBoxSizing; + + const nsStyleCoord& styleMinISize = + horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight; + const nsStyleCoord& styleISize = + (aFlags & MIN_INTRINSIC_ISIZE) ? styleMinISize : + (horizontalAxis ? stylePos->mWidth : stylePos->mHeight); + MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || + styleISize.GetUnit() == eStyleUnit_Auto || + styleISize.GetUnit() == eStyleUnit_Enumerated, + "should only use MIN_INTRINSIC_ISIZE for intrinsic values"); + const nsStyleCoord& styleMaxISize = + horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight; + + // We build up two values starting with the content box, and then + // adding padding, border and margin. The result is normally + // |result|. Then, when we handle 'width', 'min-width', and + // 'max-width', we use the results we've been building in |min| as a + // minimum, overriding 'min-width'. This ensures two things: + // * that we don't let a value of 'box-sizing' specifying a width + // smaller than the padding/border inside the box-sizing box give + // a content width less than zero + // * that we prevent tables from becoming smaller than their + // intrinsic minimum width + nscoord result = 0, min = 0; + + nscoord maxISize; + bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize); + nscoord minISize; + + // Treat "min-width: auto" as 0. + bool haveFixedMinISize; + if (eStyleUnit_Auto == styleMinISize.GetUnit()) { + // NOTE: Technically, "auto" is supposed to behave like "min-content" on + // flex items. However, we don't need to worry about that here, because + // flex items' min-sizes are intentionally ignored until the flex + // container explicitly considers them during space distribution. + minISize = 0; + haveFixedMinISize = true; + } else { + haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize); + } + + PhysicalAxis ourInlineAxis = + aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline); + // If we have a specified width (or a specified 'min-width' greater + // than the specified 'max-width', which works out to the same thing), + // don't even bother getting the frame's intrinsic width, because in + // this case GetAbsoluteCoord(styleISize, w) will always succeed, so + // we'll never need the intrinsic dimensions. + if (styleISize.GetUnit() == eStyleUnit_Enumerated && + (styleISize.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || + styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT)) { + // -moz-fit-content and -moz-available enumerated widths compute intrinsic + // widths just like auto. + // For -moz-max-content and -moz-min-content, we handle them like + // specified widths, but ignore box-sizing. + boxSizing = StyleBoxSizing::Content; + if (aMarginBoxMinSizeClamp != NS_MAXSIZE && + styleISize.GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT) { + // We need |result| to be the 'min-content size' for the clamping below. + result = aFrame->GetMinISize(aRenderingContext); + } + } else if (!styleISize.ConvertsToLength() && + !(haveFixedMinISize && haveFixedMaxISize && maxISize <= minISize)) { +#ifdef DEBUG_INTRINSIC_WIDTH + ++gNoiseIndent; +#endif + if (aType != MIN_ISIZE) { + // At this point, |styleISize| is auto/-moz-fit-content/-moz-available or + // has a percentage. The intrinisic size for those under a max-content + // constraint is the max-content contribution which we shouldn't clamp. + aMarginBoxMinSizeClamp = NS_MAXSIZE; + } + if (MOZ_UNLIKELY(aAxis != ourInlineAxis)) { + IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize(); + const nsStyleCoord intrinsicBCoord = + horizontalAxis ? intrinsicSize.width : intrinsicSize.height; + if (intrinsicBCoord.GetUnit() == eStyleUnit_Coord) { + result = intrinsicBCoord.GetCoordValue(); + } else { + // We don't have an intrinsic bsize and we need aFrame's block-dir size. + if (aFlags & BAIL_IF_REFLOW_NEEDED) { + return NS_INTRINSIC_WIDTH_UNKNOWN; + } + // XXX Unfortunately, we probably don't know this yet, so this is wrong... + // but it's not clear what we should do. If aFrame's inline size hasn't + // been determined yet, we can't necessarily figure out its block size + // either. For now, authors who put orthogonal elements into things like + // buttons or table cells may have to explicitly provide sizes rather + // than expecting intrinsic sizing to work "perfectly" in underspecified + // cases. + result = aFrame->BSize(); + } + } else { + result = aType == MIN_ISIZE + ? aFrame->GetMinISize(aRenderingContext) + : aFrame->GetPrefISize(aRenderingContext); + } +#ifdef DEBUG_INTRINSIC_WIDTH + --gNoiseIndent; + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s %s intrinsic size from frame is %d.\n", + aType == MIN_ISIZE ? "min" : "pref", + horizontalAxis ? "horizontal" : "vertical", + result); +#endif + + // Handle elements with an intrinsic ratio (or size) and a specified + // height, min-height, or max-height. + // NOTE: We treat "min-height:auto" as "0" for the purpose of this code, + // since that's what it means in all cases except for on flex items -- and + // even there, we're supposed to ignore it (i.e. treat it as 0) until the + // flex container explicitly considers it. + const nsStyleCoord& styleBSize = + horizontalAxis ? stylePos->mHeight : stylePos->mWidth; + const nsStyleCoord& styleMinBSize = + horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth; + const nsStyleCoord& styleMaxBSize = + horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth; + + if (styleBSize.GetUnit() != eStyleUnit_Auto || + !(styleMinBSize.GetUnit() == eStyleUnit_Auto || + (styleMinBSize.GetUnit() == eStyleUnit_Coord && + styleMinBSize.GetCoordValue() == 0)) || + styleMaxBSize.GetUnit() != eStyleUnit_None) { + + nsSize ratio(aFrame->GetIntrinsicRatio()); + nscoord ratioISize = (horizontalAxis ? ratio.width : ratio.height); + nscoord ratioBSize = (horizontalAxis ? ratio.height : ratio.width); + if (ratioBSize != 0) { + AddStateBitToAncestors(aFrame, + NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE); + + nscoord bSizeTakenByBoxSizing = + GetBSizeTakenByBoxSizing(boxSizing, aFrame, horizontalAxis, + aFlags & IGNORE_PADDING); + + // NOTE: This is only the minContentSize if we've been passed MIN_INTRINSIC_ISIZE + // (which is fine, because this should only be used inside a check for that flag). + nscoord minContentSize = result; + nscoord h; + if (GetAbsoluteCoord(styleBSize, h) || + GetPercentBSize(styleBSize, aFrame, horizontalAxis, h)) { + h = std::max(0, h - bSizeTakenByBoxSizing); + result = NSCoordMulDiv(h, ratioISize, ratioBSize); + } + + if (GetAbsoluteCoord(styleMaxBSize, h) || + GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h)) { + h = std::max(0, h - bSizeTakenByBoxSizing); + nscoord maxISize = NSCoordMulDiv(h, ratioISize, ratioBSize); + if (maxISize < result) { + result = maxISize; + } + if (maxISize < minContentSize) { + minContentSize = maxISize; + } + } + + if (GetAbsoluteCoord(styleMinBSize, h) || + GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h)) { + h = std::max(0, h - bSizeTakenByBoxSizing); + nscoord minISize = NSCoordMulDiv(h, ratioISize, ratioBSize); + if (minISize > result) { + result = minISize; + } + if (minISize > minContentSize) { + minContentSize = minISize; + } + } + if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE)) { + // This is the 'min-width/height:auto' "transferred size" piece of: + // https://www.w3.org/TR/css-flexbox-1/#min-width-automatic-minimum-size + // https://drafts.csswg.org/css-grid/#min-size-auto + result = std::min(result, minContentSize); + } + } + } + } + + if (aFrame->GetType() == nsGkAtoms::tableFrame) { + // Tables can't shrink smaller than their intrinsic minimum width, + // no matter what. + min = aFrame->GetMinISize(aRenderingContext); + } + + nsIFrame::IntrinsicISizeOffsetData offsets = + MOZ_LIKELY(aAxis == ourInlineAxis) ? aFrame->IntrinsicISizeOffsets() + : aFrame->IntrinsicBSizeOffsets(); + nscoord contentBoxSize = result; + result = AddIntrinsicSizeOffset(aRenderingContext, aFrame, offsets, aType, + boxSizing, result, min, styleISize, + haveFixedMinISize ? &minISize : nullptr, + styleMinISize, + haveFixedMaxISize ? &maxISize : nullptr, + styleMaxISize, + aFlags, aAxis); + nscoord overflow = result - aMarginBoxMinSizeClamp; + if (MOZ_UNLIKELY(overflow > 0)) { + nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow); + result -= contentBoxSize - newContentBoxSize; + } + +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s %s intrinsic size for container is %d twips.\n", + aType == MIN_ISIZE ? "min" : "pref", + horizontalAxis ? "horizontal" : "vertical", + result); +#endif + + return result; +} + +/* static */ nscoord +nsLayoutUtils::IntrinsicForContainer(nsRenderingContext* aRenderingContext, + nsIFrame* aFrame, + IntrinsicISizeType aType, + uint32_t aFlags) +{ + MOZ_ASSERT(aFrame && aFrame->GetParent()); + // We want the size aFrame will contribute to its parent's inline-size. + PhysicalAxis axis = + aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline); + return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, aFlags); +} + +/* static */ nscoord +nsLayoutUtils::MinSizeContributionForAxis(PhysicalAxis aAxis, + nsRenderingContext* aRC, + nsIFrame* aFrame, + IntrinsicISizeType aType, + uint32_t aFlags) +{ + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->IsFlexOrGridItem(), + "only grid/flex items have this behavior currently"); + +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s min-isize for %s WM:\n", + aType == MIN_ISIZE ? "min" : "pref", + aWM.IsVertical() ? "vertical" : "horizontal"); +#endif + + // Note: this method is only meant for grid/flex items which always + // include percentages in their intrinsic size. + aFlags |= nsLayoutUtils::ADD_PERCENTS; + const nsStylePosition* const stylePos = aFrame->StylePosition(); + const nsStyleCoord* style = aAxis == eAxisHorizontal ? &stylePos->mMinWidth + : &stylePos->mMinHeight; + nscoord minSize; + nscoord* fixedMinSize = nullptr; + auto minSizeUnit = style->GetUnit(); + if (minSizeUnit == eStyleUnit_Auto) { + if (aFrame->StyleDisplay()->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) { + style = aAxis == eAxisHorizontal ? &stylePos->mWidth + : &stylePos->mHeight; + if (GetAbsoluteCoord(*style, minSize)) { + // We have a definite width/height. This is the "specified size" in: + // https://drafts.csswg.org/css-grid/#min-size-auto + fixedMinSize = &minSize; + } + // fall through - the caller will have to deal with "transferred size" + } else { + // min-[width|height]:auto with overflow != visible computes to zero. + minSize = 0; + fixedMinSize = &minSize; + } + } else if (GetAbsoluteCoord(*style, minSize)) { + fixedMinSize = &minSize; + } else if (minSizeUnit != eStyleUnit_Enumerated) { + MOZ_ASSERT(style->HasPercent()); + minSize = 0; + fixedMinSize = &minSize; + } + + if (!fixedMinSize) { + // Let the caller deal with the "content size" cases. +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s min-isize is indefinite.\n", + aType == MIN_ISIZE ? "min" : "pref"); +#endif + return NS_UNCONSTRAINEDSIZE; + } + + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + PhysicalAxis ourInlineAxis = + aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline); + nsIFrame::IntrinsicISizeOffsetData offsets = + ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets() + : aFrame->IntrinsicBSizeOffsets(); + nscoord result = 0; + nscoord min = 0; + + const nsStyleCoord& maxISize = + aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight; + result = AddIntrinsicSizeOffset(aRC, aFrame, offsets, aType, + stylePos->mBoxSizing, + result, min, *style, fixedMinSize, + *style, nullptr, maxISize, aFlags, aAxis); + +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s min-isize is %d twips.\n", + aType == MIN_ISIZE ? "min" : "pref", result); +#endif + + return result; +} + +/* static */ nscoord +nsLayoutUtils::ComputeCBDependentValue(nscoord aPercentBasis, + const nsStyleCoord& aCoord) +{ + NS_WARNING_ASSERTION( + aPercentBasis != NS_UNCONSTRAINEDSIZE, + "have unconstrained width or height; this should only result from very " + "large sizes, not attempts at intrinsic size calculation"); + + if (aCoord.IsCoordPercentCalcUnit()) { + return nsRuleNode::ComputeCoordPercentCalc(aCoord, aPercentBasis); + } + NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None || + aCoord.GetUnit() == eStyleUnit_Auto, + "unexpected width value"); + return 0; +} + +/* static */ nscoord +nsLayoutUtils::ComputeBSizeDependentValue( + nscoord aContainingBlockBSize, + const nsStyleCoord& aCoord) +{ + // XXXldb Some callers explicitly check aContainingBlockBSize + // against NS_AUTOHEIGHT *and* unit against eStyleUnit_Percent or + // calc()s containing percents before calling this function. + // However, it would be much more likely to catch problems without + // the unit conditions. + // XXXldb Many callers pass a non-'auto' containing block height when + // according to CSS2.1 they should be passing 'auto'. + NS_PRECONDITION(NS_AUTOHEIGHT != aContainingBlockBSize || + !aCoord.HasPercent(), + "unexpected containing block block-size"); + + if (aCoord.IsCoordPercentCalcUnit()) { + return nsRuleNode::ComputeCoordPercentCalc(aCoord, aContainingBlockBSize); + } + + NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None || + aCoord.GetUnit() == eStyleUnit_Auto, + "unexpected block-size value"); + return 0; +} + +/* static */ void +nsLayoutUtils::MarkDescendantsDirty(nsIFrame *aSubtreeRoot) +{ + AutoTArray subtrees; + subtrees.AppendElement(aSubtreeRoot); + + // dirty descendants, iterating over subtrees that may include + // additional subtrees associated with placeholders + do { + nsIFrame *subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1); + subtrees.RemoveElementAt(subtrees.Length() - 1); + + // Mark all descendants dirty (using an nsTArray stack rather than + // recursion). + // Note that ReflowInput::InitResizeFlags has some similar + // code; see comments there for how and why it differs. + AutoTArray stack; + stack.AppendElement(subtreeRoot); + + do { + nsIFrame *f = stack.ElementAt(stack.Length() - 1); + stack.RemoveElementAt(stack.Length() - 1); + + f->MarkIntrinsicISizesDirty(); + + if (f->GetType() == nsGkAtoms::placeholderFrame) { + nsIFrame *oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); + if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { + // We have another distinct subtree we need to mark. + subtrees.AppendElement(oof); + } + } + + nsIFrame::ChildListIterator lists(f); + for (; !lists.IsDone(); lists.Next()) { + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* kid = childFrames.get(); + stack.AppendElement(kid); + } + } + } while (stack.Length() != 0); + } while (subtrees.Length() != 0); +} + +/* static */ +void +nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame* aFrame) +{ + AutoTArray stack; + stack.AppendElement(aFrame); + + do { + nsIFrame* f = stack.ElementAt(stack.Length() - 1); + stack.RemoveElementAt(stack.Length() - 1); + + if (!f->HasAnyStateBits( + NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) { + continue; + } + f->MarkIntrinsicISizesDirty(); + + for (nsIFrame::ChildListIterator lists(f); !lists.IsDone(); lists.Next()) { + for (nsIFrame* kid : lists.CurrentList()) { + stack.AppendElement(kid); + } + } + } while (stack.Length() != 0); +} + +nsSize +nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(nscoord minWidth, nscoord minHeight, + nscoord maxWidth, nscoord maxHeight, + nscoord tentWidth, nscoord tentHeight) +{ + // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7: + + if (minWidth > maxWidth) + maxWidth = minWidth; + if (minHeight > maxHeight) + maxHeight = minHeight; + + nscoord heightAtMaxWidth, heightAtMinWidth, + widthAtMaxHeight, widthAtMinHeight; + + if (tentWidth > 0) { + heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth); + if (heightAtMaxWidth < minHeight) + heightAtMaxWidth = minHeight; + heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth); + if (heightAtMinWidth > maxHeight) + heightAtMinWidth = maxHeight; + } else { + heightAtMaxWidth = heightAtMinWidth = NS_CSS_MINMAX(tentHeight, minHeight, maxHeight); + } + + if (tentHeight > 0) { + widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight); + if (widthAtMaxHeight < minWidth) + widthAtMaxHeight = minWidth; + widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight); + if (widthAtMinHeight > maxWidth) + widthAtMinHeight = maxWidth; + } else { + widthAtMaxHeight = widthAtMinHeight = NS_CSS_MINMAX(tentWidth, minWidth, maxWidth); + } + + // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths : + + nscoord width, height; + + if (tentWidth > maxWidth) { + if (tentHeight > maxHeight) { + if (int64_t(maxWidth) * int64_t(tentHeight) <= + int64_t(maxHeight) * int64_t(tentWidth)) { + width = maxWidth; + height = heightAtMaxWidth; + } else { + width = widthAtMaxHeight; + height = maxHeight; + } + } else { + // This also covers "(w > max-width) and (h < min-height)" since in + // that case (max-width/w < 1), and with (h < min-height): + // max(max-width * h/w, min-height) == min-height + width = maxWidth; + height = heightAtMaxWidth; + } + } else if (tentWidth < minWidth) { + if (tentHeight < minHeight) { + if (int64_t(minWidth) * int64_t(tentHeight) <= + int64_t(minHeight) * int64_t(tentWidth)) { + width = widthAtMinHeight; + height = minHeight; + } else { + width = minWidth; + height = heightAtMinWidth; + } + } else { + // This also covers "(w < min-width) and (h > max-height)" since in + // that case (min-width/w > 1), and with (h > max-height): + // min(min-width * h/w, max-height) == max-height + width = minWidth; + height = heightAtMinWidth; + } + } else { + if (tentHeight > maxHeight) { + width = widthAtMaxHeight; + height = maxHeight; + } else if (tentHeight < minHeight) { + width = widthAtMinHeight; + height = minHeight; + } else { + width = tentWidth; + height = tentHeight; + } + } + + return nsSize(width, height); +} + +/* static */ nscoord +nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame, + nsRenderingContext* aRenderingContext) +{ + NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), + "should not be container for font size inflation"); + + nsIFrame::InlineMinISizeData data; + DISPLAY_MIN_WIDTH(aFrame, data.mPrevLines); + aFrame->AddInlineMinISize(aRenderingContext, &data); + data.ForceBreak(); + return data.mPrevLines; +} + +/* static */ nscoord +nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame, + nsRenderingContext* aRenderingContext) +{ + NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), + "should not be container for font size inflation"); + + nsIFrame::InlinePrefISizeData data; + DISPLAY_PREF_WIDTH(aFrame, data.mPrevLines); + aFrame->AddInlinePrefISize(aRenderingContext, &data); + data.ForceBreak(); + return data.mPrevLines; +} + +static nscolor +DarkenColor(nscolor aColor) +{ + uint16_t hue, sat, value; + uint8_t alpha; + + // convert the RBG to HSV so we can get the lightness (which is the v) + NS_RGB2HSV(aColor, hue, sat, value, alpha); + + // The goal here is to send white to black while letting colored + // stuff stay colored... So we adopt the following approach. + // Something with sat = 0 should end up with value = 0. Something + // with a high sat can end up with a high value and it's ok.... At + // the same time, we don't want to make things lighter. Do + // something simple, since it seems to work. + if (value > sat) { + value = sat; + // convert this color back into the RGB color space. + NS_HSV2RGB(aColor, hue, sat, value, alpha); + } + return aColor; +} + +// Check whether we should darken text/decoration colors. We need to do this if +// background images and colors are being suppressed, because that means +// light text will not be visible against the (presumed light-colored) background. +static bool +ShouldDarkenColors(nsPresContext* aPresContext) +{ + return !aPresContext->GetBackgroundColorDraw() && + !aPresContext->GetBackgroundImageDraw(); +} + +nscolor +nsLayoutUtils::GetColor(nsIFrame* aFrame, nsCSSPropertyID aProperty) +{ + nscolor color = aFrame->GetVisitedDependentColor(aProperty); + if (ShouldDarkenColors(aFrame->PresContext())) { + color = DarkenColor(color); + } + return color; +} + +gfxFloat +nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame, gfxContext* aContext, + nscoord aY, nscoord aAscent) +{ + gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxFloat baseline = gfxFloat(aY) + aAscent; + gfxRect putativeRect(0, baseline/appUnitsPerDevUnit, 1, 1); + if (!aContext->UserToDevicePixelSnapped(putativeRect, true)) + return baseline; + return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit; +} + +gfxFloat +nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame, gfxContext* aContext, + nscoord aX, nscoord aAscent) +{ + gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxFloat baseline = gfxFloat(aX) + aAscent; + gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1); + if (!aContext->UserToDevicePixelSnapped(putativeRect, true)) { + return baseline; + } + return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit; +} + +// Hard limit substring lengths to 8000 characters ... this lets us statically +// size the cluster buffer array in FindSafeLength +#define MAX_GFX_TEXT_BUF_SIZE 8000 + +static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength, + uint32_t aMaxChunkLength) +{ + if (aLength <= aMaxChunkLength) + return aLength; + + int32_t len = aMaxChunkLength; + + // Ensure that we don't break inside a surrogate pair + while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) { + len--; + } + if (len == 0) { + // We don't want our caller to go into an infinite loop, so don't + // return zero. It's hard to imagine how we could actually get here + // unless there are languages that allow clusters of arbitrary size. + // If there are and someone feeds us a 500+ character cluster, too + // bad. + return aMaxChunkLength; + } + return len; +} + +static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics) +{ + return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE); +} + +nscoord +nsLayoutUtils::AppUnitWidthOfString(const char16_t *aString, + uint32_t aLength, + nsFontMetrics& aFontMetrics, + DrawTarget* aDrawTarget) +{ + uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); + nscoord width = 0; + while (aLength > 0) { + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); + width += aFontMetrics.GetWidth(aString, len, aDrawTarget); + aLength -= len; + aString += len; + } + return width; +} + +nscoord +nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString, + uint32_t aLength, + const nsIFrame* aFrame, + nsFontMetrics& aFontMetrics, + nsRenderingContext& aContext) +{ + nsPresContext* presContext = aFrame->PresContext(); + if (presContext->BidiEnabled()) { + nsBidiLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aFrame->StyleContext()); + return nsBidiPresUtils::MeasureTextWidth(aString, aLength, level, + presContext, aContext, + aFontMetrics); + } + aFontMetrics.SetTextRunRTL(false); + aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical()); + aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation); + return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics, + aContext.GetDrawTarget()); +} + +bool +nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString, + nsFontMetrics& aFontMetrics, + DrawTarget* aDrawTarget, + nscoord aWidth) +{ + const char16_t *string = aString.get(); + uint32_t length = aString.Length(); + uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); + nscoord width = 0; + while (length > 0) { + int32_t len = FindSafeLength(string, length, maxChunkLength); + width += aFontMetrics.GetWidth(string, len, aDrawTarget); + if (width > aWidth) { + return true; + } + length -= len; + string += len; + } + return false; +} + +nsBoundingMetrics +nsLayoutUtils::AppUnitBoundsOfString(const char16_t* aString, + uint32_t aLength, + nsFontMetrics& aFontMetrics, + DrawTarget* aDrawTarget) +{ + uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); + // Assign directly in the first iteration. This ensures that + // negative ascent/descent can be returned and the left bearing + // is properly initialized. + nsBoundingMetrics totalMetrics = + aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget); + aLength -= len; + aString += len; + + while (aLength > 0) { + len = FindSafeLength(aString, aLength, maxChunkLength); + nsBoundingMetrics metrics = + aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget); + totalMetrics += metrics; + aLength -= len; + aString += len; + } + return totalMetrics; +} + +void +nsLayoutUtils::DrawString(const nsIFrame* aFrame, + nsFontMetrics& aFontMetrics, + nsRenderingContext* aContext, + const char16_t* aString, + int32_t aLength, + nsPoint aPoint, + nsStyleContext* aStyleContext) +{ + nsresult rv = NS_ERROR_FAILURE; + + // If caller didn't pass a style context, use the frame's. + if (!aStyleContext) { + aStyleContext = aFrame->StyleContext(); + } + aFontMetrics.SetVertical(WritingMode(aStyleContext).IsVertical()); + aFontMetrics.SetTextOrientation( + aStyleContext->StyleVisibility()->mTextOrientation); + + nsPresContext* presContext = aFrame->PresContext(); + if (presContext->BidiEnabled()) { + nsBidiLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aStyleContext); + rv = nsBidiPresUtils::RenderText(aString, aLength, level, + presContext, *aContext, + aContext->GetDrawTarget(), aFontMetrics, + aPoint.x, aPoint.y); + } + if (NS_FAILED(rv)) + { + aFontMetrics.SetTextRunRTL(false); + DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext); + } +} + +void +nsLayoutUtils::DrawUniDirString(const char16_t* aString, + uint32_t aLength, + nsPoint aPoint, + nsFontMetrics& aFontMetrics, + nsRenderingContext& aContext) +{ + nscoord x = aPoint.x; + nscoord y = aPoint.y; + + uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics); + if (aLength <= maxChunkLength) { + aFontMetrics.DrawString(aString, aLength, x, y, &aContext, + aContext.GetDrawTarget()); + return; + } + + bool isRTL = aFontMetrics.GetTextRunRTL(); + + // If we're drawing right to left, we must start at the end. + if (isRTL) { + x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics, + aContext.GetDrawTarget()); + } + + while (aLength > 0) { + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); + nscoord width = aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget()); + if (isRTL) { + x -= width; + } + aFontMetrics.DrawString(aString, len, x, y, &aContext, + aContext.GetDrawTarget()); + if (!isRTL) { + x += width; + } + aLength -= len; + aString += len; + } +} + +/* static */ void +nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame, + nsRenderingContext* aContext, + const nsRect& aTextRect, + const nsRect& aDirtyRect, + const nscolor& aForegroundColor, + TextShadowCallback aCallback, + void* aCallbackData) +{ + const nsStyleText* textStyle = aFrame->StyleText(); + if (!textStyle->HasTextShadow()) + return; + + // Text shadow happens with the last value being painted at the back, + // ie. it is painted first. + gfxContext* aDestCtx = aContext->ThebesContext(); + for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) { + nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1); + nsPoint shadowOffset(shadowDetails->mXOffset, + shadowDetails->mYOffset); + nscoord blurRadius = std::max(shadowDetails->mRadius, 0); + + nsRect shadowRect(aTextRect); + shadowRect.MoveBy(shadowOffset); + + nsPresContext* presCtx = aFrame->PresContext(); + nsContextBoxBlur contextBoxBlur; + gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius, + presCtx->AppUnitsPerDevPixel(), + aDestCtx, aDirtyRect, nullptr); + if (!shadowContext) + continue; + + nscolor shadowColor; + if (shadowDetails->mHasColor) + shadowColor = shadowDetails->mColor; + else + shadowColor = aForegroundColor; + + // Conjure an nsRenderingContext from a gfxContext for drawing the text + // to blur. + nsRenderingContext renderingContext(shadowContext); + + aDestCtx->Save(); + aDestCtx->NewPath(); + aDestCtx->SetColor(Color::FromABGR(shadowColor)); + + // The callback will draw whatever we want to blur as a shadow. + aCallback(&renderingContext, shadowOffset, shadowColor, aCallbackData); + + contextBoxBlur.DoPaint(); + aDestCtx->Restore(); + } +} + +/* static */ nscoord +nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics, + nscoord aLineHeight, + bool aIsInverted) +{ + nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent() + : aFontMetrics->MaxAscent(); + nscoord fontHeight = aFontMetrics->MaxHeight(); + + nscoord leading = aLineHeight - fontHeight; + return fontAscent + leading/2; +} + + +/* static */ bool +nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode, + const nsIFrame* aFrame, nscoord* aResult) +{ + LinePosition position; + if (!GetFirstLinePosition(aWritingMode, aFrame, &position)) + return false; + *aResult = position.mBaseline; + return true; +} + +/* static */ bool +nsLayoutUtils::GetFirstLinePosition(WritingMode aWM, + const nsIFrame* aFrame, + LinePosition* aResult) +{ + const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast(aFrame)); + if (!block) { + // For the first-line baseline we also have to check for a table, and if + // so, use the baseline of its first row. + nsIAtom* fType = aFrame->GetType(); + if (fType == nsGkAtoms::tableWrapperFrame || + fType == nsGkAtoms::flexContainerFrame || + fType == nsGkAtoms::gridContainerFrame) { + if ((fType == nsGkAtoms::gridContainerFrame && + aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) || + (fType == nsGkAtoms::flexContainerFrame && + aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) || + (fType == nsGkAtoms::tableWrapperFrame && + static_cast(aFrame)->GetRowCount() == 0)) { + // empty grid/flex/table container + aResult->mBStart = 0; + aResult->mBaseline = aFrame->SynthesizeBaselineBOffsetFromBorderBox(aWM, + BaselineSharingGroup::eFirst); + aResult->mBEnd = aFrame->BSize(aWM); + return true; + } + aResult->mBStart = 0; + aResult->mBaseline = aFrame->GetLogicalBaseline(aWM); + // This is what we want for the list bullet caller; not sure if + // other future callers will want the same. + aResult->mBEnd = aFrame->BSize(aWM); + return true; + } + + // For first-line baselines, we have to consider scroll frames. + if (fType == nsGkAtoms::scrollFrame) { + nsIScrollableFrame *sFrame = do_QueryFrame(const_cast(aFrame)); + if (!sFrame) { + NS_NOTREACHED("not scroll frame"); + } + LinePosition kidPosition; + if (GetFirstLinePosition(aWM, + sFrame->GetScrolledFrame(), &kidPosition)) { + // Consider only the border and padding that contributes to the + // kid's position, not the scrolling, so we get the initial + // position. + *aResult = kidPosition + + aFrame->GetLogicalUsedBorderAndPadding(aWM).BStart(aWM); + return true; + } + return false; + } + + if (fType == nsGkAtoms::fieldSetFrame) { + LinePosition kidPosition; + nsIFrame* kid = aFrame->PrincipalChildList().FirstChild(); + // kid might be a legend frame here, but that's ok. + if (GetFirstLinePosition(aWM, kid, &kidPosition)) { + *aResult = kidPosition + + kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM); + return true; + } + return false; + } + + // No baseline. + return false; + } + + for (nsBlockFrame::ConstLineIterator line = block->LinesBegin(), + line_end = block->LinesEnd(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame *kid = line->mFirstChild; + LinePosition kidPosition; + if (GetFirstLinePosition(aWM, kid, &kidPosition)) { + //XXX Not sure if this is the correct value to use for container + // width here. It will only be used in vertical-rl layout, + // which we don't have full support and testing for yet. + const nsSize& containerSize = line->mContainerSize; + *aResult = kidPosition + + kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM); + return true; + } + } else { + // XXX Is this the right test? We have some bogus empty lines + // floating around, but IsEmpty is perhaps too weak. + if (line->BSize() != 0 || !line->IsEmpty()) { + nscoord bStart = line->BStart(); + aResult->mBStart = bStart; + aResult->mBaseline = bStart + line->GetLogicalAscent(); + aResult->mBEnd = bStart + line->BSize(); + return true; + } + } + } + return false; +} + +/* static */ bool +nsLayoutUtils::GetLastLineBaseline(WritingMode aWM, + const nsIFrame* aFrame, nscoord* aResult) +{ + const nsBlockFrame* block = nsLayoutUtils::GetAsBlock(const_cast(aFrame)); + if (!block) + // No baseline. (We intentionally don't descend into scroll frames.) + return false; + + for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(), + line_end = block->LinesREnd(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame *kid = line->mFirstChild; + nscoord kidBaseline; + const nsSize& containerSize = line->mContainerSize; + if (GetLastLineBaseline(aWM, kid, &kidBaseline)) { + // Ignore relative positioning for baseline calculations + *aResult = kidBaseline + + kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM); + return true; + } else if (kid->GetType() == nsGkAtoms::scrollFrame) { + // Use the bottom of the scroll frame. + // XXX CSS2.1 really doesn't say what to do here. + *aResult = kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM) + + kid->BSize(aWM); + return true; + } + } else { + // XXX Is this the right test? We have some bogus empty lines + // floating around, but IsEmpty is perhaps too weak. + if (line->BSize() != 0 || !line->IsEmpty()) { + *aResult = line->BStart() + line->GetLogicalAscent(); + return true; + } + } + } + return false; +} + +static nscoord +CalculateBlockContentBEnd(WritingMode aWM, nsBlockFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null ptr"); + + nscoord contentBEnd = 0; + + for (nsBlockFrame::LineIterator line = aFrame->LinesBegin(), + line_end = aFrame->LinesEnd(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame* child = line->mFirstChild; + const nsSize& containerSize = line->mContainerSize; + nscoord offset = + child->GetLogicalNormalPosition(aWM, containerSize).B(aWM); + contentBEnd = + std::max(contentBEnd, + nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset); + } + else { + contentBEnd = std::max(contentBEnd, line->BEnd()); + } + } + return contentBEnd; +} + +/* static */ nscoord +nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null ptr"); + + nscoord contentBEnd = aFrame->BSize(aWM); + + // We want scrollable overflow rather than visual because this + // calculation is intended to affect layout. + LogicalSize overflowSize(aWM, aFrame->GetScrollableOverflowRect().Size()); + if (overflowSize.BSize(aWM) > contentBEnd) { + nsIFrame::ChildListIDs skip(nsIFrame::kOverflowList | + nsIFrame::kExcessOverflowContainersList | + nsIFrame::kOverflowOutOfFlowList); + nsBlockFrame* blockFrame = GetAsBlock(aFrame); + if (blockFrame) { + contentBEnd = + std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame)); + skip |= nsIFrame::kPrincipalList; + } + nsIFrame::ChildListIterator lists(aFrame); + for (; !lists.IsDone(); lists.Next()) { + if (!skip.Contains(lists.CurrentID())) { + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* child = childFrames.get(); + nscoord offset = + child->GetLogicalNormalPosition(aWM, + aFrame->GetSize()).B(aWM); + contentBEnd = std::max(contentBEnd, + CalculateContentBEnd(aWM, child) + offset); + } + } + } + } + return contentBEnd; +} + +/* static */ nsIFrame* +nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) +{ + nsIFrame* layer; + for (layer = aFrame; layer; layer = layer->GetParent()) { + if (layer->IsAbsPosContainingBlock() || + (layer->GetParent() && + layer->GetParent()->GetType() == nsGkAtoms::scrollFrame)) + break; + } + if (layer) + return layer; + return aFrame->PresContext()->PresShell()->FrameManager()->GetRootFrame(); +} + +SamplingFilter +nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame) +{ + SamplingFilter defaultFilter = SamplingFilter::GOOD; + nsStyleContext *sc; + if (nsCSSRendering::IsCanvasFrame(aForFrame)) { + nsCSSRendering::FindBackground(aForFrame, &sc); + } else { + sc = aForFrame->StyleContext(); + } + + switch (sc->StyleVisibility()->mImageRendering) { + case NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED: + return SamplingFilter::POINT; + case NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY: + return SamplingFilter::LINEAR; + case NS_STYLE_IMAGE_RENDERING_CRISPEDGES: + return SamplingFilter::POINT; + default: + return defaultFilter; + } +} + +/** + * Given an image being drawn into an appunit coordinate system, and + * a point in that coordinate system, map the point back into image + * pixel space. + * @param aSize the size of the image, in pixels + * @param aDest the rectangle that the image is being mapped into + * @param aPt a point in the same coordinate system as the rectangle + */ +static gfxPoint +MapToFloatImagePixels(const gfxSize& aSize, + const gfxRect& aDest, const gfxPoint& aPt) +{ + return gfxPoint(((aPt.x - aDest.X())*aSize.width)/aDest.Width(), + ((aPt.y - aDest.Y())*aSize.height)/aDest.Height()); +} + +/** + * Given an image being drawn into an pixel-based coordinate system, and + * a point in image space, map the point into the pixel-based coordinate + * system. + * @param aSize the size of the image, in pixels + * @param aDest the rectangle that the image is being mapped into + * @param aPt a point in image space + */ +static gfxPoint +MapToFloatUserPixels(const gfxSize& aSize, + const gfxRect& aDest, const gfxPoint& aPt) +{ + return gfxPoint(aPt.x*aDest.Width()/aSize.width + aDest.X(), + aPt.y*aDest.Height()/aSize.height + aDest.Y()); +} + +/* static */ gfxRect +nsLayoutUtils::RectToGfxRect(const nsRect& aRect, int32_t aAppUnitsPerDevPixel) +{ + return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel, + gfxFloat(aRect.y) / aAppUnitsPerDevPixel, + gfxFloat(aRect.width) / aAppUnitsPerDevPixel, + gfxFloat(aRect.height) / aAppUnitsPerDevPixel); +} + +struct SnappedImageDrawingParameters { + // A transform from image space to device space. + gfxMatrix imageSpaceToDeviceSpace; + // The size at which the image should be drawn (which may not be its + // intrinsic size due to, for example, HQ scaling). + nsIntSize size; + // The region in tiled image space which will be drawn, with an associated + // region to which sampling should be restricted. + ImageRegion region; + // The default viewport size for SVG images, which we use unless a different + // one has been explicitly specified. This is the same as |size| except that + // it does not take into account any transformation on the gfxContext we're + // drawing to - for example, CSS transforms are not taken into account. + CSSIntSize svgViewportSize; + // Whether there's anything to draw at all. + bool shouldDraw; + + SnappedImageDrawingParameters() + : region(ImageRegion::Empty()) + , shouldDraw(false) + {} + + SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace, + const nsIntSize& aSize, + const ImageRegion& aRegion, + const CSSIntSize& aSVGViewportSize) + : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace) + , size(aSize) + , region(aRegion) + , svgViewportSize(aSVGViewportSize) + , shouldDraw(true) + {} +}; + +/** + * Given two axis-aligned rectangles, returns the transformation that maps the + * first onto the second. + * + * @param aFrom The rect to be transformed. + * @param aTo The rect that aFrom should be mapped onto by the transformation. + */ +static gfxMatrix +TransformBetweenRects(const gfxRect& aFrom, const gfxRect& aTo) +{ + gfxSize scale(aTo.width / aFrom.width, + aTo.height / aFrom.height); + gfxPoint translation(aTo.x - aFrom.x * scale.width, + aTo.y - aFrom.y * scale.height); + return gfxMatrix(scale.width, 0, 0, scale.height, + translation.x, translation.y); +} + +static nsRect +TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) +{ + nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft(); + return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width, + distance.y / aAnyTile.height * aAnyTile.height); +} + +static gfxFloat +StableRound(gfxFloat aValue) +{ + // Values slightly less than 0.5 should round up like 0.5 would; we're + // assuming they were meant to be 0.5. + return floor(aValue + 0.5001); +} + +static gfxPoint +StableRound(const gfxPoint& aPoint) +{ + return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y)); +} + +/** + * Given a set of input parameters, compute certain output parameters + * for drawing an image with the image snapping algorithm. + * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering + * + * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters + */ +static SnappedImageDrawingParameters +ComputeSnappedImageDrawingParameters(gfxContext* aCtx, + int32_t aAppUnitsPerDevPixel, + const nsRect aDest, + const nsRect aFill, + const nsPoint aAnchor, + const nsRect aDirty, + imgIContainer* aImage, + const SamplingFilter aSamplingFilter, + uint32_t aImageFlags, + ExtendMode aExtendMode) +{ + if (aDest.IsEmpty() || aFill.IsEmpty()) + return SnappedImageDrawingParameters(); + + // Avoid unnecessarily large offsets. + bool doTile = !aDest.Contains(aFill); + nsRect appUnitDest = doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) + : aDest; + nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft()); + + gfxRect devPixelDest = + nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel); + gfxRect devPixelFill = + nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel); + gfxRect devPixelDirty = + nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel); + + gfxMatrix currentMatrix = aCtx->CurrentMatrix(); + gfxRect fill = devPixelFill; + gfxRect dest = devPixelDest; + bool didSnap; + // Snap even if we have a scale in the context. But don't snap if + // we have something that's not translation+scale, or if the scale flips in + // the X or Y direction, because snapped image drawing can't handle that yet. + if (!currentMatrix.HasNonAxisAlignedTransform() && + currentMatrix._11 > 0.0 && currentMatrix._22 > 0.0 && + aCtx->UserToDevicePixelSnapped(fill, true) && + aCtx->UserToDevicePixelSnapped(dest, true)) { + // We snapped. On this code path, |fill| and |dest| take into account + // currentMatrix's transform. + didSnap = true; + } else { + // We didn't snap. On this code path, |fill| and |dest| do not take into + // account currentMatrix's transform. + didSnap = false; + fill = devPixelFill; + dest = devPixelDest; + } + + // If we snapped above, |dest| already takes into account |currentMatrix|'s scale + // and has integer coordinates. If not, we need these properties to compute + // the optimal drawn image size, so compute |snappedDestSize| here. + gfxSize snappedDestSize = dest.Size(); + if (!didSnap) { + gfxSize scaleFactors = currentMatrix.ScaleFactors(true); + snappedDestSize.Scale(scaleFactors.width, scaleFactors.height); + snappedDestSize.width = NS_round(snappedDestSize.width); + snappedDestSize.height = NS_round(snappedDestSize.height); + } + + // We need to be sure that this is at least one pixel in width and height, + // or we'll end up drawing nothing even if we have a nonempty fill. + snappedDestSize.width = std::max(snappedDestSize.width, 1.0); + snappedDestSize.height = std::max(snappedDestSize.height, 1.0); + + // Bail if we're not going to end up drawing anything. + if (fill.IsEmpty() || snappedDestSize.IsEmpty()) { + return SnappedImageDrawingParameters(); + } + + nsIntSize intImageSize = + aImage->OptimalImageSizeForDest(snappedDestSize, + imgIContainer::FRAME_CURRENT, + aSamplingFilter, aImageFlags); + gfxSize imageSize(intImageSize.width, intImageSize.height); + + // XXX(seth): May be buggy; see bug 1151016. + CSSIntSize svgViewportSize = currentMatrix.IsIdentity() + ? CSSIntSize(intImageSize.width, intImageSize.height) + : CSSIntSize::Truncate(devPixelDest.width, devPixelDest.height); + + // Compute the set of pixels that would be sampled by an ideal rendering + gfxPoint subimageTopLeft = + MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft()); + gfxPoint subimageBottomRight = + MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.BottomRight()); + gfxRect subimage; + subimage.MoveTo(NSToIntFloor(subimageTopLeft.x), + NSToIntFloor(subimageTopLeft.y)); + subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x, + NSToIntCeil(subimageBottomRight.y) - subimage.y); + + if (subimage.IsEmpty()) { + // Bail if the subimage is empty (we're not going to be drawing anything). + return SnappedImageDrawingParameters(); + } + + gfxMatrix transform; + gfxMatrix invTransform; + + bool anchorAtUpperLeft = anchor.x == appUnitDest.x && + anchor.y == appUnitDest.y; + bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest); + if (anchorAtUpperLeft && exactlyOneImageCopy) { + // The simple case: we can ignore the anchor point and compute the + // transformation from the sampled region (the subimage) to the fill rect. + // This approach is preferable when it works since it tends to produce + // less numerical error. + transform = TransformBetweenRects(subimage, fill); + invTransform = TransformBetweenRects(fill, subimage); + } else { + // The more complicated case: we compute the transformation from the + // image rect positioned at the image space anchor point to the dest rect + // positioned at the device space anchor point. + + // Compute the anchor point in both device space and image space. This + // code assumes that pixel-based devices have one pixel per device unit! + gfxPoint anchorPoint(gfxFloat(anchor.x)/aAppUnitsPerDevPixel, + gfxFloat(anchor.y)/aAppUnitsPerDevPixel); + gfxPoint imageSpaceAnchorPoint = + MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint); + + if (didSnap) { + imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint); + anchorPoint = imageSpaceAnchorPoint; + anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint); + anchorPoint = currentMatrix.Transform(anchorPoint); + anchorPoint = StableRound(anchorPoint); + } + + // Compute an unsnapped version of the dest rect's size. We continue to + // follow the pattern that we take |currentMatrix| into account only if + // |didSnap| is true. + gfxSize unsnappedDestSize + = didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors(true) + : devPixelDest.Size(); + + gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize); + gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize); + + // Calculate anchoredDestRect with snapped fill rect when the devPixelFill rect + // corresponds to just a single tile in that direction + if (fill.Width() != devPixelFill.Width() && + devPixelDest.x == devPixelFill.x && + devPixelDest.XMost() == devPixelFill.XMost()) { + anchoredDestRect.width = fill.width; + } + if (fill.Height() != devPixelFill.Height() && + devPixelDest.y == devPixelFill.y && + devPixelDest.YMost() == devPixelFill.YMost()) { + anchoredDestRect.height = fill.height; + } + + transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect); + invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect); + } + + // If the transform is not a straight translation by integers, then + // filtering will occur, and restricting the fill rect to the dirty rect + // would change the values computed for edge pixels, which we can't allow. + // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not + // produce pixel-aligned coordinates, which would also break the values + // computed for edge pixels. + if (didSnap && !invTransform.HasNonIntegerTranslation()) { + // This form of Transform is safe to call since non-axis-aligned + // transforms wouldn't be snapped. + devPixelDirty = currentMatrix.Transform(devPixelDirty); + devPixelDirty.RoundOut(); + fill = fill.Intersect(devPixelDirty); + } + if (fill.IsEmpty()) + return SnappedImageDrawingParameters(); + + gfxRect imageSpaceFill(didSnap ? invTransform.Transform(fill) + : invTransform.TransformBounds(fill)); + + // If we didn't snap, we need to post-multiply the matrix on the context to + // get the final matrix we'll draw with, because we didn't take it into + // account when computing the matrices above. + if (!didSnap) { + transform = transform * currentMatrix; + } + + ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP) + ? ExtendMode::CLAMP + : aExtendMode; + // We were passed in the default extend mode but need to tile. + if (extendMode == ExtendMode::CLAMP && doTile) { + MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP)); + extendMode = ExtendMode::REPEAT; + } + + ImageRegion region = + ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage, extendMode); + + return SnappedImageDrawingParameters(transform, intImageSize, + region, svgViewportSize); +} + +static DrawResult +DrawImageInternal(gfxContext& aContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const SamplingFilter aSamplingFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + const SVGImageContext* aSVGContext, + uint32_t aImageFlags, + ExtendMode aExtendMode = ExtendMode::CLAMP) +{ + DrawResult result = DrawResult::SUCCESS; + + if (aPresContext->Type() == nsPresContext::eContext_Print) { + // We want vector images to be passed on as vector commands, not a raster + // image. + aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE; + } + if (aDest.Contains(aFill)) { + aImageFlags |= imgIContainer::FLAG_CLAMP; + } + int32_t appUnitsPerDevPixel = + aPresContext->AppUnitsPerDevPixel(); + + SnappedImageDrawingParameters params = + ComputeSnappedImageDrawingParameters(&aContext, appUnitsPerDevPixel, aDest, + aFill, aAnchor, aDirty, aImage, + aSamplingFilter, aImageFlags, aExtendMode); + + if (!params.shouldDraw) { + return result; + } + + { + gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext); + + RefPtr destCtx = &aContext; + + destCtx->SetMatrix(params.imageSpaceToDeviceSpace); + + Maybe svgContext = ToMaybe(aSVGContext); + if (!svgContext) { + // Use the default viewport. + svgContext = Some(SVGImageContext(params.svgViewportSize, Nothing())); + } + + result = aImage->Draw(destCtx, params.size, params.region, + imgIContainer::FRAME_CURRENT, aSamplingFilter, + svgContext, aImageFlags); + + } + + return result; +} + +/* static */ DrawResult +nsLayoutUtils::DrawSingleUnscaledImage(gfxContext& aContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const SamplingFilter aSamplingFilter, + const nsPoint& aDest, + const nsRect* aDirty, + uint32_t aImageFlags, + const nsRect* aSourceArea) +{ + CSSIntSize imageSize; + aImage->GetWidth(&imageSize.width); + aImage->GetHeight(&imageSize.height); + if (imageSize.width < 1 || imageSize.height < 1) { + NS_WARNING("Image width or height is non-positive"); + return DrawResult::TEMPORARY_ERROR; + } + + nsSize size(CSSPixel::ToAppUnits(imageSize)); + nsRect source; + if (aSourceArea) { + source = *aSourceArea; + } else { + source.SizeTo(size); + } + + nsRect dest(aDest - source.TopLeft(), size); + nsRect fill(aDest, source.Size()); + // Ensure that only a single image tile is drawn. If aSourceArea extends + // outside the image bounds, we want to honor the aSourceArea-to-aDest + // translation but we don't want to actually tile the image. + fill.IntersectRect(fill, dest); + return DrawImageInternal(aContext, aPresContext, + aImage, aSamplingFilter, + dest, fill, aDest, aDirty ? *aDirty : dest, + nullptr, aImageFlags); +} + +/* static */ DrawResult +nsLayoutUtils::DrawSingleImage(gfxContext& aContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const SamplingFilter aSamplingFilter, + const nsRect& aDest, + const nsRect& aDirty, + const SVGImageContext* aSVGContext, + uint32_t aImageFlags, + const nsPoint* aAnchorPoint, + const nsRect* aSourceArea) +{ + nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); + CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(aImage, aDest.Size())); + if (pixelImageSize.width < 1 || pixelImageSize.height < 1) { + NS_WARNING("Image width or height is non-positive"); + return DrawResult::TEMPORARY_ERROR; + } + + nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize)); + nsRect source; + nsCOMPtr image; + if (aSourceArea) { + source = *aSourceArea; + nsIntRect subRect(source.x, source.y, source.width, source.height); + subRect.ScaleInverseRoundOut(appUnitsPerCSSPixel); + image = ImageOps::Clip(aImage, subRect); + + nsRect imageRect; + imageRect.SizeTo(imageSize); + nsRect clippedSource = imageRect.Intersect(source); + + source -= clippedSource.TopLeft(); + imageSize = clippedSource.Size(); + } else { + source.SizeTo(imageSize); + image = aImage; + } + + nsRect dest = GetWholeImageDestination(imageSize, source, aDest); + + // Ensure that only a single image tile is drawn. If aSourceArea extends + // outside the image bounds, we want to honor the aSourceArea-to-aDest + // transform but we don't want to actually tile the image. + nsRect fill; + fill.IntersectRect(aDest, dest); + return DrawImageInternal(aContext, aPresContext, image, + aSamplingFilter, dest, fill, + aAnchorPoint ? *aAnchorPoint : fill.TopLeft(), + aDirty, aSVGContext, aImageFlags); +} + +/* static */ void +nsLayoutUtils::ComputeSizeForDrawing(imgIContainer *aImage, + CSSIntSize& aImageSize, /*outparam*/ + nsSize& aIntrinsicRatio, /*outparam*/ + bool& aGotWidth, /*outparam*/ + bool& aGotHeight /*outparam*/) +{ + aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width)); + aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height)); + bool gotRatio = NS_SUCCEEDED(aImage->GetIntrinsicRatio(&aIntrinsicRatio)); + + if (!(aGotWidth && aGotHeight) && !gotRatio) { + // We hit an error (say, because the image failed to load or couldn't be + // decoded) and should return zero size. + aGotWidth = aGotHeight = true; + aImageSize = CSSIntSize(0, 0); + aIntrinsicRatio = nsSize(0, 0); + } +} + +/* static */ CSSIntSize +nsLayoutUtils::ComputeSizeForDrawingWithFallback(imgIContainer* aImage, + const nsSize& aFallbackSize) +{ + CSSIntSize imageSize; + nsSize imageRatio; + bool gotHeight, gotWidth; + ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight); + + // If we didn't get both width and height, try to compute them using the + // intrinsic ratio of the image. + if (gotWidth != gotHeight) { + if (!gotWidth) { + if (imageRatio.height != 0) { + imageSize.width = + NSCoordSaturatingNonnegativeMultiply(imageSize.height, + float(imageRatio.width) / + float(imageRatio.height)); + gotWidth = true; + } + } else { + if (imageRatio.width != 0) { + imageSize.height = + NSCoordSaturatingNonnegativeMultiply(imageSize.width, + float(imageRatio.height) / + float(imageRatio.width)); + gotHeight = true; + } + } + } + + // If we still don't have a width or height, just use the fallback size the + // caller provided. + if (!gotWidth) { + imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width); + } + if (!gotHeight) { + imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height); + } + + return imageSize; +} + +/* static */ DrawResult +nsLayoutUtils::DrawBackgroundImage(gfxContext& aContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const CSSIntSize& aImageSize, + SamplingFilter aSamplingFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsSize& aRepeatSize, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags, + ExtendMode aExtendMode) +{ + PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage", + js::ProfileEntry::Category::GRAPHICS); + + SVGImageContext svgContext(aImageSize, Nothing()); + + /* Fast path when there is no need for image spacing */ + if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) { + return DrawImageInternal(aContext, aPresContext, aImage, + aSamplingFilter, aDest, aFill, aAnchor, + aDirty, &svgContext, aImageFlags, aExtendMode); + } + + nsPoint firstTilePos = aDest.TopLeft() + + nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) * aRepeatSize.width, + NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) * aRepeatSize.height); + for (int32_t i = firstTilePos.x; i < aFill.XMost(); i += aRepeatSize.width) { + for (int32_t j = firstTilePos.y; j < aFill.YMost(); j += aRepeatSize.height) { + nsRect dest(i, j, aDest.width, aDest.height); + DrawResult result = DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter, + dest, dest, aAnchor, aDirty, &svgContext, + aImageFlags, ExtendMode::CLAMP); + if (result != DrawResult::SUCCESS) { + return result; + } + } + } + + return DrawResult::SUCCESS; +} + +/* static */ DrawResult +nsLayoutUtils::DrawImage(gfxContext& aContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const SamplingFilter aSamplingFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags) +{ + return DrawImageInternal(aContext, aPresContext, aImage, + aSamplingFilter, aDest, aFill, aAnchor, + aDirty, nullptr, aImageFlags); +} + +/* static */ nsRect +nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize, + const nsRect& aImageSourceArea, + const nsRect& aDestArea) +{ + double scaleX = double(aDestArea.width)/aImageSourceArea.width; + double scaleY = double(aDestArea.height)/aImageSourceArea.height; + nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x*scaleX); + nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y*scaleY); + nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width*scaleX); + nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height*scaleY); + return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY), + nsSize(wholeSizeX, wholeSizeY)); +} + +/* static */ already_AddRefed +nsLayoutUtils::OrientImage(imgIContainer* aContainer, + const nsStyleImageOrientation& aOrientation) +{ + MOZ_ASSERT(aContainer, "Should have an image container"); + nsCOMPtr img(aContainer); + + if (aOrientation.IsFromImage()) { + img = ImageOps::Orient(img, img->GetOrientation()); + } else if (!aOrientation.IsDefault()) { + Angle angle = aOrientation.Angle(); + Flip flip = aOrientation.IsFlipped() ? Flip::Horizontal + : Flip::Unflipped; + img = ImageOps::Orient(img, Orientation(angle, flip)); + } + + return img.forget(); +} + +static bool NonZeroStyleCoord(const nsStyleCoord& aCoord) +{ + if (aCoord.IsCoordPercentCalcUnit()) { + // Since negative results are clamped to 0, check > 0. + return nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) > 0 || + nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) > 0; + } + + return true; +} + +/* static */ bool +nsLayoutUtils::HasNonZeroCorner(const nsStyleCorners& aCorners) +{ + NS_FOR_CSS_HALF_CORNERS(corner) { + if (NonZeroStyleCoord(aCorners.Get(corner))) + return true; + } + return false; +} + +// aCorner is a "full corner" value, i.e. NS_CORNER_TOP_LEFT etc +static bool IsCornerAdjacentToSide(uint8_t aCorner, css::Side aSide) +{ + static_assert((int)NS_SIDE_TOP == NS_CORNER_TOP_LEFT, "Check for Full Corner"); + static_assert((int)NS_SIDE_RIGHT == NS_CORNER_TOP_RIGHT, "Check for Full Corner"); + static_assert((int)NS_SIDE_BOTTOM == NS_CORNER_BOTTOM_RIGHT, "Check for Full Corner"); + static_assert((int)NS_SIDE_LEFT == NS_CORNER_BOTTOM_LEFT, "Check for Full Corner"); + static_assert((int)NS_SIDE_TOP == ((NS_CORNER_TOP_RIGHT - 1)&3), "Check for Full Corner"); + static_assert((int)NS_SIDE_RIGHT == ((NS_CORNER_BOTTOM_RIGHT - 1)&3), "Check for Full Corner"); + static_assert((int)NS_SIDE_BOTTOM == ((NS_CORNER_BOTTOM_LEFT - 1)&3), "Check for Full Corner"); + static_assert((int)NS_SIDE_LEFT == ((NS_CORNER_TOP_LEFT - 1)&3), "Check for Full Corner"); + + return aSide == aCorner || aSide == ((aCorner - 1)&3); +} + +/* static */ bool +nsLayoutUtils::HasNonZeroCornerOnSide(const nsStyleCorners& aCorners, + css::Side aSide) +{ + static_assert(NS_CORNER_TOP_LEFT_X/2 == NS_CORNER_TOP_LEFT, "Check for Non Zero on side"); + static_assert(NS_CORNER_TOP_LEFT_Y/2 == NS_CORNER_TOP_LEFT, "Check for Non Zero on side"); + static_assert(NS_CORNER_TOP_RIGHT_X/2 == NS_CORNER_TOP_RIGHT, "Check for Non Zero on side"); + static_assert(NS_CORNER_TOP_RIGHT_Y/2 == NS_CORNER_TOP_RIGHT, "Check for Non Zero on side"); + static_assert(NS_CORNER_BOTTOM_RIGHT_X/2 == NS_CORNER_BOTTOM_RIGHT, "Check for Non Zero on side"); + static_assert(NS_CORNER_BOTTOM_RIGHT_Y/2 == NS_CORNER_BOTTOM_RIGHT, "Check for Non Zero on side"); + static_assert(NS_CORNER_BOTTOM_LEFT_X/2 == NS_CORNER_BOTTOM_LEFT, "Check for Non Zero on side"); + static_assert(NS_CORNER_BOTTOM_LEFT_Y/2 == NS_CORNER_BOTTOM_LEFT, "Check for Non Zero on side"); + + NS_FOR_CSS_HALF_CORNERS(corner) { + // corner is a "half corner" value, so dividing by two gives us a + // "full corner" value. + if (NonZeroStyleCoord(aCorners.Get(corner)) && + IsCornerAdjacentToSide(corner/2, aSide)) + return true; + } + return false; +} + +/* static */ nsTransparencyMode +nsLayoutUtils::GetFrameTransparency(nsIFrame* aBackgroundFrame, + nsIFrame* aCSSRootFrame) { + if (aCSSRootFrame->StyleEffects()->mOpacity < 1.0f) + return eTransparencyTransparent; + + if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius)) + return eTransparencyTransparent; + + if (aCSSRootFrame->StyleDisplay()->mAppearance == NS_THEME_WIN_GLASS) + return eTransparencyGlass; + + if (aCSSRootFrame->StyleDisplay()->mAppearance == NS_THEME_WIN_BORDERLESS_GLASS) + return eTransparencyBorderlessGlass; + + nsITheme::Transparency transparency; + if (aCSSRootFrame->IsThemed(&transparency)) + return transparency == nsITheme::eTransparent + ? eTransparencyTransparent + : eTransparencyOpaque; + + // We need an uninitialized window to be treated as opaque because + // doing otherwise breaks window display effects on some platforms, + // specifically Vista. (bug 450322) + if (aBackgroundFrame->GetType() == nsGkAtoms::viewportFrame && + !aBackgroundFrame->PrincipalChildList().FirstChild()) { + return eTransparencyOpaque; + } + + nsStyleContext* bgSC; + if (!nsCSSRendering::FindBackground(aBackgroundFrame, &bgSC)) { + return eTransparencyTransparent; + } + const nsStyleBackground* bg = bgSC->StyleBackground(); + if (NS_GET_A(bg->mBackgroundColor) < 255 || + // bottom layer's clip is used for the color + bg->BottomLayer().mClip != NS_STYLE_IMAGELAYER_CLIP_BORDER) + return eTransparencyTransparent; + return eTransparencyOpaque; +} + +static bool IsPopupFrame(nsIFrame* aFrame) +{ + // aFrame is a popup it's the list control frame dropdown for a combobox. + nsIAtom* frameType = aFrame->GetType(); + if (frameType == nsGkAtoms::listControlFrame) { + nsListControlFrame* lcf = static_cast(aFrame); + return lcf->IsInDropDownMode(); + } + + // ... or if it's a XUL menupopup frame. + return frameType == nsGkAtoms::menuPopupFrame; +} + +/* static */ bool +nsLayoutUtils::IsPopup(nsIFrame* aFrame) +{ + // Optimization: the frame can't possibly be a popup if it has no view. + if (!aFrame->HasView()) { + NS_ASSERTION(!IsPopupFrame(aFrame), "popup frame must have a view"); + return false; + } + return IsPopupFrame(aFrame); +} + +/* static */ nsIFrame* +nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame) +{ + // We could use GetRootPresContext() here if the + // NS_FRAME_IN_POPUP frame bit is set. + nsIFrame* f = aFrame; + for (;;) { + if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + f = f->PresContext()->FrameManager()->GetRootFrame(); + } else if (IsPopup(f)) { + return f; + } + nsIFrame* parent = GetCrossDocParentFrame(f); + if (!parent) + return f; + f = parent; + } +} + +/* static */ nsIFrame* +nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame) +{ + nsIFrame *f = aFrame; + for (;;) { + if (f->IsTransformed() || f->IsPreserve3DLeaf() || IsPopup(f)) { + return f; + } + nsIFrame* parent = GetCrossDocParentFrame(f); + if (!parent) { + return f; + } + f = parent; + } +} + +/* static */ uint32_t +nsLayoutUtils::GetTextRunFlagsForStyle(nsStyleContext* aStyleContext, + const nsStyleFont* aStyleFont, + const nsStyleText* aStyleText, + nscoord aLetterSpacing) +{ + uint32_t result = 0; + if (aLetterSpacing != 0) { + result |= gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES; + } + if (aStyleText->mControlCharacterVisibility == NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN) { + result |= gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS; + } + switch (aStyleContext->StyleText()->mTextRendering) { + case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED: + result |= gfxTextRunFactory::TEXT_OPTIMIZE_SPEED; + break; + case NS_STYLE_TEXT_RENDERING_AUTO: + if (aStyleFont->mFont.size < + aStyleContext->PresContext()->GetAutoQualityMinFontSize()) { + result |= gfxTextRunFactory::TEXT_OPTIMIZE_SPEED; + } + break; + default: + break; + } + return result | GetTextRunOrientFlagsForStyle(aStyleContext); +} + +/* static */ uint32_t +nsLayoutUtils::GetTextRunOrientFlagsForStyle(nsStyleContext* aStyleContext) +{ + uint8_t writingMode = aStyleContext->StyleVisibility()->mWritingMode; + switch (writingMode) { + case NS_STYLE_WRITING_MODE_HORIZONTAL_TB: + return gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL; + + case NS_STYLE_WRITING_MODE_VERTICAL_LR: + case NS_STYLE_WRITING_MODE_VERTICAL_RL: + switch (aStyleContext->StyleVisibility()->mTextOrientation) { + case NS_STYLE_TEXT_ORIENTATION_MIXED: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED; + case NS_STYLE_TEXT_ORIENTATION_UPRIGHT: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT; + case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + default: + NS_NOTREACHED("unknown text-orientation"); + return 0; + } + + case NS_STYLE_WRITING_MODE_SIDEWAYS_LR: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; + + case NS_STYLE_WRITING_MODE_SIDEWAYS_RL: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + + default: + NS_NOTREACHED("unknown writing-mode"); + return 0; + } +} + +/* static */ void +nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2, + nsRect* aHStrip, nsRect* aVStrip) { + NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(), + "expected rects at the same position"); + nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width), + std::max(aR1.height, aR2.height)); + nscoord VStripStart = std::min(aR1.width, aR2.width); + nscoord HStripStart = std::min(aR1.height, aR2.height); + *aVStrip = unionRect; + aVStrip->x += VStripStart; + aVStrip->width -= VStripStart; + *aHStrip = unionRect; + aHStrip->y += HStripStart; + aHStrip->height -= HStripStart; +} + +nsDeviceContext* +nsLayoutUtils::GetDeviceContextForScreenInfo(nsPIDOMWindowOuter* aWindow) +{ + if (!aWindow) { + return nullptr; + } + + nsCOMPtr docShell = aWindow->GetDocShell(); + while (docShell) { + // Now make sure our size is up to date. That will mean that the device + // context does the right thing on multi-monitor systems when we return it to + // the caller. It will also make sure that our prescontext has been created, + // if we're supposed to have one. + nsCOMPtr win = docShell->GetWindow(); + if (!win) { + // No reason to go on + return nullptr; + } + + win->EnsureSizeUpToDate(); + + RefPtr presContext; + docShell->GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + nsDeviceContext* context = presContext->DeviceContext(); + if (context) { + return context; + } + } + + nsCOMPtr parentItem; + docShell->GetParent(getter_AddRefs(parentItem)); + docShell = do_QueryInterface(parentItem); + } + + return nullptr; +} + +/* static */ bool +nsLayoutUtils::IsReallyFixedPos(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame->GetParent(), + "IsReallyFixedPos called on frame not in tree"); + NS_PRECONDITION(aFrame->StyleDisplay()->mPosition == + NS_STYLE_POSITION_FIXED, + "IsReallyFixedPos called on non-'position:fixed' frame"); + + nsIAtom *parentType = aFrame->GetParent()->GetType(); + return parentType == nsGkAtoms::viewportFrame || + parentType == nsGkAtoms::pageContentFrame; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromOffscreenCanvas(OffscreenCanvas* aOffscreenCanvas, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + SurfaceFromElementResult result; + + bool* isPremultiplied = nullptr; + if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) { + isPremultiplied = &result.mIsPremultiplied; + } + + nsIntSize size = aOffscreenCanvas->GetWidthHeight(); + + result.mSourceSurface = aOffscreenCanvas->GetSurfaceSnapshot(isPremultiplied); + if (!result.mSourceSurface) { + // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just + // draw nothing, so return an empty surface. + DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height), + SurfaceFormat::B8G8R8A8); + if (dt) { + result.mSourceSurface = dt->Snapshot(); + } + } else if (aTarget) { + RefPtr opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); + if (opt) { + result.mSourceSurface = opt; + } + } + + result.mHasSize = true; + result.mSize = size; + result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly(); + + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + SurfaceFromElementResult result; + nsresult rv; + + nsCOMPtr imgRequest; + rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(imgRequest)); + if (NS_FAILED(rv)) { + return result; + } + + if (!imgRequest) { + // There's no image request. This is either because a request for + // a non-empty URI failed, or the URI is the empty string. + nsCOMPtr currentURI; + aElement->GetCurrentURI(getter_AddRefs(currentURI)); + if (!currentURI) { + // Treat the empty URI as available instead of broken state. + result.mHasSize = true; + } + return result; + } + + uint32_t status; + imgRequest->GetImageStatus(&status); + result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE; + if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) { + // Spec says to use GetComplete, but that only works on + // nsIDOMHTMLImageElement, and we support all sorts of other stuff + // here. Do this for now pending spec clarification. + result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0; + return result; + } + + nsCOMPtr principal; + rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal)); + if (NS_FAILED(rv)) { + return result; + } + + nsCOMPtr imgContainer; + rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); + if (NS_FAILED(rv)) { + return result; + } + + uint32_t noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS; + + uint32_t whichFrame = (aSurfaceFlags & SFE_WANT_FIRST_FRAME) + ? (uint32_t) imgIContainer::FRAME_FIRST + : (uint32_t) imgIContainer::FRAME_CURRENT; + uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE; + if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION) + frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION; + if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) { + frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + result.mIsPremultiplied = false; + } + + int32_t imgWidth, imgHeight; + nsCOMPtr content = do_QueryInterface(aElement); + HTMLImageElement* element = HTMLImageElement::FromContentOrNull(content); + if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR && + element && + imgContainer->GetType() == imgIContainer::TYPE_VECTOR) { + imgWidth = element->Width(); + imgHeight = element->Height(); + } else { + rv = imgContainer->GetWidth(&imgWidth); + nsresult rv2 = imgContainer->GetHeight(&imgHeight); + if (NS_FAILED(rv) || NS_FAILED(rv2)) + return result; + } + result.mSize = IntSize(imgWidth, imgHeight); + + if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) { + if (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) { + frameFlags |= imgIContainer::FLAG_WANT_DATA_SURFACE; + } + result.mSourceSurface = imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags); + if (!result.mSourceSurface) { + return result; + } + // The surface we return is likely to be cached. We don't want to have to + // convert to a surface that's compatible with aTarget each time it's used + // (that would result in terrible performance), so we convert once here + // upfront if aTarget is specified. + if (aTarget) { + RefPtr optSurface = + aTarget->OptimizeSourceSurface(result.mSourceSurface); + if (optSurface) { + result.mSourceSurface = optSurface; + } + } + } else { + result.mDrawInfo.mImgContainer = imgContainer; + result.mDrawInfo.mWhichFrame = whichFrame; + result.mDrawInfo.mDrawingFlags = frameFlags; + } + + int32_t corsmode; + if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) { + result.mCORSUsed = (corsmode != imgIRequest::CORS_NONE); + } + + result.mPrincipal = principal.forget(); + // no images, including SVG images, can load content from another domain. + result.mIsWriteOnly = false; + result.mImageRequest = imgRequest.forget(); + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(HTMLImageElement *aElement, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + return SurfaceFromElement(static_cast(aElement), + aSurfaceFlags, aTarget); +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + SurfaceFromElementResult result; + + bool* isPremultiplied = nullptr; + if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) { + isPremultiplied = &result.mIsPremultiplied; + } + + IntSize size = aElement->GetSize(); + + result.mSourceSurface = aElement->GetSurfaceSnapshot(isPremultiplied); + if (!result.mSourceSurface) { + // If the element doesn't have a context then we won't get a snapshot. The canvas spec wants us to not error and just + // draw nothing, so return an empty surface. + DrawTarget *ref = aTarget ? aTarget.get() : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + RefPtr dt = ref->CreateSimilarDrawTarget(IntSize(size.width, size.height), + SurfaceFormat::B8G8R8A8); + if (dt) { + result.mSourceSurface = dt->Snapshot(); + } + } else if (aTarget) { + RefPtr opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); + if (opt) { + result.mSourceSurface = opt; + } + } + + // Ensure that any future changes to the canvas trigger proper invalidation, + // in case this is being used by -moz-element() + aElement->MarkContextClean(); + + result.mHasSize = true; + result.mSize = size; + result.mPrincipal = aElement->NodePrincipal(); + result.mIsWriteOnly = aElement->IsWriteOnly(); + + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + SurfaceFromElementResult result; + + NS_WARNING_ASSERTION( + (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) == 0, + "We can't support non-premultiplied alpha for video!"); + + if (aElement->ContainsRestrictedContent()) { + return result; + } + + uint16_t readyState; + if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) && + (readyState == nsIDOMHTMLMediaElement::HAVE_NOTHING || + readyState == nsIDOMHTMLMediaElement::HAVE_METADATA)) { + result.mIsStillLoading = true; + return result; + } + + // If it doesn't have a principal, just bail + nsCOMPtr principal = aElement->GetCurrentVideoPrincipal(); + if (!principal) + return result; + + ImageContainer* container = aElement->GetImageContainer(); + if (!container) + return result; + + AutoLockImage lockImage(container); + + result.mLayersImage = lockImage.GetImage(); + if (!result.mLayersImage) + return result; + + if (aTarget) { + // They gave us a DrawTarget to optimize for, so even though we have a layers::Image, + // we should unconditionally grab a SourceSurface and try to optimize it. + result.mSourceSurface = result.mLayersImage->GetAsSourceSurface(); + if (!result.mSourceSurface) + return result; + + RefPtr opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); + if (opt) { + result.mSourceSurface = opt; + } + } + + result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE; + result.mHasSize = true; + result.mSize = result.mLayersImage->GetSize(); + result.mPrincipal = principal.forget(); + result.mIsWriteOnly = false; + + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(dom::Element* aElement, + uint32_t aSurfaceFlags, + RefPtr& aTarget) +{ + // If it's a , we may be able to just grab its internal surface + if (HTMLCanvasElement* canvas = + HTMLCanvasElement::FromContentOrNull(aElement)) { + return SurfaceFromElement(canvas, aSurfaceFlags, aTarget); + } + + // Maybe it's