diff -r 000000000000 -r 6474c204b198 layout/base/nsLayoutUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/base/nsLayoutUtils.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,6597 @@ +/* -*- 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/MemoryReporting.h" +#include "nsPresContext.h" +#include "nsIContent.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsFrameList.h" +#include "nsGkAtoms.h" +#include "nsIAtom.h" +#include "nsCSSPseudoElements.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSColorUtils.h" +#include "nsView.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 "gfxRect.h" +#include "gfxContext.h" +#include "nsRenderingContext.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsCSSRendering.h" +#include "nsThemeConstants.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIWidget.h" +#include "gfxMatrix.h" +#include "gfxPoint3D.h" +#include "gfxPrefs.h" +#include "gfxTypes.h" +#include "nsTArray.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "nsICanvasRenderingContextInternal.h" +#include "gfxPlatform.h" +#include +#include "mozilla/dom/HTMLVideoElement.h" +#include "mozilla/dom/HTMLImageElement.h" +#include "mozilla/dom/DOMRect.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 "gfxUtils.h" +#include "nsDataHashtable.h" +#include "nsTextFrame.h" +#include "nsFontFaceList.h" +#include "nsFontInflationData.h" +#include "nsSVGUtils.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 "mozilla/Preferences.h" + +#include "mozilla/LookAndFeel.h" + +#ifdef MOZ_XUL +#include "nsXULPopupManager.h" +#endif + +#include "GeckoProfiler.h" +#include "nsAnimationManager.h" +#include "nsTransitionManager.h" +#include "RestyleManager.h" + +// Additional includes used on B2G by code in GetOrMaybeCreateDisplayPort(). +#ifdef MOZ_WIDGET_GONK +#include "mozilla/layers/AsyncPanZoomController.h" +#endif + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::gfx; + +using mozilla::image::Angle; +using mozilla::image::Flip; +using mozilla::image::ImageOps; +using mozilla::image::Orientation; + +#define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" +#define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled" +#define TEXT_ALIGN_TRUE_ENABLED_PREF_NAME "layout.css.text-align-true-value.enabled" + +#ifdef DEBUG +// TODO: remove, see bug 598468. +bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false; +#endif // DEBUG + +typedef FrameMetrics::ViewID ViewID; + +/* 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 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] = + isGridEnabled ? eCSSKeyword_grid : eCSSKeyword_UNKNOWN; + } + if (sIndexOfInlineGridInDisplayTable >= 0) { + nsCSSProps::kDisplayKTable[sIndexOfInlineGridInDisplayTable] = + isGridEnabled ? eCSSKeyword_inline_grid : eCSSKeyword_UNKNOWN; + } +} + +// When the pref "layout.css.sticky.enabled" changes, this function is invoked +// to let us update kPositionKTable, to selectively disable or restore the +// entry for "sticky" in that table. +static void +StickyEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + MOZ_ASSERT(strncmp(aPrefName, STICKY_ENABLED_PREF_NAME, + ArrayLength(STICKY_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 sIndexOfStickyInPositionTable; + static bool sIsStickyKeywordIndexInitialized; // initialized to false + + bool isStickyEnabled = + Preferences::GetBool(STICKY_ENABLED_PREF_NAME, false); + + if (!sIsStickyKeywordIndexInitialized) { + // First run: find the position of "sticky" in kPositionKTable. + sIndexOfStickyInPositionTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_sticky, + nsCSSProps::kPositionKTable); + MOZ_ASSERT(sIndexOfStickyInPositionTable >= 0, + "Couldn't find sticky in kPositionKTable"); + sIsStickyKeywordIndexInitialized = true; + } + + // OK -- now, stomp on or restore the "sticky" entry in kPositionKTable, + // depending on whether the sticky pref is enabled vs. disabled. + nsCSSProps::kPositionKTable[sIndexOfStickyInPositionTable] = + isStickyEnabled ? eCSSKeyword_sticky : eCSSKeyword_UNKNOWN; +} + +// When the pref "layout.css.text-align-true-value.enabled" changes, this +// function is called to let us update kTextAlignKTable & kTextAlignLastKTable, +// to selectively disable or restore the entries for "true" in those tables. +static void +TextAlignTrueEnabledPrefChangeCallback(const char* aPrefName, void* aClosure) +{ + NS_ASSERTION(strcmp(aPrefName, TEXT_ALIGN_TRUE_ENABLED_PREF_NAME) == 0, + "Did you misspell " TEXT_ALIGN_TRUE_ENABLED_PREF_NAME " ?"); + + static bool sIsInitialized; + static int32_t sIndexOfTrueInTextAlignTable; + static int32_t sIndexOfTrueInTextAlignLastTable; + bool isTextAlignTrueEnabled = + Preferences::GetBool(TEXT_ALIGN_TRUE_ENABLED_PREF_NAME, false); + + if (!sIsInitialized) { + // First run: find the position of "true" in kTextAlignKTable. + sIndexOfTrueInTextAlignTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_true, + nsCSSProps::kTextAlignKTable); + // First run: find the position of "true" in kTextAlignLastKTable. + sIndexOfTrueInTextAlignLastTable = + nsCSSProps::FindIndexOfKeyword(eCSSKeyword_true, + nsCSSProps::kTextAlignLastKTable); + sIsInitialized = true; + } + + // OK -- now, stomp on or restore the "true" entry in the keyword tables, + // depending on whether the pref is enabled vs. disabled. + MOZ_ASSERT(sIndexOfTrueInTextAlignTable >= 0); + nsCSSProps::kTextAlignKTable[sIndexOfTrueInTextAlignTable] = + isTextAlignTrueEnabled ? eCSSKeyword_true : eCSSKeyword_UNKNOWN; + MOZ_ASSERT(sIndexOfTrueInTextAlignLastTable >= 0); + nsCSSProps::kTextAlignLastKTable[sIndexOfTrueInTextAlignLastTable] = + isTextAlignTrueEnabled ? eCSSKeyword_true : eCSSKeyword_UNKNOWN; +} + +template +static AnimationsOrTransitions* +HasAnimationOrTransitionForCompositor(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty) +{ + AnimationsOrTransitions* animations = + static_cast(aContent->GetProperty(aAnimationProperty)); + if (animations) { + bool propertyMatches = animations->HasAnimationOfProperty(aProperty); + if (propertyMatches && + animations->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimate_AllowPartial)) { + return animations; + } + } + + return nullptr; +} + +bool +nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent, + nsCSSProperty aProperty) +{ + if (!aContent->MayHaveAnimations()) + return false; + return HasAnimationOrTransitionForCompositor + (aContent, nsGkAtoms::animationsProperty, aProperty) || + HasAnimationOrTransitionForCompositor + (aContent, nsGkAtoms::transitionsProperty, aProperty); +} + +template +AnimationsOrTransitions* +mozilla::HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty) +{ + AnimationsOrTransitions* animations = + static_cast(aContent->GetProperty(aAnimationProperty)); + if (animations) { + bool propertyMatches = animations->HasAnimationOfProperty(aProperty); + if (propertyMatches) { + return animations; + } + } + + return nullptr; +} + +template ElementAnimations* +mozilla::HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty); + +template ElementTransitions* +mozilla::HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty); + + +bool +nsLayoutUtils::HasAnimations(nsIContent* aContent, + nsCSSProperty aProperty) +{ + if (!aContent->MayHaveAnimations()) + return false; + return HasAnimationOrTransition + (aContent, nsGkAtoms::animationsProperty, aProperty) || + HasAnimationOrTransition + (aContent, nsGkAtoms::transitionsProperty, aProperty); +} + +static gfxSize +GetScaleForValue(const nsStyleAnimation::Value& aValue, + nsIFrame* aFrame) +{ + if (!aFrame) { + NS_WARNING("No frame."); + return gfxSize(); + } + if (aValue.GetUnit() != nsStyleAnimation::eUnit_Transform) { + NS_WARNING("Expected a transform."); + return gfxSize(); + } + + nsCSSValueSharedList* list = aValue.GetCSSValueSharedListValue(); + MOZ_ASSERT(list->mHead); + + if (list->mHead->mValue.GetUnit() == eCSSUnit_None) { + // There is an animation, but no actual transform yet. + return gfxSize(); + } + + nsRect frameBounds = aFrame->GetRect(); + bool dontCare; + gfx3DMatrix transform = nsStyleTransformMatrix::ReadTransforms( + list->mHead, + aFrame->StyleContext(), + aFrame->PresContext(), dontCare, frameBounds, + aFrame->PresContext()->AppUnitsPerDevPixel()); + + gfxMatrix transform2d; + bool canDraw2D = transform.CanDraw2D(&transform2d); + if (!canDraw2D) { + return gfxSize(); + } + + return transform2d.ScaleFactors(true); +} + +float +GetSuitableScale(float aMaxScale, float aMinScale) +{ + // If the minimum scale >= 1.0f, use it; if the maximum <= 1.0f, use it; + // otherwise use 1.0f. + if (aMinScale >= 1.0f) { + return aMinScale; + } + else if (aMaxScale <= 1.0f) { + return aMaxScale; + } + + return 1.0f; +} + +gfxSize +nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent) +{ + gfxSize maxScale(1.0f, 1.0f); + gfxSize minScale(1.0f, 1.0f); + + ElementAnimations* animations = HasAnimationOrTransitionForCompositor + (aContent, nsGkAtoms::animationsProperty, eCSSProperty_transform); + if (animations) { + for (uint32_t animIdx = animations->mAnimations.Length(); animIdx-- != 0; ) { + mozilla::StyleAnimation& anim = animations->mAnimations[animIdx]; + for (uint32_t propIdx = anim.mProperties.Length(); propIdx-- != 0; ) { + AnimationProperty& prop = anim.mProperties[propIdx]; + if (prop.mProperty == eCSSProperty_transform) { + for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) { + AnimationPropertySegment& segment = prop.mSegments[segIdx]; + gfxSize from = GetScaleForValue(segment.mFromValue, + aContent->GetPrimaryFrame()); + maxScale.width = std::max(maxScale.width, from.width); + maxScale.height = std::max(maxScale.height, from.height); + minScale.width = std::min(minScale.width, from.width); + minScale.height = std::min(minScale.height, from.height); + gfxSize to = GetScaleForValue(segment.mToValue, + aContent->GetPrimaryFrame()); + maxScale.width = std::max(maxScale.width, to.width); + maxScale.height = std::max(maxScale.height, to.height); + minScale.width = std::min(minScale.width, to.width); + minScale.height = std::min(minScale.height, to.height); + } + } + } + } + } + + ElementTransitions* transitions = HasAnimationOrTransitionForCompositor + (aContent, nsGkAtoms::transitionsProperty, eCSSProperty_transform); + if (transitions) { + for (uint32_t i = 0, i_end = transitions->mPropertyTransitions.Length(); + i < i_end; ++i){ + ElementPropertyTransition &pt = transitions->mPropertyTransitions[i]; + if (pt.IsRemovedSentinel()) { + continue; + } + MOZ_ASSERT(pt.mProperties.Length() == 1, + "Should have one animation property for a transition"); + MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1, + "Animation property should have one segment for a transition"); + const AnimationPropertySegment& segment = pt.mProperties[0].mSegments[0]; + + if (pt.mProperties[0].mProperty == eCSSProperty_transform) { + gfxSize start = GetScaleForValue(segment.mFromValue, + aContent->GetPrimaryFrame()); + maxScale.width = std::max(maxScale.width, start.width); + maxScale.height = std::max(maxScale.height, start.height); + minScale.width = std::min(minScale.width, start.width); + minScale.height = std::min(minScale.height, start.height); + gfxSize end = GetScaleForValue(segment.mToValue, + aContent->GetPrimaryFrame()); + maxScale.width = std::max(maxScale.width, end.width); + maxScale.height = std::max(maxScale.height, end.height); + minScale.width = std::min(minScale.width, end.width); + minScale.height = std::min(minScale.height, end.height); + } + } + } + + return gfxSize(GetSuitableScale(maxScale.width, minScale.width), + GetSuitableScale(maxScale.height, minScale.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::UseBackgroundNearestFiltering() +{ + static bool sUseBackgroundNearestFilteringEnabled; + static bool sUseBackgroundNearestFilteringPrefInitialised = false; + + if (!sUseBackgroundNearestFilteringPrefInitialised) { + sUseBackgroundNearestFilteringPrefInitialised = true; + sUseBackgroundNearestFilteringEnabled = + Preferences::GetBool("gfx.filter.nearest.force-enabled", false); + } + + return sUseBackgroundNearestFilteringEnabled; +} + +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::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::IsTextAlignTrueValueEnabled() +{ + static bool sTextAlignTrueValueEnabled; + static bool sTextAlignTrueValueEnabledPrefCached = false; + + if (!sTextAlignTrueValueEnabledPrefCached) { + sTextAlignTrueValueEnabledPrefCached = true; + Preferences::AddBoolVarCache(&sTextAlignTrueValueEnabled, + TEXT_ALIGN_TRUE_ENABLED_PREF_NAME, + false); + } + + return sTextAlignTrueValueEnabled; +} + +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) +{ + NS_ABORT_IF_FALSE(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; + } +} + +nsIScrollableFrame* +nsLayoutUtils::FindScrollableFrameFor(ViewID aId) +{ + nsIContent* content = FindContentFor(aId); + if (!content) { + return nullptr; + } + + nsIFrame* scrolledFrame = content->GetPrimaryFrame(); + if (scrolledFrame && content->OwnerDoc()->GetRootElement() == content) { + // The content is the root element of a subdocument, so return the root scrollable + // for the subdocument. + scrolledFrame = scrolledFrame->PresContext()->PresShell()->GetRootScrollFrame(); + } + return scrolledFrame ? scrolledFrame->GetScrollTargetFrame() : nullptr; +} + +bool +nsLayoutUtils::GetDisplayPort(nsIContent* aContent, nsRect *aResult) +{ + DisplayPortPropertyData* rectData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPort)); + DisplayPortMarginsPropertyData* marginsData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); + if (!rectData && !marginsData) { + return false; + } + + if (aResult) { + if (rectData && marginsData) { + // choose margins if equal priority + if (rectData->mPriority > marginsData->mPriority) { + marginsData = nullptr; + } else { + rectData = nullptr; + } + } + + if (rectData) { + *aResult = rectData->mRect; + } else { + nsRect* baseData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortBase)); + nsRect base; + if (baseData) { + base = *baseData; + } + + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (frame) { + bool isRoot = false; + if (aContent->OwnerDoc()->GetRootElement() == aContent) { + // We want the scroll frame, the root scroll frame differs from all + // others in that the primary frame is not the scroll frame. + frame = frame->PresContext()->PresShell()->GetRootScrollFrame(); + isRoot = true; + } + + // first convert the base rect to layer pixels + nsPresContext* presContext = frame->PresContext(); + int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); + gfxSize res = presContext->PresShell()->GetCumulativeResolution(); + gfxSize 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. + gfxSize localRes = presContext->PresShell()->GetResolution(); + parentRes.width /= localRes.width; + parentRes.height /= localRes.height; + } + LayerRect rect; + rect.x = parentRes.width * NSAppUnitsToFloatPixels(base.x, auPerDevPixel); + rect.y = parentRes.height * NSAppUnitsToFloatPixels(base.y, auPerDevPixel); + rect.width = + parentRes.width * NSAppUnitsToFloatPixels(base.width, auPerDevPixel); + rect.height = + parentRes.height * NSAppUnitsToFloatPixels(base.height, auPerDevPixel); + + rect.Inflate(marginsData->mMargins); + + nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame(); + nsPoint scrollPos( + scrollableFrame ? scrollableFrame->GetScrollPosition() : nsPoint(0,0)); + if (marginsData->mAlignmentX > 0 || marginsData->mAlignmentY > 0) { + // Avoid division by zero. + if (marginsData->mAlignmentX == 0) { + marginsData->mAlignmentX = 1; + } + if (marginsData->mAlignmentY == 0) { + marginsData->mAlignmentY = 1; + } + + LayerPoint scrollPosLayer( + res.width * NSAppUnitsToFloatPixels(scrollPos.x, auPerDevPixel), + res.height * NSAppUnitsToFloatPixels(scrollPos.y, auPerDevPixel)); + rect += scrollPosLayer; + + // Inflate the rectangle by 1 so that we always push to the next tile + // boundary. This is desirable to stop from having a rectangle with a + // moving origin occasionally being smaller when it coincidentally lines + // up to tile boundaries. + rect.Inflate(1); + + float left = + marginsData->mAlignmentX * floor(rect.x / marginsData->mAlignmentX); + float top = + marginsData->mAlignmentY * floor(rect.y / marginsData->mAlignmentY); + float right = + marginsData->mAlignmentX * ceil(rect.XMost() / marginsData->mAlignmentX); + float bottom = + marginsData->mAlignmentY * ceil(rect.YMost() / marginsData->mAlignmentY); + rect = LayerRect(left, top, right - left, bottom - top); + rect -= scrollPosLayer; + } + + nsRect result; + result.x = NSFloatPixelsToAppUnits(rect.x / res.width, auPerDevPixel); + result.y = NSFloatPixelsToAppUnits(rect.y / res.height, auPerDevPixel); + result.width = + NSFloatPixelsToAppUnits(rect.width / res.width, auPerDevPixel); + result.height = + NSFloatPixelsToAppUnits(rect.height / res.height, auPerDevPixel); + + // Finally, clamp the display port to the expanded scrollable rect. + nsRect expandedScrollableRect = CalculateExpandedScrollableRect(frame); + result = expandedScrollableRect.Intersect(result + scrollPos) - scrollPos; + + *aResult = result; + } + } + } + + return true; +} + +void +nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent, + nsIPresShell* aPresShell, + const LayerMargin& aMargins, + uint32_t aAlignmentX, + uint32_t aAlignmentY, + uint32_t aPriority, + RepaintMode aRepaintMode) +{ + DisplayPortMarginsPropertyData* currentData = + static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortMargins)); + if (currentData && currentData->mPriority > aPriority) { + return; + } + + aContent->SetProperty(nsGkAtoms::DisplayPortMargins, + new DisplayPortMarginsPropertyData( + aMargins, aAlignmentX, aAlignmentY, aPriority), + nsINode::DeleteProperty); + + nsIFrame* rootScrollFrame = aPresShell->GetRootScrollFrame(); + if (rootScrollFrame && aContent == rootScrollFrame->GetContent()) { + // We are setting a root displayport for a document. + // The pres shell needs a special flag set. + aPresShell->SetIgnoreViewportScrolling(true); + } + + if (aRepaintMode == RepaintMode::Repaint) { + nsIFrame* rootFrame = aPresShell->FrameManager()->GetRootFrame(); + if (rootFrame) { + rootFrame->SchedulePaint(); + } + } +} + +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) +{ + void* property = aContent->GetProperty(nsGkAtoms::CriticalDisplayPort); + if (!property) { + return false; + } + + if (aResult) { + *aResult = *static_cast(property); + } + return true; +} + +nsIFrame* +nsLayoutUtils::LastContinuationWithChild(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + aFrame = aFrame->LastContinuation(); + while (!aFrame->GetFirstPrincipalChild() && + aFrame->GetPrevContinuation()) { + aFrame = aFrame->GetPrevContinuation(); + } + return aFrame; +} + +/** + * GetFirstChildFrame returns the first "real" child frame of a + * given frame. It will descend down into pseudo-frames (unless the + * pseudo-frame is the :before generated frame). + * @param aFrame the frame + * @param aFrame the frame's content node + */ +static nsIFrame* +GetFirstChildFrame(nsIFrame* aFrame, + nsIContent* aContent) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + + // Get the first child frame + nsIFrame* childFrame = aFrame->GetFirstPrincipalChild(); + + // If the child frame is a pseudo-frame, then return its first child. + // Note that the frame we create for the generated content is also a + // pseudo-frame and so don't drill down in that case + if (childFrame && + childFrame->IsPseudoFrame(aContent) && + !childFrame->IsGeneratedContentFrame()) { + return GetFirstChildFrame(childFrame, aContent); + } + + return childFrame; +} + +/** + * GetLastChildFrame returns the last "real" child frame of a + * given frame. It will descend down into pseudo-frames (unless the + * pseudo-frame is the :after generated frame). + * @param aFrame the frame + * @param aFrame the frame's content node + */ +static nsIFrame* +GetLastChildFrame(nsIFrame* aFrame, + nsIContent* aContent) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + + // Get the last continuation frame that's a parent + nsIFrame* lastParentContinuation = + nsLayoutUtils::LastContinuationWithChild(aFrame); + nsIFrame* lastChildFrame = + lastParentContinuation->GetLastChild(nsIFrame::kPrincipalList); + if (lastChildFrame) { + // Get the frame's first continuation. This matters in case the frame has + // been continued across multiple lines or split by BiDi resolution. + lastChildFrame = lastChildFrame->FirstContinuation(); + + // If the last child frame is a pseudo-frame, then return its last child. + // Note that the frame we create for the generated content is also a + // pseudo-frame and so don't drill down in that case + if (lastChildFrame && + lastChildFrame->IsPseudoFrame(aContent) && + !lastChildFrame->IsGeneratedContentFrame()) { + return GetLastChildFrame(lastChildFrame, aContent); + } + + return lastChildFrame; + } + + return nullptr; +} + +//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 (NS_STYLE_DISPLAY_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->GetFirstChild(nsIFrame::kPopupList); + 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 (nsGkAtoms::tableCaptionFrame == childType) { + 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. + nsIFrame* 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::GetBeforeFrame(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + NS_ASSERTION(!aFrame->GetPrevContinuation(), + "aFrame must be first continuation"); + + nsIFrame* cif = aFrame->GetContentInsertionFrame(); + nsIFrame* firstFrame = GetFirstChildFrame(cif, aFrame->GetContent()); + + if (firstFrame && IsGeneratedContentFor(nullptr, firstFrame, + nsCSSPseudoElements::before)) { + return firstFrame; + } + + return nullptr; +} + +// static +nsIFrame* +nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "NULL frame pointer"); + + nsIFrame* cif = aFrame->GetContentInsertionFrame(); + nsIFrame* lastFrame = GetLastChildFrame(cif, aFrame->GetContent()); + + if (lastFrame && IsGeneratedContentFor(nullptr, lastFrame, + nsCSSPseudoElements::after)) { + return lastFrame; + } + + return nullptr; +} + +// static +nsIFrame* +nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, nsIAtom* aFrameType) +{ + for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { + if (frame->GetType() == aFrameType) { + return frame; + } + } + return nullptr; +} + +// static +nsIFrame* +nsLayoutUtils::GetStyleFrame(nsIFrame* aFrame) +{ + if (aFrame->GetType() == nsGkAtoms::tableOuterFrame) { + nsIFrame* inner = aFrame->GetFirstPrincipalChild(); + NS_ASSERTION(inner, "Outer table must have an inner"); + return inner; + } + + return aFrame; +} + +nsIFrame* +nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) +{ + nsIFrame *frame = aContent->GetPrimaryFrame(); + if (!frame) { + return nullptr; + } + + return nsLayoutUtils::GetStyleFrame(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()->Tag() == 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"); + + nsAutoTArray 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; + } + + nsAutoTArray 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"); + + nsAutoTArray 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; + } + + nsAutoTArray 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; +} + +/* 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); + // Make sure the layer is aware of any fixed position margins that have + // been set. + nsMargin fixedMargins = aPresContext->PresShell()->GetContentDocumentFixedPositionMargins(); + LayerMargin fixedLayerMargins(NSAppUnitsToFloatPixels(fixedMargins.top, factor) * + aContainerParameters.mYScale, + NSAppUnitsToFloatPixels(fixedMargins.right, factor) * + aContainerParameters.mXScale, + NSAppUnitsToFloatPixels(fixedMargins.bottom, factor) * + aContainerParameters.mYScale, + NSAppUnitsToFloatPixels(fixedMargins.left, factor) * + aContainerParameters.mXScale); + + if (aFixedPosFrame != aViewportFrame) { + const nsStylePosition* position = aFixedPosFrame->StylePosition(); + if (position->mOffset.GetRightUnit() != eStyleUnit_Auto) { + if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) { + anchor.x = anchorRect.x + anchorRect.width / 2.f; + } else { + anchor.x = anchorRect.XMost(); + } + } + if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto) { + if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) { + anchor.y = anchorRect.y + anchorRect.height / 2.f; + } else { + anchor.y = anchorRect.YMost(); + } + } + + // If the frame is auto-positioned on either axis, set the top/left layer + // margins to -1, to indicate to the compositor that this layer is + // unaffected by fixed margins. + if (position->mOffset.GetLeftUnit() == eStyleUnit_Auto && + position->mOffset.GetRightUnit() == eStyleUnit_Auto) { + fixedLayerMargins.left = -1; + } + if (position->mOffset.GetTopUnit() == eStyleUnit_Auto && + position->mOffset.GetBottomUnit() == eStyleUnit_Auto) { + fixedLayerMargins.top = -1; + } + } + + aLayer->SetFixedPositionAnchor(anchor); + aLayer->SetFixedPositionMargins(fixedLayerMargins); +} + +bool +nsLayoutUtils::ViewportHasDisplayPort(nsPresContext* aPresContext, nsRect* aDisplayPort) +{ + nsIFrame* rootScrollFrame = + aPresContext->PresShell()->GetRootScrollFrame(); + return rootScrollFrame && + nsLayoutUtils::GetDisplayPort(rootScrollFrame->GetContent(), aDisplayPort); +} + +bool +nsLayoutUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame, nsRect* aDisplayPort) +{ + // 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(), aDisplayPort); +} + +static nsIFrame* +GetAnimatedGeometryRootForFrame(nsIFrame* aFrame, + const nsIFrame* aStopAtAncestor) +{ + nsIFrame* f = aFrame; + nsIFrame* stickyFrame = nullptr; + while (f != aStopAtAncestor) { + if (nsLayoutUtils::IsPopup(f)) + break; + if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(f)) + break; + if (!f->GetParent() && + nsLayoutUtils::ViewportHasDisplayPort(f->PresContext())) { + // Viewport frames in a display port need to be animated geometry roots + // for background-attachment:fixed elements. + break; + } + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f); + if (!parent) + break; + nsIAtom* parentType = parent->GetType(); +#ifdef ANDROID + // Treat the slider thumb as being as an active scrolled root + // on mobile so that it can move without repainting. + if (parentType == nsGkAtoms::sliderFrame) + break; +#endif + // Sticky frames are active if their nearest scrollable frame + // is also active, just keep a record of sticky frames that we + // encounter for now. + if (f->StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY && + !stickyFrame) { + stickyFrame = f; + } + if (parentType == nsGkAtoms::scrollFrame) { + nsIScrollableFrame* sf = do_QueryFrame(parent); + if (sf->IsScrollingActive() && sf->GetScrolledFrame() == f) { + // If we found a sticky frame inside this active scroll frame, + // then use that. Otherwise use the scroll frame. + if (stickyFrame) { + return stickyFrame; + } + return f; + } else { + stickyFrame = nullptr; + } + } + // Fixed-pos frames are parented by the viewport frame, which has no parent + if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(f)) { + return f; + } + f = parent; + } + return f; +} + +nsIFrame* +nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) +{ + nsIFrame* f = aItem->Frame(); + if (aItem->GetType() == nsDisplayItem::TYPE_SCROLL_LAYER) { + nsDisplayScrollLayer* scrollLayerItem = + static_cast(aItem); + nsIFrame* scrolledFrame = scrollLayerItem->GetScrolledFrame(); + return GetAnimatedGeometryRootForFrame(scrolledFrame, + aBuilder->FindReferenceFrameFor(scrolledFrame)); + } + if (aItem->ShouldFixToViewport(aBuilder)) { + // Make its active scrolled root be the active scrolled root of + // the enclosing viewport, since it shouldn't be scrolled by scrolled + // frames in its document. InvalidateFixedBackgroundFramesFromList in + // nsGfxScrollFrame will not repaint this item when scrolling occurs. + nsIFrame* viewportFrame = + nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame); + NS_ASSERTION(viewportFrame, "no viewport???"); + return GetAnimatedGeometryRootForFrame(viewportFrame, + aBuilder->FindReferenceFrameFor(viewportFrame)); + } + return GetAnimatedGeometryRootForFrame(f, aItem->ReferenceFrame()); +} + +// 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) { + ScrollbarStyles ss = scrollableFrame->GetScrollbarStyles(); + if ((aFlags & SCROLLABLE_INCLUDE_HIDDEN) || + ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN || + ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) + return scrollableFrame; + } + } + return nullptr; +} + +// static +nsRect +nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame, + const nsRect& aScrolledFrameOverflowArea, + const nsSize& aScrollPortSize, + uint8_t aDirection) +{ + nscoord x1 = aScrolledFrameOverflowArea.x, + x2 = aScrolledFrameOverflowArea.XMost(), + y1 = aScrolledFrameOverflowArea.y, + y2 = aScrolledFrameOverflowArea.YMost(); + if (y1 < 0) { + y1 = 0; + } + if (aDirection != NS_STYLE_DIRECTION_RTL) { + 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; + } + return nsRect(x1, y1, x2 - x1, y2 - y1); +} + +//static +bool +nsLayoutUtils::HasPseudoStyle(nsIContent* aContent, + nsStyleContext* aStyleContext, + nsCSSPseudoElements::Type aPseudoElement, + nsPresContext* aPresContext) +{ + NS_PRECONDITION(aPresContext, "Must have a prescontext"); + + nsRefPtr 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->GetInternalNSEvent(); + if (!event) + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + return GetEventCoordinatesRelativeTo(event, aFrame); +} + +nsPoint +nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, + nsIFrame* aFrame) +{ + if (!aEvent || (aEvent->eventStructType != NS_MOUSE_EVENT && + aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT && + aEvent->eventStructType != NS_WHEEL_EVENT && + aEvent->eventStructType != NS_DRAG_EVENT && + aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT && + aEvent->eventStructType != NS_POINTER_EVENT && + aEvent->eventStructType != NS_GESTURENOTIFY_EVENT && + aEvent->eventStructType != NS_TOUCH_EVENT && + aEvent->eventStructType != NS_QUERY_CONTENT_EVENT)) + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + + return GetEventCoordinatesRelativeTo(aEvent, + LayoutDeviceIntPoint::ToUntyped(aEvent->AsGUIEvent()->refPoint), + aFrame); +} + +nsPoint +nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent, + const nsIntPoint aPoint, + nsIFrame* aFrame) +{ + if (!aFrame) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + nsIWidget* widget = aEvent->AsGUIEvent()->widget; + if (!widget) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame); +} + +nsPoint +nsLayoutUtils::GetEventCoordinatesRelativeTo(nsIWidget* aWidget, + const nsIntPoint 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)); + return pt - view->ViewToWidgetOffset(); + } + } + + /* If we walk up the frame tree and discover that any of the frames are + * transformed, we need to do extra work to convert from the global + * space to the local space. + */ + 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.ConvertAppUnits(rootAPD, localAPD); + + /* 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; +} + +gfx3DMatrix +nsLayoutUtils::ChangeMatrixBasis(const gfxPoint3D &aOrigin, + const gfx3DMatrix &aMatrix) +{ + gfx3DMatrix result = aMatrix; + + /* Translate to the origin before aMatrix */ + result.Translate(-aOrigin); + + /* Translate back into position after aMatrix */ + result.TranslatePost(aOrigin); + + return result; +} + +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; +} + +// Helper for RoundedRectIntersectsRect. +static bool +CheckCorner(nscoord aXOffset, nscoord aYOffset, + nscoord aXRadius, nscoord aYRadius) +{ + NS_ABORT_IF_FALSE(aXOffset > 0 && aYOffset > 0, + "must not pass nonpositives to CheckCorner"); + NS_ABORT_IF_FALSE(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::MatrixTransformRectOut(const nsRect &aBounds, + const gfx3DMatrix &aMatrix, float aFactor) +{ + nsRect outside = aBounds; + outside.ScaleRoundOut(1/aFactor); + gfxRect image = aMatrix.TransformBounds(gfxRect(outside.x, + outside.y, + outside.width, + outside.height)); + return RoundGfxRectToAppRect(image, aFactor); +} + +nsRect +nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds, + const gfx3DMatrix &aMatrix, float aFactor) +{ + gfxRect image = aMatrix.TransformBounds(gfxRect(NSAppUnitsToDoublePixels(aBounds.x, aFactor), + NSAppUnitsToDoublePixels(aBounds.y, aFactor), + NSAppUnitsToDoublePixels(aBounds.width, aFactor), + NSAppUnitsToDoublePixels(aBounds.height, aFactor))); + + return RoundGfxRectToAppRect(image, aFactor); +} + +nsPoint +nsLayoutUtils::MatrixTransformPoint(const nsPoint &aPoint, + const gfx3DMatrix &aMatrix, float aFactor) +{ + gfxPoint image = aMatrix.Transform(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor), + NSAppUnitsToFloatPixels(aPoint.y, aFactor))); + return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor), + NSFloatPixelsToAppUnits(float(image.y), aFactor)); +} + +gfx3DMatrix +nsLayoutUtils::GetTransformToAncestor(nsIFrame *aFrame, const nsIFrame *aAncestor) +{ + nsIFrame* parent; + gfx3DMatrix ctm; + if (aFrame == aAncestor) { + return ctm; + } + ctm = aFrame->GetTransformMatrix(aAncestor, &parent); + while (parent && parent != aAncestor) { + if (!parent->Preserves3DChildren()) { + ctm.ProjectTo2D(); + } + ctm = ctm * parent->GetTransformMatrix(aAncestor, &parent); + } + return ctm; +} + +static nsIFrame* +FindNearestCommonAncestorFrame(nsIFrame* aFrame1, nsIFrame* aFrame2) +{ + nsAutoTArray ancestors1; + nsAutoTArray 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; + } + gfx3DMatrix downToDest = GetTransformToAncestor(aToFrame, nearestCommonAncestor); + if (downToDest.IsSingular()) { + return NONINVERTIBLE_TRANSFORM; + } + downToDest.Invert(); + gfx3DMatrix upToAncestor = GetTransformToAncestor(aFromFrame, nearestCommonAncestor); + CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame( + double(nsPresContext::AppUnitsPerCSSPixel())/ + aFromFrame->PresContext()->AppUnitsPerDevPixel()); + CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame( + double(nsPresContext::AppUnitsPerCSSPixel())/ + aToFrame->PresContext()->AppUnitsPerDevPixel()); + for (uint32_t i = 0; i < aPointCount; ++i) { + LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame; + gfxPoint toDevPixels = downToDest.ProjectPoint( + upToAncestor.ProjectPoint(gfxPoint(devPixels.x, devPixels.y))); + // 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; +} + +bool +nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame, + gfx3DMatrix* aTransform) +{ + // FIXME/bug 796690: we can sometimes compute a transform in these + // cases, it just increases complexity considerably. Punt for now. + if (aFrame->Preserves3DChildren() || 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, nsDisplayListBuilder::OTHER, + false/*don't build caret*/); + nsDisplayList list; + nsDisplayTransform* item = + new (&builder) nsDisplayTransform(&builder, aFrame, &list); + + *aTransform = + item->GetTransform(); + item->~nsDisplayTransform(); + + return true; +} + +static bool +TransformGfxPointFromAncestor(nsIFrame *aFrame, + const gfxPoint &aPoint, + nsIFrame *aAncestor, + gfxPoint* aOut) +{ + gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect childBounds = aFrame->GetVisualOverflowRectRelativeToSelf(); + gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + return ctm.UntransformPoint(aPoint, childGfxBounds, aOut); +} + +static gfxRect +TransformGfxRectToAncestor(nsIFrame *aFrame, + const gfxRect &aRect, + const nsIFrame *aAncestor, + bool* aPreservesAxisAlignedRectangles = nullptr) +{ + gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); + if (aPreservesAxisAlignedRectangles) { + gfxMatrix matrix2d; + *aPreservesAxisAlignedRectangles = + ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles(); + } + return ctm.TransformBounds(aRect); +} + +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(); + gfxPoint 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 */) +{ + SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); + + float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + gfxRect result; + + if (text) { + result = text->TransformFrameRectFromTextChild(aRect, aFrame); + result = TransformGfxRectToAncestor(text, result, aAncestor); + // 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 = gfxRect(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), + NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); + result = TransformGfxRectToAncestor(aFrame, result, aAncestor, aPreservesAxisAlignedRectangles); + } + + 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 nsIntPoint GetWidgetOffset(nsIWidget* aWidget, nsIWidget*& aRootWidget) { + nsIntPoint offset(0, 0); + nsIWidget* parent = aWidget->GetParent(); + while (parent) { + nsIntRect bounds; + aWidget->GetBounds(bounds); + offset += bounds.TopLeft(); + aWidget = parent; + parent = aWidget->GetParent(); + } + aRootWidget = aWidget; + return offset; +} + +nsPoint +nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext, + nsIWidget* aWidget, nsIntPoint aPt, + nsView* aView) +{ + nsPoint viewOffset; + nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); + if (!viewWidget) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + nsIWidget* fromRoot; + nsIntPoint fromOffset = GetWidgetOffset(aWidget, fromRoot); + nsIWidget* toRoot; + nsIntPoint toOffset = GetWidgetOffset(viewWidget, toRoot); + + nsIntPoint widgetPoint; + if (fromRoot == toRoot) { + widgetPoint = aPt + fromOffset - toOffset; + } else { + nsIntPoint screenPoint = aWidget->WidgetToScreenOffset(); + widgetPoint = aPt + screenPoint - viewWidget->WidgetToScreenOffset(); + } + + nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x), + aPresContext->DevPixelsToAppUnits(widgetPoint.y)); + return widgetAppUnits - viewOffset; +} + +// Combine aNewBreakType with aOrigBreakType, but limit the break types +// to NS_STYLE_CLEAR_LEFT, RIGHT, BOTH. +uint8_t +nsLayoutUtils::CombineBreakType(uint8_t aOrigBreakType, + uint8_t aNewBreakType) +{ + uint8_t breakType = aOrigBreakType; + switch(breakType) { + case NS_STYLE_CLEAR_LEFT: + if (NS_STYLE_CLEAR_RIGHT == aNewBreakType || + NS_STYLE_CLEAR_BOTH == aNewBreakType) { + breakType = NS_STYLE_CLEAR_BOTH; + } + break; + case NS_STYLE_CLEAR_RIGHT: + if (NS_STYLE_CLEAR_LEFT == aNewBreakType || + NS_STYLE_CLEAR_BOTH == aNewBreakType) { + breakType = NS_STYLE_CLEAR_BOTH; + } + break; + case NS_STYLE_CLEAR_NONE: + if (NS_STYLE_CLEAR_LEFT == aNewBreakType || + NS_STYLE_CLEAR_RIGHT == aNewBreakType || + NS_STYLE_CLEAR_BOTH == aNewBreakType) { + breakType = aNewBreakType; + } + } + return breakType; +} + +#ifdef MOZ_DUMP_PAINTING +#include + +static bool gDumpEventList = false; +int gPaintCount = 0; +#endif + +nsresult +nsLayoutUtils::GetRemoteContentIds(nsIFrame* aFrame, + const nsRect& aTarget, + nsTArray &aOutIDs, + bool aIgnoreRootScrollFrame) +{ + nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::EVENT_DELIVERY, + false); + nsDisplayList list; + + if (aIgnoreRootScrollFrame) { + nsIFrame* rootScrollFrame = + aFrame->PresContext()->PresShell()->GetRootScrollFrame(); + if (rootScrollFrame) { + builder.SetIgnoreScrollFrame(rootScrollFrame); + } + } + + builder.EnterPresShell(aFrame, aTarget); + aFrame->BuildDisplayListForStackingContext(&builder, aTarget, &list); + builder.LeavePresShell(aFrame, aTarget); + + nsAutoTArray outFrames; + nsDisplayItem::HitTestState hitTestState(&aOutIDs); + list.HitTest(&builder, aTarget, &hitTestState, &outFrames); + list.DeleteAll(); + + return NS_OK; +} + +nsIFrame* +nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt, uint32_t aFlags) +{ + PROFILER_LABEL("nsLayoutUtils", "GetFrameForPoint"); + nsresult rv; + nsAutoTArray 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"); + nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::EVENT_DELIVERY, + false); + nsDisplayList list; + nsRect target(aRect); + + 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, target); + aFrame->BuildDisplayListForStackingContext(&builder, target, &list); + builder.LeavePresShell(aFrame, target); + +#ifdef MOZ_DUMP_PAINTING + if (gDumpEventList) { + fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y); + nsFrame::PrintDisplayList(&builder, list); + } +#endif + + nsDisplayItem::HitTestState hitTestState; + list.HitTest(&builder, target, &hitTestState, &aOutFrames); + list.DeleteAll(); + return NS_OK; +} + +// This function is only used on B2G, and some compilers complain about +// unused static functions, so we need to #ifdef it. +#ifdef MOZ_WIDGET_GONK +// aScrollFrame and aScrollFrameAsScrollable must be non-nullptr +static FrameMetrics +CalculateFrameMetricsForDisplayPort(nsIFrame* aScrollFrame, + nsIScrollableFrame* aScrollFrameAsScrollable) { + // Calculate the metrics necessary for calculating the displayport. + // This code has a lot in common with the code in RecordFrameMetrics(); + // we may want to refactor this at some point. + FrameMetrics metrics; + nsPresContext* presContext = aScrollFrame->PresContext(); + nsIPresShell* presShell = presContext->PresShell(); + CSSToLayoutDeviceScale deviceScale(float(nsPresContext::AppUnitsPerCSSPixel()) + / presContext->AppUnitsPerDevPixel()); + ParentLayerToLayerScale resolution(presShell->GetResolution().width); + LayoutDeviceToLayerScale cumulativeResolution(presShell->GetCumulativeResolution().width); + + metrics.mDevPixelsPerCSSPixel = deviceScale; + metrics.mResolution = resolution; + metrics.mCumulativeResolution = cumulativeResolution; + metrics.SetZoom(deviceScale * cumulativeResolution * LayerToScreenScale(1)); + + // Only the size of the composition bounds is relevant to the + // displayport calculation, not its origin. + nsSize compositionSize = nsLayoutUtils::CalculateCompositionSizeForFrame(aScrollFrame); + metrics.mCompositionBounds + = RoundedToInt(LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize), + presContext->AppUnitsPerDevPixel()) + * (cumulativeResolution / resolution)); + + // This function is used for setting a display port for subframes, so + // aScrollFrame will not be the root content document's root scroll frame. + metrics.SetRootCompositionSize( + nsLayoutUtils::CalculateRootCompositionSize(aScrollFrame, false, metrics)); + + metrics.SetScrollOffset(CSSPoint::FromAppUnits( + aScrollFrameAsScrollable->GetScrollPosition())); + + metrics.mScrollableRect = CSSRect::FromAppUnits( + nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrameAsScrollable, nullptr)); + + return metrics; +} +#endif + +bool +nsLayoutUtils::GetOrMaybeCreateDisplayPort(nsDisplayListBuilder& aBuilder, + nsIFrame* aScrollFrame, + nsRect aDisplayPortBase, + nsRect* aOutDisplayport) { + nsIContent* content = aScrollFrame->GetContent(); + nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollFrame); + if (!content || !scrollableFrame) { + return false; + } + + // Set the base rect. Note that this will not influence 'haveDisplayPort', + // which is based on either the whole rect or margins being set, but it + // will affect what is returned in 'aOutDisplayPort' if margins are set. + SetDisplayPortBase(content, aDisplayPortBase); + + bool haveDisplayPort = GetDisplayPort(content, aOutDisplayport); + +#ifdef MOZ_WIDGET_GONK + // On B2G, 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. + // Note: we only do this in processes where we do subframe scrolling to + // begin with (i.e., not in the parent process on B2G). + if (WantSubAPZC() && + !aBuilder.HaveScrollableDisplayPort() && + scrollableFrame->WantAsyncScroll()) { + + // If we don't already have a displayport, calculate and set one. + if (!haveDisplayPort) { + FrameMetrics metrics = CalculateFrameMetricsForDisplayPort(aScrollFrame, scrollableFrame); + LayerMargin displayportMargins = AsyncPanZoomController::CalculatePendingDisplayPort( + metrics, ScreenPoint(0.0f, 0.0f), 0.0); + nsIPresShell* presShell = aScrollFrame->PresContext()->GetPresShell(); + gfx::IntSize alignment = gfxPrefs::LayersTilesEnabled() + ? gfx::IntSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) : + gfx::IntSize(0, 0); + nsLayoutUtils::SetDisplayPortMargins( + content, presShell, displayportMargins, alignment.width, + alignment.height, 0, nsLayoutUtils::RepaintMode::DoNotRepaint); + haveDisplayPort = GetDisplayPort(content, aOutDisplayport); + NS_ASSERTION(haveDisplayPort, "should have a displayport after having just set it"); + } + + // Record that the we now have a scrollable display port. + aBuilder.SetHaveScrollableDisplayPort(); + } +#endif + + return haveDisplayPort; +} + +nsresult +nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFrame, + const nsRegion& aDirtyRegion, nscolor aBackstop, + uint32_t aFlags) +{ + PROFILER_LABEL("nsLayoutUtils","PaintFrame"); + if (aFlags & PAINT_WIDGET_LAYERS) { + nsView* view = aFrame->GetView(); + if (!(view && view->GetWidget() && GetDisplayRootFrame(aFrame) == aFrame)) { + aFlags &= ~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; + } + + nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::PAINTING, + !(aFlags & PAINT_HIDE_CARET)); + + nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); + bool usingDisplayPort = false; + nsRect displayport; + if (rootScrollFrame && !aFrame->GetParent()) { + nsRect displayportBase( + nsPoint(0,0), + nsLayoutUtils::CalculateCompositionSizeForFrame(rootScrollFrame)); + usingDisplayPort = nsLayoutUtils::GetOrMaybeCreateDisplayPort( + builder, rootScrollFrame, displayportBase, &displayport); + } + + nsRegion visibleRegion; + if (aFlags & 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. + if (!usingDisplayPort) { + visibleRegion = aFrame->GetVisualOverflowRectRelativeToSelf(); + } else { + visibleRegion = displayport; + } + } else { + visibleRegion = aDirtyRegion; + } + + // If we're going to display something different from what we'd normally + // paint in a window then we will flush out any retained layer trees before + // *and after* we draw. + bool willFlushRetainedLayers = (aFlags & PAINT_HIDE_CARET) != 0; + + nsDisplayList list; + if (aFlags & PAINT_IN_TRANSFORM) { + builder.SetInTransform(true); + } + if (aFlags & PAINT_SYNC_DECODE_IMAGES) { + builder.SetSyncDecodeImages(true); + } + if (aFlags & (PAINT_WIDGET_LAYERS | PAINT_TO_WINDOW)) { + builder.SetPaintingToWindow(true); + } + if (aFlags & PAINT_IGNORE_SUPPRESSION) { + builder.IgnorePaintSuppression(); + } + // Windowed plugins aren't allowed in popups + if ((aFlags & PAINT_WIDGET_LAYERS) && + !willFlushRetainedLayers && + !(aFlags & 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 & 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) { + aRenderingContext->Translate(pos); + } + } + 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)); + } + } + + nsRect dirtyRect = visibleRegion.GetBounds(); + builder.EnterPresShell(aFrame, dirtyRect); + { + PROFILER_LABEL("nsLayoutUtils","PaintFrame::BuildDisplayList"); + aFrame->BuildDisplayListForStackingContext(&builder, dirtyRect, &list); + } + const bool paintAllContinuations = aFlags & PAINT_ALL_CONTINUATIONS; + NS_ASSERTION(!paintAllContinuations || !aFrame->GetPrevContinuation(), + "If painting all continuations, the frame must be " + "first-continuation"); + + nsIAtom* frameType = aFrame->GetType(); + + if (paintAllContinuations) { + nsIFrame* currentFrame = aFrame; + while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) { + PROFILER_LABEL("nsLayoutUtils","PaintFrame::ContinuationsBuildDisplayList"); + nsRect frameDirty = dirtyRect - builder.ToReferenceFrame(currentFrame); + currentFrame->BuildDisplayListForStackingContext(&builder, + frameDirty, &list); + } + } + + // 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()); + 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()); + presShell->AddCanvasBackgroundColorItem( + builder, list, aFrame, canvasArea, aBackstop); + + // If the passed in backstop color makes us draw something different from + // normal, we need to flush layers. + if ((aFlags & PAINT_WIDGET_LAYERS) && !willFlushRetainedLayers) { + nsView* view = aFrame->GetView(); + if (view) { + nscolor backstop = presShell->ComputeBackstopColor(view); + // The PresShell's canvas background color doesn't get updated until + // EnterPresShell, so this check has to be done after that. + nscolor canvasColor = presShell->GetCanvasBackground(); + if (NS_ComposeColors(aBackstop, canvasColor) != + NS_ComposeColors(backstop, canvasColor)) { + willFlushRetainedLayers = true; + } + } + } + } + + builder.LeavePresShell(aFrame, dirtyRect); + + if (builder.GetHadToIgnorePaintSuppression()) { + willFlushRetainedLayers = true; + } + +#ifdef MOZ_DUMP_PAINTING + FILE* savedDumpFile = gfxUtils::sDumpPaintFile; + if (gfxUtils::sDumpPaintList || gfxUtils::sDumpPainting) { + if (gfxUtils::sDumpPaintingToFile) { + nsCString string("dump-"); + string.AppendInt(gPaintCount); + string.Append(".html"); + gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w"); + } else { + gfxUtils::sDumpPaintFile = stderr; + } + if (gfxUtils::sDumpPaintingToFile) { + fprintf_stderr(gfxUtils::sDumpPaintFile, ""); + } + fprintf_stderr(gfxUtils::sDumpPaintFile, "Painting --- before optimization (dirty %d,%d,%d,%d):\n", + dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); + nsFrame::PrintDisplayList(&builder, list, gfxUtils::sDumpPaintFile, gfxUtils::sDumpPaintingToFile); + if (gfxUtils::sDumpPaintingToFile) { + fprintf_stderr(gfxUtils::sDumpPaintFile, ""); + } + fprintf_stderr(gfxUtils::sDumpPaintFile, "Painting --- after optimization:\n"); + nsFrame::PrintDisplayList(&builder, list, gfxUtils::sDumpPaintFile, gfxUtils::sDumpPaintingToFile); + + fprintf_stderr(gfxUtils::sDumpPaintFile, "Painting --- retained layer tree:\n"); + nsIWidget* widget = aFrame->GetNearestWidget(); + if (widget) { + nsRefPtr layerManager = widget->GetLayerManager(); + if (layerManager) { + FrameLayerBuilder::DumpRetainedLayerTree(layerManager, gfxUtils::sDumpPaintFile, + gfxUtils::sDumpPaintingToFile); + } + } + if (gfxUtils::sDumpPaintingToFile) { + fprintf(gfxUtils::sDumpPaintFile, ""); + fclose(gfxUtils::sDumpPaintFile); + } + gfxUtils::sDumpPaintFile = savedDumpFile; + gPaintCount++; + } +#endif + + // Update the widget's opaque region information. This sets + // glass boundaries on Windows. Also set up plugin clip regions and bounds. + if ((aFlags & PAINT_WIDGET_LAYERS) && + !willFlushRetainedLayers && + !(aFlags & PAINT_DOCUMENT_RELATIVE)) { + nsIWidget *widget = aFrame->GetNearestWidget(); + if (widget) { + nsRegion excludedRegion = builder.GetExcludedGlassRegion(); + excludedRegion.Sub(excludedRegion, visibleRegion); + nsIntRegion windowRegion(excludedRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel())); + widget->UpdateOpaqueRegion(windowRegion); + } + } + + if (builder.WillComputePluginGeometry()) { + nsRefPtr layerManager; + nsIWidget* widget = aFrame->GetNearestWidget(); + if (widget) { + layerManager = widget->GetLayerManager(); + } + + 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(nsRenderingContext* aRendContext, + 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 = aRendContext->GetWidth(aText, aIndex); + 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 = aRendContext->GetWidth(aText, inx); + + int32_t fullWidth = aBaseWidth + textWidth; + if (fullWidth == aCursorPos) { + aTextWidth = textWidth; + aIndex = inx; + return true; + } else if (aCursorPos < fullWidth) { + aTextWidth = aBaseWidth; + if (BinarySearchForPosition(aRendContext, aText, aBaseWidth, aBaseInx, aStartInx, inx, aCursorPos, aIndex, aTextWidth)) { + return true; + } + } else { + aTextWidth = fullWidth; + if (BinarySearchForPosition(aRendContext, 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::tableOuter) { + AddBoxesForFrame(aFrame->GetFirstPrincipalChild(), aCallback); + nsIFrame* kid = aFrame->GetFirstChild(nsIFrame::kCaptionList); + if (kid) { + AddBoxesForFrame(kid, aCallback); + } + } else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock || + pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock || + pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) { + for (nsIFrame* kid = aFrame->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { + 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::tableOuter) { + nsIFrame* f = GetFirstNonAnonymousFrame(aFrame->GetFirstPrincipalChild()); + if (f) { + return f; + } + nsIFrame* kid = aFrame->GetFirstChild(nsIFrame::kCaptionList); + 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->GetFirstPrincipalChild(); kid; kid = kid->GetNextSibling()) { + 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) MOZ_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); + } +}; + +void +nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo, + RectCallback* aCallback, uint32_t aFlags) +{ + BoxToRect converter(aRelativeTo, aCallback, 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) { + nsRefPtr 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; +} + +nsresult +nsLayoutUtils::GetFontMetricsForFrame(const nsIFrame* aFrame, + nsFontMetrics** aFontMetrics, + float aInflation) +{ + return nsLayoutUtils::GetFontMetricsForStyleContext(aFrame->StyleContext(), + aFontMetrics, + aInflation); +} + +nsresult +nsLayoutUtils::GetFontMetricsForStyleContext(nsStyleContext* aStyleContext, + nsFontMetrics** aFontMetrics, + float aInflation) +{ + // pass the user font set object into the device context to pass along to CreateFontGroup + nsPresContext* pc = aStyleContext->PresContext(); + gfxUserFontSet* fs = pc->GetUserFontSet(); + gfxTextPerfMetrics* tp = pc->GetTextPerfMetrics(); + + nsFont font = aStyleContext->StyleFont()->mFont; + // We need to not run font.size through floats when it's large since + // doing so would be lossy. Fortunately, in such cases, aInflation is + // guaranteed to be 1.0f. + if (aInflation != 1.0f) { + font.size = NSToCoordRound(font.size * aInflation); + } + return pc->DeviceContext()->GetMetricsFor( + font, aStyleContext->StyleFont()->mLanguage, + fs, tp, *aFontMetrics); +} + +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(); + + void* value = aFrame->Properties().Get(nsIFrame::IBSplitSibling()); + return static_cast(value); + } + + return nullptr; +} + +nsIFrame* +nsLayoutUtils::FirstContinuationOrIBSplitSibling(nsIFrame *aFrame) +{ + nsIFrame *result = aFrame->FirstContinuation(); + if (result->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) { + while (true) { + nsIFrame *f = static_cast + (result->Properties().Get(nsIFrame::IBSplitPrevSibling())); + if (!f) + break; + result = f; + } + } + + 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)); +} + +static nscoord AddPercents(nsLayoutUtils::IntrinsicWidthType aType, + nscoord aCurrent, float aPercent) +{ + nscoord result = aCurrent; + if (aPercent > 0.0f && aType == nsLayoutUtils::PREF_WIDTH) { + // XXX Should we also consider percentages for min widths, up to a + // limit? + if (aPercent >= 1.0f) + result = nscoord_MAX; + else + result = NSToCoordRound(float(result) / (1.0f - aPercent)); + } + return result; +} + +// 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; +} + +// Only call on style coords for which GetAbsoluteCoord returned false. +static bool +GetPercentHeight(const nsStyleCoord& aStyle, + nsIFrame* aFrame, + nscoord& aResult) +{ + if (eStyleUnit_Percent != aStyle.GetUnit() && + !aStyle.IsCalcUnit()) + return false; + + MOZ_ASSERT(!aStyle.IsCalcUnit() || aStyle.CalcHasPercent(), + "GetAbsoluteCoord should have handled this"); + + nsIFrame *f = aFrame->GetContainingBlock(); + if (!f) { + NS_NOTREACHED("top of frame tree not a containing block"); + return false; + } + + // 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. + if (f->StyleContext()->GetPseudo() == nsCSSAnonBoxes::scrolledContent) { + f = f->GetParent(); + } + + const nsStylePosition *pos = f->StylePosition(); + nscoord h; + if (!GetAbsoluteCoord(pos->mHeight, h) && + !GetPercentHeight(pos->mHeight, f, h)) { + NS_ASSERTION(pos->mHeight.GetUnit() == eStyleUnit_Auto || + pos->mHeight.HasPercent(), + "unknown height 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(pos->mHeight.GetUnit() == eStyleUnit_Auto, + "Unexpected height unit for viewport or canvas or page-content"); + // For the viewport, canvas, and page-content kids, the percentage + // basis is just the parent height. + h = f->GetSize().height; + if (h == NS_UNCONSTRAINEDSIZE) { + // We don't have a percentage basis after all + return false; + } + } + + nscoord maxh; + if (GetAbsoluteCoord(pos->mMaxHeight, maxh) || + GetPercentHeight(pos->mMaxHeight, f, maxh)) { + if (maxh < h) + h = maxh; + } else { + NS_ASSERTION(pos->mMaxHeight.GetUnit() == eStyleUnit_None || + pos->mMaxHeight.HasPercent(), + "unknown max-height unit"); + } + + nscoord minh; + if (GetAbsoluteCoord(pos->mMinHeight, minh) || + GetPercentHeight(pos->mMinHeight, f, minh)) { + if (minh > h) + h = minh; + } else { + NS_ASSERTION(pos->mMinHeight.HasPercent(), + "unknown min-height unit"); + } + + if (aStyle.IsCalcUnit()) { + aResult = std::max(nsRuleNode::ComputeComputedCalc(aStyle, h), 0); + return true; + } + + aResult = NSToCoordRound(aStyle.GetPercentValue() * h); + return true; +} + +// 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->GetPrefWidth(aRenderingContext); + else + aResult = aFrame->GetMinWidth(aRenderingContext); + return true; +} + +#undef DEBUG_INTRINSIC_WIDTH + +#ifdef DEBUG_INTRINSIC_WIDTH +static int32_t gNoiseIndent = 0; +#endif + +/* static */ nscoord +nsLayoutUtils::IntrinsicForContainer(nsRenderingContext *aRenderingContext, + nsIFrame *aFrame, + IntrinsicWidthType aType, + uint32_t aFlags) +{ + NS_PRECONDITION(aFrame, "null frame"); + NS_PRECONDITION(aType == MIN_WIDTH || aType == PREF_WIDTH, "bad type"); + +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s intrinsic width for container:\n", + aType == MIN_WIDTH ? "min" : "pref"); +#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); + + nsIFrame::IntrinsicWidthOffsetData offsets = + aFrame->IntrinsicWidthOffsets(aRenderingContext); + + const nsStylePosition *stylePos = aFrame->StylePosition(); + uint8_t boxSizing = stylePos->mBoxSizing; + const nsStyleCoord &styleWidth = stylePos->mWidth; + const nsStyleCoord &styleMinWidth = stylePos->mMinWidth; + const nsStyleCoord &styleMaxWidth = stylePos->mMaxWidth; + + // 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 maxw; + bool haveFixedMaxWidth = GetAbsoluteCoord(styleMaxWidth, maxw); + nscoord minw; + bool haveFixedMinWidth = GetAbsoluteCoord(styleMinWidth, minw); + + // 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(styleWidth, w) will always succeed, so + // we'll never need the intrinsic dimensions. + if (styleWidth.GetUnit() == eStyleUnit_Enumerated && + (styleWidth.GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT || + styleWidth.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 = NS_STYLE_BOX_SIZING_CONTENT; + } else if (!styleWidth.ConvertsToLength() && + !(haveFixedMinWidth && haveFixedMaxWidth && maxw <= minw)) { +#ifdef DEBUG_INTRINSIC_WIDTH + ++gNoiseIndent; +#endif + if (aType == MIN_WIDTH) + result = aFrame->GetMinWidth(aRenderingContext); + else + result = aFrame->GetPrefWidth(aRenderingContext); +#ifdef DEBUG_INTRINSIC_WIDTH + --gNoiseIndent; + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s intrinsic width from frame is %d.\n", + aType == MIN_WIDTH ? "min" : "pref", result); +#endif + + // Handle elements with an intrinsic ratio (or size) and a specified + // height, min-height, or max-height. + const nsStyleCoord &styleHeight = stylePos->mHeight; + const nsStyleCoord &styleMinHeight = stylePos->mMinHeight; + const nsStyleCoord &styleMaxHeight = stylePos->mMaxHeight; + if (styleHeight.GetUnit() != eStyleUnit_Auto || + !(styleMinHeight.GetUnit() == eStyleUnit_Coord && + styleMinHeight.GetCoordValue() == 0) || + styleMaxHeight.GetUnit() != eStyleUnit_None) { + + nsSize ratio = aFrame->GetIntrinsicRatio(); + + if (ratio.height != 0) { + nscoord heightTakenByBoxSizing = 0; + switch (boxSizing) { + case NS_STYLE_BOX_SIZING_BORDER: { + const nsStyleBorder* styleBorder = aFrame->StyleBorder(); + heightTakenByBoxSizing += + styleBorder->GetComputedBorder().TopBottom(); + // fall through + } + case NS_STYLE_BOX_SIZING_PADDING: { + if (!(aFlags & IGNORE_PADDING)) { + const nsStylePadding* stylePadding = aFrame->StylePadding(); + nscoord pad; + if (GetAbsoluteCoord(stylePadding->mPadding.GetTop(), pad) || + GetPercentHeight(stylePadding->mPadding.GetTop(), aFrame, pad)) { + heightTakenByBoxSizing += pad; + } + if (GetAbsoluteCoord(stylePadding->mPadding.GetBottom(), pad) || + GetPercentHeight(stylePadding->mPadding.GetBottom(), aFrame, pad)) { + heightTakenByBoxSizing += pad; + } + } + // fall through + } + case NS_STYLE_BOX_SIZING_CONTENT: + default: + break; + } + + nscoord h; + if (GetAbsoluteCoord(styleHeight, h) || + GetPercentHeight(styleHeight, aFrame, h)) { + h = std::max(0, h - heightTakenByBoxSizing); + result = + NSToCoordRound(h * (float(ratio.width) / float(ratio.height))); + } + + if (GetAbsoluteCoord(styleMaxHeight, h) || + GetPercentHeight(styleMaxHeight, aFrame, h)) { + h = std::max(0, h - heightTakenByBoxSizing); + nscoord maxWidth = + NSToCoordRound(h * (float(ratio.width) / float(ratio.height))); + if (maxWidth < result) + result = maxWidth; + } + + if (GetAbsoluteCoord(styleMinHeight, h) || + GetPercentHeight(styleMinHeight, aFrame, h)) { + h = std::max(0, h - heightTakenByBoxSizing); + nscoord minWidth = + NSToCoordRound(h * (float(ratio.width) / float(ratio.height))); + if (minWidth > result) + result = minWidth; + } + } + } + } + + if (aFrame->GetType() == nsGkAtoms::tableFrame) { + // Tables can't shrink smaller than their intrinsic minimum width, + // no matter what. + min = aFrame->GetMinWidth(aRenderingContext); + } + + // We also need to track what has been added on outside of the box + // (controlled by 'box-sizing') where 'width', 'min-width' and + // 'max-width' are applied. 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. + nscoord coordOutsideWidth = 0; + float pctOutsideWidth = 0; + float pctTotal = 0.0f; + + if (!(aFlags & IGNORE_PADDING)) { + coordOutsideWidth += offsets.hPadding; + pctOutsideWidth += offsets.hPctPadding; + + if (boxSizing == NS_STYLE_BOX_SIZING_PADDING) { + min += coordOutsideWidth; + result = NSCoordSaturatingAdd(result, coordOutsideWidth); + pctTotal += pctOutsideWidth; + + coordOutsideWidth = 0; + pctOutsideWidth = 0.0f; + } + } + + coordOutsideWidth += offsets.hBorder; + + if (boxSizing == NS_STYLE_BOX_SIZING_BORDER) { + min += coordOutsideWidth; + result = NSCoordSaturatingAdd(result, coordOutsideWidth); + pctTotal += pctOutsideWidth; + + coordOutsideWidth = 0; + pctOutsideWidth = 0.0f; + } + + coordOutsideWidth += offsets.hMargin; + pctOutsideWidth += offsets.hPctMargin; + + min += coordOutsideWidth; + result = NSCoordSaturatingAdd(result, coordOutsideWidth); + pctTotal += pctOutsideWidth; + + nscoord w; + if (GetAbsoluteCoord(styleWidth, w) || + GetIntrinsicCoord(styleWidth, aRenderingContext, aFrame, + PROP_WIDTH, w)) { + result = AddPercents(aType, w + coordOutsideWidth, pctOutsideWidth); + } + else if (aType == MIN_WIDTH && + // The only cases of coord-percent-calc() units that + // GetAbsoluteCoord didn't handle are percent and calc()s + // containing percent. + styleWidth.IsCoordPercentCalcUnit() && + aFrame->IsFrameOfType(nsIFrame::eReplaced)) { + // A percentage width on replaced elements means they can shrink to 0. + result = 0; // let |min| handle padding/border/margin + } + 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. + result = AddPercents(aType, result, pctTotal); + } + + if (haveFixedMaxWidth || + GetIntrinsicCoord(styleMaxWidth, aRenderingContext, aFrame, + PROP_MAX_WIDTH, maxw)) { + maxw = AddPercents(aType, maxw + coordOutsideWidth, pctOutsideWidth); + if (result > maxw) + result = maxw; + } + + if (haveFixedMinWidth || + GetIntrinsicCoord(styleMinWidth, aRenderingContext, aFrame, + PROP_MIN_WIDTH, minw)) { + minw = AddPercents(aType, minw + coordOutsideWidth, pctOutsideWidth); + if (result < minw) + result = minw; + } + + min = AddPercents(aType, min, pctTotal); + if (result < min) + result = min; + + const nsStyleDisplay *disp = aFrame->StyleDisplay(); + if (aFrame->IsThemed(disp)) { + nsIntSize size(0, 0); + bool canOverride = true; + nsPresContext *presContext = aFrame->PresContext(); + presContext->GetTheme()-> + GetMinimumWidgetSize(aRenderingContext, aFrame, disp->mAppearance, + &size, &canOverride); + + nscoord themeWidth = presContext->DevPixelsToAppUnits(size.width); + + // GMWS() returns a border-box width + themeWidth += offsets.hMargin; + themeWidth = AddPercents(aType, themeWidth, offsets.hPctMargin); + + if (themeWidth > result || !canOverride) + result = themeWidth; + } + +#ifdef DEBUG_INTRINSIC_WIDTH + nsFrame::IndentBy(stderr, gNoiseIndent); + static_cast(aFrame)->ListTag(stderr); + printf_stderr(" %s intrinsic width for container is %d twips.\n", + aType == MIN_WIDTH ? "min" : "pref", result); +#endif + + return result; +} + +/* static */ nscoord +nsLayoutUtils::ComputeCBDependentValue(nscoord aPercentBasis, + const nsStyleCoord& aCoord) +{ + NS_WARN_IF_FALSE(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::ComputeWidthValue( + nsRenderingContext* aRenderingContext, + nsIFrame* aFrame, + nscoord aContainingBlockWidth, + nscoord aContentEdgeToBoxSizing, + nscoord aBoxSizingToMarginEdge, + const nsStyleCoord& aCoord) +{ + NS_PRECONDITION(aFrame, "non-null frame expected"); + NS_PRECONDITION(aRenderingContext, "non-null rendering context expected"); + NS_WARN_IF_FALSE(aContainingBlockWidth != NS_UNCONSTRAINEDSIZE, + "have unconstrained width; this should only result from " + "very large sizes, not attempts at intrinsic width " + "calculation"); + NS_PRECONDITION(aContainingBlockWidth >= 0, + "width less than zero"); + + nscoord result; + if (aCoord.IsCoordPercentCalcUnit()) { + result = nsRuleNode::ComputeCoordPercentCalc(aCoord, + aContainingBlockWidth); + // The result of a calc() expression might be less than 0; we + // should clamp at runtime (below). (Percentages and coords that + // are less than 0 have already been dropped by the parser.) + result -= aContentEdgeToBoxSizing; + } else { + MOZ_ASSERT(eStyleUnit_Enumerated == aCoord.GetUnit()); + // If aFrame is a container for font size inflation, then shrink + // wrapping inside of it should not apply font size inflation. + AutoMaybeDisableFontInflation an(aFrame); + + int32_t val = aCoord.GetIntValue(); + switch (val) { + case NS_STYLE_WIDTH_MAX_CONTENT: + result = aFrame->GetPrefWidth(aRenderingContext); + NS_ASSERTION(result >= 0, "width less than zero"); + break; + case NS_STYLE_WIDTH_MIN_CONTENT: + result = aFrame->GetMinWidth(aRenderingContext); + NS_ASSERTION(result >= 0, "width less than zero"); + break; + case NS_STYLE_WIDTH_FIT_CONTENT: + { + nscoord pref = aFrame->GetPrefWidth(aRenderingContext), + min = aFrame->GetMinWidth(aRenderingContext), + fill = aContainingBlockWidth - + (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing); + result = std::max(min, std::min(pref, fill)); + NS_ASSERTION(result >= 0, "width less than zero"); + } + break; + case NS_STYLE_WIDTH_AVAILABLE: + result = aContainingBlockWidth - + (aBoxSizingToMarginEdge + aContentEdgeToBoxSizing); + } + } + + return std::max(0, result); +} + +/* static */ nscoord +nsLayoutUtils::ComputeHeightDependentValue( + nscoord aContainingBlockHeight, + const nsStyleCoord& aCoord) +{ + // XXXldb Some callers explicitly check aContainingBlockHeight + // 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 != aContainingBlockHeight || + !aCoord.HasPercent(), + "unexpected containing block height"); + + if (aCoord.IsCoordPercentCalcUnit()) { + return nsRuleNode::ComputeCoordPercentCalc(aCoord, aContainingBlockHeight); + } + + NS_ASSERTION(aCoord.GetUnit() == eStyleUnit_None || + aCoord.GetUnit() == eStyleUnit_Auto, + "unexpected height value"); + return 0; +} + +/* static */ void +nsLayoutUtils::MarkDescendantsDirty(nsIFrame *aSubtreeRoot) +{ + nsAutoTArray 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 nsHTMLReflowState::InitResizeFlags has some similar + // code; see comments there for how and why it differs. + nsAutoTArray stack; + stack.AppendElement(subtreeRoot); + + do { + nsIFrame *f = stack.ElementAt(stack.Length() - 1); + stack.RemoveElementAt(stack.Length() - 1); + + f->MarkIntrinsicWidthsDirty(); + + 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); +} + +#define MULDIV(a,b,c) (nscoord(int64_t(a) * int64_t(b) / int64_t(c))) + +/* static */ nsSize +nsLayoutUtils::ComputeSizeWithIntrinsicDimensions( + nsRenderingContext* aRenderingContext, nsIFrame* aFrame, + const IntrinsicSize& aIntrinsicSize, + nsSize aIntrinsicRatio, nsSize aCBSize, + nsSize aMargin, nsSize aBorder, nsSize aPadding) +{ + const nsStylePosition* stylePos = aFrame->StylePosition(); + + // If we're a flex item, we'll compute our size a bit differently. + const nsStyleCoord* widthStyleCoord = &(stylePos->mWidth); + const nsStyleCoord* heightStyleCoord = &(stylePos->mHeight); + + bool isFlexItem = aFrame->IsFlexItem(); + bool isHorizontalFlexItem = false; + + if (isFlexItem) { + // Flex items use their "flex-basis" property in place of their main-size + // property (e.g. "width") for sizing purposes, *unless* they have + // "flex-basis:auto", in which case they use their main-size property after + // all. + uint32_t flexDirection = + aFrame->GetParent()->StylePosition()->mFlexDirection; + isHorizontalFlexItem = + flexDirection == NS_STYLE_FLEX_DIRECTION_ROW || + flexDirection == NS_STYLE_FLEX_DIRECTION_ROW_REVERSE; + + // NOTE: The logic here should match the similar chunk for determining + // widthStyleCoord and heightStyleCoord in nsFrame::ComputeSize(). + const nsStyleCoord* flexBasis = &(stylePos->mFlexBasis); + if (flexBasis->GetUnit() != eStyleUnit_Auto) { + if (isHorizontalFlexItem) { + widthStyleCoord = flexBasis; + } else { + // One caveat for vertical flex items: We don't support enumerated + // values (e.g. "max-content") for height properties yet. So, if our + // computed flex-basis is an enumerated value, we'll just behave as if + // it were "auto", which means "use the main-size property after all" + // (which is "height", in this case). + // NOTE: Once we support intrinsic sizing keywords for "height", + // we should remove this check. + if (flexBasis->GetUnit() != eStyleUnit_Enumerated) { + heightStyleCoord = flexBasis; + } + } + } + } + + // Handle intrinsic sizes and their interaction with + // {min-,max-,}{width,height} according to the rules in + // http://www.w3.org/TR/CSS21/visudet.html#min-max-widths + + // Note: throughout the following section of the function, I avoid + // a * (b / c) because of its reduced accuracy relative to a * b / c + // or (a * b) / c (which are equivalent). + + const bool isAutoWidth = widthStyleCoord->GetUnit() == eStyleUnit_Auto; + const bool isAutoHeight = IsAutoHeight(*heightStyleCoord, aCBSize.height); + + nsSize boxSizingAdjust(0,0); + switch (stylePos->mBoxSizing) { + case NS_STYLE_BOX_SIZING_BORDER: + boxSizingAdjust += aBorder; + // fall through + case NS_STYLE_BOX_SIZING_PADDING: + boxSizingAdjust += aPadding; + } + nscoord boxSizingToMarginEdgeWidth = + aMargin.width + aBorder.width + aPadding.width - boxSizingAdjust.width; + + nscoord width, minWidth, maxWidth, height, minHeight, maxHeight; + + if (!isAutoWidth) { + width = nsLayoutUtils::ComputeWidthValue(aRenderingContext, + aFrame, aCBSize.width, boxSizingAdjust.width, + boxSizingToMarginEdgeWidth, *widthStyleCoord); + } + + if (stylePos->mMaxWidth.GetUnit() != eStyleUnit_None && + !(isFlexItem && isHorizontalFlexItem)) { + maxWidth = nsLayoutUtils::ComputeWidthValue(aRenderingContext, + aFrame, aCBSize.width, boxSizingAdjust.width, + boxSizingToMarginEdgeWidth, stylePos->mMaxWidth); + } else { + // NOTE: Flex items ignore their min & max sizing properties in their + // flex container's main-axis. (Those properties get applied later in + // the flexbox algorithm.) + maxWidth = nscoord_MAX; + } + + if (!(isFlexItem && isHorizontalFlexItem)) { + minWidth = nsLayoutUtils::ComputeWidthValue(aRenderingContext, + aFrame, aCBSize.width, boxSizingAdjust.width, + boxSizingToMarginEdgeWidth, stylePos->mMinWidth); + } else { + // NOTE: Flex items ignore their min & max sizing properties in their + // flex container's main-axis. (Those properties get applied later in + // the flexbox algorithm.) + minWidth = 0; + } + + if (!isAutoHeight) { + height = nsLayoutUtils::ComputeHeightValue(aCBSize.height, + boxSizingAdjust.height, + *heightStyleCoord); + } + + if (!IsAutoHeight(stylePos->mMaxHeight, aCBSize.height) && + !(isFlexItem && !isHorizontalFlexItem)) { + maxHeight = nsLayoutUtils::ComputeHeightValue(aCBSize.height, + boxSizingAdjust.height, + stylePos->mMaxHeight); + } else { + maxHeight = nscoord_MAX; + } + + if (!IsAutoHeight(stylePos->mMinHeight, aCBSize.height) && + !(isFlexItem && !isHorizontalFlexItem)) { + minHeight = nsLayoutUtils::ComputeHeightValue(aCBSize.height, + boxSizingAdjust.height, + stylePos->mMinHeight); + } else { + minHeight = 0; + } + + // Resolve percentage intrinsic width/height as necessary: + + NS_ASSERTION(aCBSize.width != NS_UNCONSTRAINEDSIZE, + "Our containing block must not have unconstrained width!"); + + bool hasIntrinsicWidth, hasIntrinsicHeight; + nscoord intrinsicWidth, intrinsicHeight; + + if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) { + hasIntrinsicWidth = true; + intrinsicWidth = aIntrinsicSize.width.GetCoordValue(); + if (intrinsicWidth < 0) + intrinsicWidth = 0; + } else { + NS_ASSERTION(aIntrinsicSize.width.GetUnit() == eStyleUnit_None, + "unexpected unit"); + hasIntrinsicWidth = false; + intrinsicWidth = 0; + } + + if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { + hasIntrinsicHeight = true; + intrinsicHeight = aIntrinsicSize.height.GetCoordValue(); + if (intrinsicHeight < 0) + intrinsicHeight = 0; + } else { + NS_ASSERTION(aIntrinsicSize.height.GetUnit() == eStyleUnit_None, + "unexpected unit"); + hasIntrinsicHeight = false; + intrinsicHeight = 0; + } + + NS_ASSERTION(aIntrinsicRatio.width >= 0 && aIntrinsicRatio.height >= 0, + "Intrinsic ratio has a negative component!"); + + // Now calculate the used values for width and height: + + if (isAutoWidth) { + if (isAutoHeight) { + + // 'auto' width, 'auto' height + + // Get tentative values - CSS 2.1 sections 10.3.2 and 10.6.2: + + nscoord tentWidth, tentHeight; + + if (hasIntrinsicWidth) { + tentWidth = intrinsicWidth; + } else if (hasIntrinsicHeight && aIntrinsicRatio.height > 0) { + tentWidth = MULDIV(intrinsicHeight, aIntrinsicRatio.width, aIntrinsicRatio.height); + } else if (aIntrinsicRatio.width > 0) { + tentWidth = aCBSize.width - boxSizingToMarginEdgeWidth; // XXX scrollbar? + if (tentWidth < 0) tentWidth = 0; + } else { + tentWidth = nsPresContext::CSSPixelsToAppUnits(300); + } + + if (hasIntrinsicHeight) { + tentHeight = intrinsicHeight; + } else if (aIntrinsicRatio.width > 0) { + tentHeight = MULDIV(tentWidth, aIntrinsicRatio.height, aIntrinsicRatio.width); + } else { + tentHeight = nsPresContext::CSSPixelsToAppUnits(150); + } + + return ComputeAutoSizeWithIntrinsicDimensions(minWidth, minHeight, + maxWidth, maxHeight, + tentWidth, tentHeight); + } else { + + // 'auto' width, non-'auto' height + height = NS_CSS_MINMAX(height, minHeight, maxHeight); + if (aIntrinsicRatio.height > 0) { + width = MULDIV(height, aIntrinsicRatio.width, aIntrinsicRatio.height); + } else if (hasIntrinsicWidth) { + width = intrinsicWidth; + } else { + width = nsPresContext::CSSPixelsToAppUnits(300); + } + width = NS_CSS_MINMAX(width, minWidth, maxWidth); + + } + } else { + if (isAutoHeight) { + + // non-'auto' width, 'auto' height + width = NS_CSS_MINMAX(width, minWidth, maxWidth); + if (aIntrinsicRatio.width > 0) { + height = MULDIV(width, aIntrinsicRatio.height, aIntrinsicRatio.width); + } else if (hasIntrinsicHeight) { + height = intrinsicHeight; + } else { + height = nsPresContext::CSSPixelsToAppUnits(150); + } + height = NS_CSS_MINMAX(height, minHeight, maxHeight); + + } else { + + // non-'auto' width, non-'auto' height + width = NS_CSS_MINMAX(width, minWidth, maxWidth); + height = NS_CSS_MINMAX(height, minHeight, maxHeight); + + } + } + + return nsSize(width, height); +} + +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 = MULDIV(maxWidth, tentHeight, tentWidth); + if (heightAtMaxWidth < minHeight) + heightAtMaxWidth = minHeight; + heightAtMinWidth = MULDIV(minWidth, tentHeight, tentWidth); + if (heightAtMinWidth > maxHeight) + heightAtMinWidth = maxHeight; + } else { + heightAtMaxWidth = heightAtMinWidth = NS_CSS_MINMAX(tentHeight, minHeight, maxHeight); + } + + if (tentHeight > 0) { + widthAtMaxHeight = MULDIV(maxHeight, tentWidth, tentHeight); + if (widthAtMaxHeight < minWidth) + widthAtMaxHeight = minWidth; + widthAtMinHeight = MULDIV(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::MinWidthFromInline(nsIFrame* aFrame, + nsRenderingContext* aRenderingContext) +{ + NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), + "should not be container for font size inflation"); + + nsIFrame::InlineMinWidthData data; + DISPLAY_MIN_WIDTH(aFrame, data.prevLines); + aFrame->AddInlineMinWidth(aRenderingContext, &data); + data.ForceBreak(aRenderingContext); + return data.prevLines; +} + +/* static */ nscoord +nsLayoutUtils::PrefWidthFromInline(nsIFrame* aFrame, + nsRenderingContext* aRenderingContext) +{ + NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(), + "should not be container for font size inflation"); + + nsIFrame::InlinePrefWidthData data; + DISPLAY_PREF_WIDTH(aFrame, data.prevLines); + aFrame->AddInlinePrefWidth(aRenderingContext, &data); + data.ForceBreak(aRenderingContext); + return data.prevLines; +} + +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, nsCSSProperty aProperty) +{ + if (aProperty == eCSSProperty_color) + { + nscolor nativeColor = NS_RGB(0, 0, 0); + if (GetNativeTextColor(aFrame, nativeColor)) + return nativeColor; + } + + nscolor color = aFrame->GetVisitedDependentColor(aProperty); + if (ShouldDarkenColors(aFrame->PresContext())) { + color = DarkenColor(color); + } + + return color; +} + +bool +nsLayoutUtils::GetNativeTextColor(nsIFrame* aFrame, nscolor& aColor) +{ + nsPresContext *presContext = aFrame->PresContext(); + if (!presContext->IsChrome()) { + // If native appearance was used to draw the background of the containing + // frame, return a contrasting native foreground color instead of the + // color from the element's style. This avoids a problem where black + // text was displayed on a black background when a Windows theme such as + // "High Contrast Black" was used. The background is drawn inside + // nsNativeThemeWin::ClassicDrawWidgetBackground(). + // + // Because both the background color and this foreground color are used + // directly without exposing the colors via CSS computed styles, the + // native colors are not leaked to content. + nsIFrame* bgFrame = + nsCSSRendering::FindNonTransparentBackgroundFrame(aFrame); + if (bgFrame) { + const nsStyleDisplay* displayData = bgFrame->StyleDisplay(); + uint8_t widgetType = displayData->mAppearance; + nsITheme *theme = presContext->GetTheme(); + if (theme && widgetType && theme->ThemeSupportsWidget(presContext, + bgFrame, + widgetType)) { + bool isDisabled = false; + nsIContent* frameContent = bgFrame->GetContent(); + if (frameContent && frameContent->IsElement()) { + EventStates es = frameContent->AsElement()->State(); + isDisabled = es.HasState(NS_EVENT_STATE_DISABLED); + } + + if (NS_SUCCEEDED(LookAndFeel::GetColorForNativeAppearance(widgetType, + isDisabled, &aColor))) { + return true; + } + } + } + } + + return false; +} + +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; +} + +void +nsLayoutUtils::DrawString(const nsIFrame* aFrame, + nsRenderingContext* aContext, + const char16_t* aString, + int32_t aLength, + nsPoint aPoint, + nsStyleContext* aStyleContext) +{ + nsresult rv = NS_ERROR_FAILURE; + nsPresContext* presContext = aFrame->PresContext(); + if (presContext->BidiEnabled()) { + nsBidiLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aStyleContext ? + aStyleContext : aFrame->StyleContext()); + rv = nsBidiPresUtils::RenderText(aString, aLength, level, + presContext, *aContext, *aContext, + aPoint.x, aPoint.y); + } + if (NS_FAILED(rv)) + { + aContext->SetTextRunRTL(false); + aContext->DrawString(aString, aLength, aPoint.x, aPoint.y); + } +} + +nscoord +nsLayoutUtils::GetStringWidth(const nsIFrame* aFrame, + nsRenderingContext* aContext, + const char16_t* aString, + int32_t aLength) +{ + nsPresContext* presContext = aFrame->PresContext(); + if (presContext->BidiEnabled()) { + nsBidiLevel level = + nsBidiPresUtils::BidiLevelFromStyle(aFrame->StyleContext()); + return nsBidiPresUtils::MeasureTextWidth(aString, aLength, + level, presContext, *aContext); + } + aContext->SetTextRunRTL(false); + return aContext->GetWidth(aString, aLength); +} + +/* 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. + nsRefPtr renderingContext = new nsRenderingContext(); + renderingContext->Init(presCtx->DeviceContext(), shadowContext); + + aDestCtx->Save(); + aDestCtx->NewPath(); + aDestCtx->SetColor(gfxRGBA(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) +{ + nscoord fontAscent = aFontMetrics->MaxAscent(); + nscoord fontHeight = aFontMetrics->MaxHeight(); + + nscoord leading = aLineHeight - fontHeight; + return fontAscent + leading/2; +} + + +/* static */ bool +nsLayoutUtils::GetFirstLineBaseline(const nsIFrame* aFrame, nscoord* aResult) +{ + LinePosition position; + if (!GetFirstLinePosition(aFrame, &position)) + return false; + *aResult = position.mBaseline; + return true; +} + +/* static */ bool +nsLayoutUtils::GetFirstLinePosition(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::tableOuterFrame) { + aResult->mTop = 0; + aResult->mBaseline = aFrame->GetBaseline(); + // This is what we want for the list bullet caller; not sure if + // other future callers will want the same. + aResult->mBottom = aFrame->GetSize().height; + 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(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->GetUsedBorderAndPadding().top; + return true; + } + return false; + } + + if (fType == nsGkAtoms::fieldSetFrame) { + LinePosition kidPosition; + nsIFrame* kid = aFrame->GetFirstPrincipalChild(); + // kid might be a legend frame here, but that's ok. + if (GetFirstLinePosition(kid, &kidPosition)) { + *aResult = kidPosition + kid->GetNormalPosition().y; + return true; + } + return false; + } + + // No baseline. + return false; + } + + for (nsBlockFrame::const_line_iterator line = block->begin_lines(), + line_end = block->end_lines(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame *kid = line->mFirstChild; + LinePosition kidPosition; + if (GetFirstLinePosition(kid, &kidPosition)) { + *aResult = kidPosition + kid->GetNormalPosition().y; + 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 top = line->BStart(); + aResult->mTop = top; + aResult->mBaseline = top + line->GetAscent(); + aResult->mBottom = top + line->BSize(); + return true; + } + } + } + return false; +} + +/* static */ bool +nsLayoutUtils::GetLastLineBaseline(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::const_reverse_line_iterator line = block->rbegin_lines(), + line_end = block->rend_lines(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame *kid = line->mFirstChild; + nscoord kidBaseline; + if (GetLastLineBaseline(kid, &kidBaseline)) { + // Ignore relative positioning for baseline calculations + *aResult = kidBaseline + kid->GetNormalPosition().y; + 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->GetNormalPosition().y + kid->GetRect().height; + 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->GetAscent(); + return true; + } + } + } + return false; +} + +static nscoord +CalculateBlockContentBottom(nsBlockFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null ptr"); + + nscoord contentBottom = 0; + + for (nsBlockFrame::line_iterator line = aFrame->begin_lines(), + line_end = aFrame->end_lines(); + line != line_end; ++line) { + if (line->IsBlock()) { + nsIFrame* child = line->mFirstChild; + nscoord offset = child->GetNormalPosition().y; + contentBottom = std::max(contentBottom, + nsLayoutUtils::CalculateContentBottom(child) + offset); + } + else { + contentBottom = std::max(contentBottom, line->BEnd()); + } + } + return contentBottom; +} + +/* static */ nscoord +nsLayoutUtils::CalculateContentBottom(nsIFrame* aFrame) +{ + NS_PRECONDITION(aFrame, "null ptr"); + + nscoord contentBottom = aFrame->GetRect().height; + + // We want scrollable overflow rather than visual because this + // calculation is intended to affect layout. + if (aFrame->GetScrollableOverflowRect().height > contentBottom) { + nsIFrame::ChildListIDs skip(nsIFrame::kOverflowList | + nsIFrame::kExcessOverflowContainersList | + nsIFrame::kOverflowOutOfFlowList); + nsBlockFrame* blockFrame = GetAsBlock(aFrame); + if (blockFrame) { + contentBottom = + std::max(contentBottom, CalculateBlockContentBottom(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->GetNormalPosition().y; + contentBottom = std::max(contentBottom, + CalculateContentBottom(child) + offset); + } + } + } + } + return contentBottom; +} + +/* static */ nsIFrame* +nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) +{ + nsIFrame* layer; + for (layer = aFrame; layer; layer = layer->GetParent()) { + if (layer->IsPositioned() || + (layer->GetParent() && + layer->GetParent()->GetType() == nsGkAtoms::scrollFrame)) + break; + } + if (layer) + return layer; + return aFrame->PresContext()->PresShell()->FrameManager()->GetRootFrame(); +} + +GraphicsFilter +nsLayoutUtils::GetGraphicsFilterForFrame(nsIFrame* aForFrame) +{ + GraphicsFilter defaultFilter = GraphicsFilter::FILTER_GOOD; + nsStyleContext *sc; + if (nsCSSRendering::IsCanvasFrame(aForFrame)) { + nsCSSRendering::FindBackground(aForFrame, &sc); + } else { + sc = aForFrame->StyleContext(); + } + + switch (sc->StyleSVG()->mImageRendering) { + case NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED: + return GraphicsFilter::FILTER_FAST; + case NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY: + return GraphicsFilter::FILTER_BEST; + case NS_STYLE_IMAGE_RENDERING_CRISPEDGES: + return GraphicsFilter::FILTER_NEAREST; + 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 either device space or user space (depending on mResetCTM) + // to image space + gfxMatrix mUserSpaceToImageSpace; + // A device-space, pixel-aligned rectangle to fill + gfxRect mFillRect; + // A pixel rectangle in tiled image space outside of which gfx should not + // sample (using EXTEND_PAD as necessary) + nsIntRect mSubimage; + // Whether there's anything to draw at all + bool mShouldDraw; + // true iff the CTM of the rendering context needs to be reset to the + // identity matrix before drawing + bool mResetCTM; + + SnappedImageDrawingParameters() + : mShouldDraw(false) + , mResetCTM(false) + {} + + SnappedImageDrawingParameters(const gfxMatrix& aUserSpaceToImageSpace, + const gfxRect& aFillRect, + const nsIntRect& aSubimage, + bool aResetCTM) + : mUserSpaceToImageSpace(aUserSpaceToImageSpace) + , mFillRect(aFillRect) + , mSubimage(aSubimage) + , mShouldDraw(true) + , mResetCTM(aResetCTM) + {} +}; + +/** + * 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, + const nsIntSize aImageSize) + +{ + if (aDest.IsEmpty() || aFill.IsEmpty() || !aImageSize.width || !aImageSize.height) + return SnappedImageDrawingParameters(); + + gfxRect devPixelDest = + nsLayoutUtils::RectToGfxRect(aDest, aAppUnitsPerDevPixel); + gfxRect devPixelFill = + nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel); + gfxRect devPixelDirty = + nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel); + + gfxMatrix currentMatrix = aCtx->CurrentMatrix(); + gfxRect fill = devPixelFill; + 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.xx > 0.0 && currentMatrix.yy > 0.0 && + aCtx->UserToDevicePixelSnapped(fill, true)) { + didSnap = true; + if (fill.IsEmpty()) { + return SnappedImageDrawingParameters(); + } + } else { + didSnap = false; + fill = devPixelFill; + } + + gfxSize imageSize(aImageSize.width, aImageSize.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()); + nsIntRect intSubimage; + intSubimage.MoveTo(NSToIntFloor(subimageTopLeft.x), + NSToIntFloor(subimageTopLeft.y)); + intSubimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - intSubimage.x, + NSToIntCeil(subimageBottomRight.y) - intSubimage.y); + + // Compute the anchor point and compute final fill rect. + // This code assumes that pixel-based devices have one pixel per + // device unit! + gfxPoint anchorPoint(gfxFloat(aAnchor.x)/aAppUnitsPerDevPixel, + gfxFloat(aAnchor.y)/aAppUnitsPerDevPixel); + gfxPoint imageSpaceAnchorPoint = + MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint); + + if (didSnap) { + imageSpaceAnchorPoint.Round(); + anchorPoint = imageSpaceAnchorPoint; + anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint); + anchorPoint = currentMatrix.Transform(anchorPoint); + anchorPoint.Round(); + + // This form of Transform is safe to call since non-axis-aligned + // transforms wouldn't be snapped. + devPixelDirty = currentMatrix.Transform(devPixelDirty); + } + + gfxFloat scaleX = imageSize.width*aAppUnitsPerDevPixel/aDest.width; + gfxFloat scaleY = imageSize.height*aAppUnitsPerDevPixel/aDest.height; + if (didSnap) { + // We'll reset aCTX to the identity matrix before drawing, so we need to + // adjust our scales to match. + scaleX /= currentMatrix.xx; + scaleY /= currentMatrix.yy; + } + gfxFloat translateX = imageSpaceAnchorPoint.x - anchorPoint.x*scaleX; + gfxFloat translateY = imageSpaceAnchorPoint.y - anchorPoint.y*scaleY; + gfxMatrix transform(scaleX, 0, 0, scaleY, translateX, translateY); + + gfxRect finalFillRect = fill; + // If the user-space-to-image-space 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 && !transform.HasNonIntegerTranslation()) { + devPixelDirty.RoundOut(); + finalFillRect = fill.Intersect(devPixelDirty); + } + if (finalFillRect.IsEmpty()) + return SnappedImageDrawingParameters(); + + return SnappedImageDrawingParameters(transform, finalFillRect, intSubimage, + didSnap); +} + + +static nsresult +DrawImageInternal(nsRenderingContext* aRenderingContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + const nsIntSize& aImageSize, + const SVGImageContext* aSVGContext, + uint32_t aImageFlags) +{ + if (aDest.Contains(aFill)) { + aImageFlags |= imgIContainer::FLAG_CLAMP; + } + int32_t appUnitsPerDevPixel = aRenderingContext->AppUnitsPerDevPixel(); + gfxContext* ctx = aRenderingContext->ThebesContext(); + + SnappedImageDrawingParameters drawingParams = + ComputeSnappedImageDrawingParameters(ctx, appUnitsPerDevPixel, aDest, aFill, + aAnchor, aDirty, aImageSize); + + if (!drawingParams.mShouldDraw) + return NS_OK; + + gfxContextMatrixAutoSaveRestore saveMatrix(ctx); + if (drawingParams.mResetCTM) { + ctx->IdentityMatrix(); + } + + aImage->Draw(ctx, aGraphicsFilter, drawingParams.mUserSpaceToImageSpace, + drawingParams.mFillRect, drawingParams.mSubimage, aImageSize, + aSVGContext, imgIContainer::FRAME_CURRENT, aImageFlags); + return NS_OK; +} + +/* static */ void +nsLayoutUtils::DrawPixelSnapped(nsRenderingContext* aRenderingContext, + gfxDrawable* aDrawable, + GraphicsFilter aFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty) +{ + int32_t appUnitsPerDevPixel = aRenderingContext->AppUnitsPerDevPixel(); + gfxContext* ctx = aRenderingContext->ThebesContext(); + gfxIntSize drawableSize = aDrawable->Size(); + nsIntSize imageSize(drawableSize.width, drawableSize.height); + + SnappedImageDrawingParameters drawingParams = + ComputeSnappedImageDrawingParameters(ctx, appUnitsPerDevPixel, aDest, aFill, + aAnchor, aDirty, imageSize); + + if (!drawingParams.mShouldDraw) + return; + + gfxContextMatrixAutoSaveRestore saveMatrix(ctx); + if (drawingParams.mResetCTM) { + ctx->IdentityMatrix(); + } + + gfxRect sourceRect = + drawingParams.mUserSpaceToImageSpace.Transform(drawingParams.mFillRect); + gfxRect imageRect(0, 0, imageSize.width, imageSize.height); + gfxRect subimage(drawingParams.mSubimage.x, drawingParams.mSubimage.y, + drawingParams.mSubimage.width, drawingParams.mSubimage.height); + + NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(), + "We must be allowed to sample *some* source pixels!"); + + gfxUtils::DrawPixelSnapped(ctx, aDrawable, + drawingParams.mUserSpaceToImageSpace, subimage, + sourceRect, imageRect, drawingParams.mFillRect, + gfxImageFormat::ARGB32, aFilter); +} + +/* static */ nsresult +nsLayoutUtils::DrawSingleUnscaledImage(nsRenderingContext* aRenderingContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsPoint& aDest, + const nsRect* aDirty, + uint32_t aImageFlags, + const nsRect* aSourceArea) +{ + nsIntSize imageSize; + aImage->GetWidth(&imageSize.width); + aImage->GetHeight(&imageSize.height); + NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE); + + nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); + nsSize size(imageSize.width*appUnitsPerCSSPixel, + imageSize.height*appUnitsPerCSSPixel); + + 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(aRenderingContext, aImage, aGraphicsFilter, + dest, fill, aDest, aDirty ? *aDirty : dest, + imageSize, nullptr, aImageFlags); +} + +/* static */ nsresult +nsLayoutUtils::DrawSingleImage(nsRenderingContext* aRenderingContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aDirty, + const SVGImageContext* aSVGContext, + uint32_t aImageFlags, + const nsRect* aSourceArea) +{ + nsIntSize imageSize; + if (aImage->GetType() == imgIContainer::TYPE_VECTOR) { + // We choose a size for vector images that emulates a raster image which + // is perfectly sized for the destination rect: each pixel in the image + // maps exactly to a single pixel on-screen. + nscoord appUnitsPerDevPx = aRenderingContext->AppUnitsPerDevPixel(); + imageSize.width = NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPx); + imageSize.height = NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPx); + } else { + // Raster images have an intrinsic size, so we just use that. + aImage->GetWidth(&imageSize.width); + aImage->GetHeight(&imageSize.height); + } + NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE); + + nsRect source; + if (aSourceArea) { + source = *aSourceArea; + } else { + nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); + source.SizeTo(imageSize.width*appUnitsPerCSSPixel, + imageSize.height*appUnitsPerCSSPixel); + } + + nsRect dest = nsLayoutUtils::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(aRenderingContext, aImage, aGraphicsFilter, dest, fill, + fill.TopLeft(), aDirty, imageSize, aSVGContext, aImageFlags); +} + +/* static */ void +nsLayoutUtils::ComputeSizeForDrawing(imgIContainer *aImage, + nsIntSize& 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 = nsIntSize(0, 0); + aIntrinsicRatio = nsSize(0, 0); + } +} + + +/* static */ nsresult +nsLayoutUtils::DrawBackgroundImage(nsRenderingContext* aRenderingContext, + imgIContainer* aImage, + const nsIntSize& aImageSize, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags) +{ + PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage"); + + if (UseBackgroundNearestFiltering()) { + aGraphicsFilter = GraphicsFilter::FILTER_NEAREST; + } + + return DrawImageInternal(aRenderingContext, aImage, aGraphicsFilter, + aDest, aFill, aAnchor, aDirty, + aImageSize, nullptr, aImageFlags); +} + +/* static */ nsresult +nsLayoutUtils::DrawImage(nsRenderingContext* aRenderingContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags) +{ + nsIntSize imageSize; + nsSize imageRatio; + bool gotHeight, gotWidth; + ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight); + + // XXX Dimensionless images shouldn't fall back to filled-area size -- the + // caller should provide the image size, a la DrawBackgroundImage. + 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 (!gotWidth) { + imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFill.width); + } + if (!gotHeight) { + imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFill.height); + } + + return DrawImageInternal(aRenderingContext, aImage, aGraphicsFilter, + aDest, aFill, aAnchor, aDirty, + imageSize, nullptr, aImageFlags); +} + +/* static */ nsRect +nsLayoutUtils::GetWholeImageDestination(const nsIntSize& 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 appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); + nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width*appUnitsPerCSSPixel*scaleX); + nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height*appUnitsPerCSSPixel*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) +{ + PR_STATIC_ASSERT((int)NS_SIDE_TOP == NS_CORNER_TOP_LEFT); + PR_STATIC_ASSERT((int)NS_SIDE_RIGHT == NS_CORNER_TOP_RIGHT); + PR_STATIC_ASSERT((int)NS_SIDE_BOTTOM == NS_CORNER_BOTTOM_RIGHT); + PR_STATIC_ASSERT((int)NS_SIDE_LEFT == NS_CORNER_BOTTOM_LEFT); + PR_STATIC_ASSERT((int)NS_SIDE_TOP == ((NS_CORNER_TOP_RIGHT - 1)&3)); + PR_STATIC_ASSERT((int)NS_SIDE_RIGHT == ((NS_CORNER_BOTTOM_RIGHT - 1)&3)); + PR_STATIC_ASSERT((int)NS_SIDE_BOTTOM == ((NS_CORNER_BOTTOM_LEFT - 1)&3)); + PR_STATIC_ASSERT((int)NS_SIDE_LEFT == ((NS_CORNER_TOP_LEFT - 1)&3)); + + return aSide == aCorner || aSide == ((aCorner - 1)&3); +} + +/* static */ bool +nsLayoutUtils::HasNonZeroCornerOnSide(const nsStyleCorners& aCorners, + css::Side aSide) +{ + PR_STATIC_ASSERT(NS_CORNER_TOP_LEFT_X/2 == NS_CORNER_TOP_LEFT); + PR_STATIC_ASSERT(NS_CORNER_TOP_LEFT_Y/2 == NS_CORNER_TOP_LEFT); + PR_STATIC_ASSERT(NS_CORNER_TOP_RIGHT_X/2 == NS_CORNER_TOP_RIGHT); + PR_STATIC_ASSERT(NS_CORNER_TOP_RIGHT_Y/2 == NS_CORNER_TOP_RIGHT); + PR_STATIC_ASSERT(NS_CORNER_BOTTOM_RIGHT_X/2 == NS_CORNER_BOTTOM_RIGHT); + PR_STATIC_ASSERT(NS_CORNER_BOTTOM_RIGHT_Y/2 == NS_CORNER_BOTTOM_RIGHT); + PR_STATIC_ASSERT(NS_CORNER_BOTTOM_LEFT_X/2 == NS_CORNER_BOTTOM_LEFT); + PR_STATIC_ASSERT(NS_CORNER_BOTTOM_LEFT_Y/2 == NS_CORNER_BOTTOM_LEFT); + + 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->StyleDisplay()->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->GetFirstPrincipalChild()) { + 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_BG_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() || IsPopup(f)) { + return f; + } + nsIFrame* parent = GetCrossDocParentFrame(f); + if (!parent) { + return f; + } + f = parent; + } +} + +/* static */ nsIFrame* +nsLayoutUtils::GetTransformRootFrame(nsIFrame* aFrame) +{ + nsIFrame *parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + while (parent && parent->Preserves3DChildren()) { + parent = nsLayoutUtils::GetCrossDocParentFrame(parent); + } + return 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->StyleSVG()->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; +} + +/* 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(nsPIDOMWindow* 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 = do_GetInterface(docShell); + if (!win) { + // No reason to go on + return nullptr; + } + + win->EnsureSizeUpToDate(); + + nsRefPtr 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::SurfaceFromElement(nsIImageLoadingContent* aElement, + uint32_t aSurfaceFlags, + DrawTarget* aTarget) +{ + SurfaceFromElementResult result; + nsresult rv; + + nsCOMPtr imgRequest; + rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(imgRequest)); + if (NS_FAILED(rv) || !imgRequest) + return result; + + uint32_t status; + imgRequest->GetImageStatus(&status); + 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; + rv = imgContainer->GetWidth(&imgWidth); + nsresult rv2 = imgContainer->GetHeight(&imgHeight); + if (NS_FAILED(rv) || NS_FAILED(rv2)) + return result; + + if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) { + if (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) { + frameFlags |= imgIContainer::FLAG_WANT_DATA_SURFACE; + } + result.mSourceSurface = imgContainer->GetFrame(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.mSize = gfxIntSize(imgWidth, imgHeight); + 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, + DrawTarget* aTarget) +{ + return SurfaceFromElement(static_cast(aElement), + aSurfaceFlags, aTarget); +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement, + uint32_t aSurfaceFlags, + DrawTarget* aTarget) +{ + SurfaceFromElementResult result; + + bool* isPremultiplied = nullptr; + if (aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) { + isPremultiplied = &result.mIsPremultiplied; + } + + gfxIntSize 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 : 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.mSize = size; + result.mPrincipal = aElement->NodePrincipal(); + result.mIsWriteOnly = aElement->IsWriteOnly(); + + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, + uint32_t aSurfaceFlags, + DrawTarget* aTarget) +{ + SurfaceFromElementResult result; + + NS_WARN_IF_FALSE((aSurfaceFlags & SFE_PREFER_NO_PREMULTIPLY_ALPHA) == 0, "We can't support non-premultiplied alpha for video!"); + + 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->GetCurrentPrincipal(); + if (!principal) + return result; + + ImageContainer *container = aElement->GetImageContainer(); + if (!container) + return result; + + mozilla::gfx::IntSize size; + result.mSourceSurface = container->GetCurrentAsSourceSurface(&size); + if (!result.mSourceSurface) + return result; + + if (aTarget) { + RefPtr opt = aTarget->OptimizeSourceSurface(result.mSourceSurface); + if (opt) { + result.mSourceSurface = opt; + } + } + + result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE; + result.mSize = ThebesIntSize(size); + result.mPrincipal = principal.forget(); + result.mIsWriteOnly = false; + + return result; +} + +nsLayoutUtils::SurfaceFromElementResult +nsLayoutUtils::SurfaceFromElement(dom::Element* aElement, + uint32_t aSurfaceFlags, + DrawTarget* 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