import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,133 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "AsyncScrollBase.h"
#include "gfxPrefs.h"
using namespace mozilla;
AsyncScrollBase::AsyncScrollBase(nsPoint aStartPos)
: mIsFirstIteration(true)
, mStartPos(aStartPos)
{
}
void
AsyncScrollBase::Update(TimeStamp aTime,
nsPoint aDestination,
const nsSize& aCurrentVelocity)
{
TimeDuration duration = ComputeDuration(aTime);
nsSize currentVelocity = aCurrentVelocity;
if (!mIsFirstIteration) {
// If an additional event has not changed the destination, then do not let
// another minimum duration reset slow things down. If it would then
// instead continue with the existing timing function.
if (aDestination == mDestination &&
aTime + duration > mStartTime + mDuration)
{
return;
}
currentVelocity = VelocityAt(aTime);
mStartPos = PositionAt(aTime);
}
mStartTime = aTime;
mDuration = duration;
mDestination = aDestination;
InitTimingFunction(mTimingFunctionX, mStartPos.x, currentVelocity.width,
aDestination.x);
InitTimingFunction(mTimingFunctionY, mStartPos.y, currentVelocity.height,
aDestination.y);
mIsFirstIteration = false;
}
TimeDuration
AsyncScrollBase::ComputeDuration(TimeStamp aTime)
{
// Average last 3 delta durations (rounding errors up to 2ms are negligible for us)
int32_t eventsDeltaMs = (aTime - mPrevEventTime[2]).ToMilliseconds() / 3;
mPrevEventTime[2] = mPrevEventTime[1];
mPrevEventTime[1] = mPrevEventTime[0];
mPrevEventTime[0] = aTime;
// Modulate duration according to events rate (quicker events -> shorter durations).
// The desired effect is to use longer duration when scrolling slowly, such that
// it's easier to follow, but reduce the duration to make it feel more snappy when
// scrolling quickly. To reduce fluctuations of the duration, we average event
// intervals using the recent 4 timestamps (now + three prev -> 3 intervals).
int32_t durationMS = clamped<int32_t>(eventsDeltaMs * mIntervalRatio, mOriginMinMS, mOriginMaxMS);
return TimeDuration::FromMilliseconds(durationMS);
}
void
AsyncScrollBase::InitializeHistory(TimeStamp aTime)
{
// Starting a new scroll (i.e. not when extending an existing scroll animation),
// create imaginary prev timestamps with maximum relevant intervals between them.
// Longest relevant interval (which results in maximum duration)
TimeDuration maxDelta = TimeDuration::FromMilliseconds(mOriginMaxMS / mIntervalRatio);
mPrevEventTime[0] = aTime - maxDelta;
mPrevEventTime[1] = mPrevEventTime[0] - maxDelta;
mPrevEventTime[2] = mPrevEventTime[1] - maxDelta;
}
void
AsyncScrollBase::InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos,
nscoord aCurrentVelocity,
nscoord aDestination)
{
if (aDestination == aCurrentPos || gfxPrefs::SmoothScrollCurrentVelocityWeighting() == 0) {
aTimingFunction.Init(0, 0, 1 - gfxPrefs::SmoothScrollStopDecelerationWeighting(), 1);
return;
}
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = aCurrentVelocity * (mDuration / oneSecond) / (aDestination - aCurrentPos);
double normalization = sqrt(1.0 + slope * slope);
double dt = 1.0 / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
double dxy = slope / normalization * gfxPrefs::SmoothScrollCurrentVelocityWeighting();
aTimingFunction.Init(dt, dxy, 1 - gfxPrefs::SmoothScrollStopDecelerationWeighting(), 1);
}
nsPoint
AsyncScrollBase::PositionAt(TimeStamp aTime) const
{
double progressX = mTimingFunctionX.GetSplineValue(ProgressAt(aTime));
double progressY = mTimingFunctionY.GetSplineValue(ProgressAt(aTime));
return nsPoint(NSToCoordRound((1 - progressX) * mStartPos.x + progressX * mDestination.x),
NSToCoordRound((1 - progressY) * mStartPos.y + progressY * mDestination.y));
}
nsSize
AsyncScrollBase::VelocityAt(TimeStamp aTime) const
{
double timeProgress = ProgressAt(aTime);
return nsSize(VelocityComponent(timeProgress, mTimingFunctionX,
mStartPos.x, mDestination.x),
VelocityComponent(timeProgress, mTimingFunctionY,
mStartPos.y, mDestination.y));
}
nscoord
AsyncScrollBase::VelocityComponent(double aTimeProgress,
const nsSMILKeySpline& aTimingFunction,
nscoord aStart,
nscoord aDestination) const
{
double dt, dxy;
aTimingFunction.GetSplineDerivativeValues(aTimeProgress, dt, dxy);
if (dt == 0)
return dxy >= 0 ? nscoord_MAX : nscoord_MIN;
const TimeDuration oneSecond = TimeDuration::FromSeconds(1);
double slope = dxy / dt;
return NSToCoordRound(slope * (aDestination - aStart) / (mDuration / oneSecond));
}

View file

@ -0,0 +1,105 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_layout_AsyncScrollBase_h_
#define mozilla_layout_AsyncScrollBase_h_
#include "mozilla/TimeStamp.h"
#include "nsPoint.h"
#include "nsSMILKeySpline.h"
namespace mozilla {
// This is the base class for driving scroll wheel animation on both the
// compositor and main thread.
class AsyncScrollBase
{
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
explicit AsyncScrollBase(nsPoint aStartPos);
void Update(TimeStamp aTime,
nsPoint aDestination,
const nsSize& aCurrentVelocity);
// Get the velocity at a point in time in nscoords/sec.
nsSize VelocityAt(TimeStamp aTime) const;
// Returns the expected scroll position at a given point in time, in app
// units, relative to the scroll frame.
nsPoint PositionAt(TimeStamp aTime) const;
bool IsFinished(TimeStamp aTime) {
return aTime > mStartTime + mDuration;
}
protected:
double ProgressAt(TimeStamp aTime) const {
return clamped((aTime - mStartTime) / mDuration, 0.0, 1.0);
}
nscoord VelocityComponent(double aTimeProgress,
const nsSMILKeySpline& aTimingFunction,
nscoord aStart, nscoord aDestination) const;
// Calculate duration, possibly dynamically according to events rate and
// event origin. (also maintain previous timestamps - which are only used
// here).
TimeDuration ComputeDuration(TimeStamp aTime);
// Initialize event history.
void InitializeHistory(TimeStamp aTime);
// Initializes the timing function in such a way that the current velocity is
// preserved.
void InitTimingFunction(nsSMILKeySpline& aTimingFunction,
nscoord aCurrentPos, nscoord aCurrentVelocity,
nscoord aDestination);
// mPrevEventTime holds previous 3 timestamps for intervals averaging (to
// reduce duration fluctuations). When AsyncScroll is constructed and no
// previous timestamps are available (indicated with mIsFirstIteration),
// initialize mPrevEventTime using imaginary previous timestamps with maximum
// relevant intervals between them.
TimeStamp mPrevEventTime[3];
bool mIsFirstIteration;
TimeStamp mStartTime;
// Cached Preferences value.
//
// These values are minimum and maximum animation duration per event origin,
// and a global ratio which defines how longer is the animation's duration
// compared to the average recent events intervals (such that for a relatively
// consistent events rate, the next event arrives before current animation ends)
int32_t mOriginMinMS;
int32_t mOriginMaxMS;
double mIntervalRatio;
nsPoint mStartPos;
TimeDuration mDuration;
nsPoint mDestination;
nsSMILKeySpline mTimingFunctionX;
nsSMILKeySpline mTimingFunctionY;
};
// Helper for accelerated wheel deltas. This can be called from the main thread
// or the APZ Controller thread.
static inline double
ComputeAcceleratedWheelDelta(double aDelta, int32_t aCounter, int32_t aFactor)
{
if (!aDelta) {
return aDelta;
}
return (aDelta * aCounter * double(aFactor) / 10);
}
static const uint32_t kScrollSeriesTimeoutMs = 80; // in milliseconds
} // namespace mozilla
#endif // mozilla_layout_AsyncScrollBase_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,396 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/* state used in reflow of block frames */
#ifndef BlockReflowInput_h
#define BlockReflowInput_h
#include "nsFloatManager.h"
#include "nsLineBox.h"
#include "mozilla/ReflowInput.h"
class nsBlockFrame;
class nsFrameList;
class nsOverflowContinuationTracker;
namespace mozilla {
// BlockReflowInput contains additional reflow input information that the
// block frame uses along with ReflowInput. Like ReflowInput, this
// is read-only data that is passed down from a parent frame to its children.
class BlockReflowInput {
// Block reflow input flags.
struct Flags {
Flags()
: mHasUnconstrainedBSize(false)
, mIsBStartMarginRoot(false)
, mIsBEndMarginRoot(false)
, mShouldApplyBStartMargin(false)
, mIsFirstInflow(false)
, mHasLineAdjacentToTop(false)
, mBlockNeedsFloatManager(false)
, mIsLineLayoutEmpty(false)
, mIsOverflowContainer(false)
, mIsFloatListInBlockPropertyTable(false)
, mFloatFragmentsInsideColumnEnabled(false)
{}
// Set in the BlockReflowInput constructor when the frame being reflowed has
// been given NS_UNCONSTRAINEDSIZE as its available BSize in the
// ReflowInput. If set, NS_UNCONSTRAINEDSIZE is passed to nsLineLayout as
// the available BSize.
bool mHasUnconstrainedBSize : 1;
// Set in the BlockReflowInput constructor when reflowing a "block margin
// root" frame (i.e. a frame with the NS_BLOCK_MARGIN_ROOT flag set, for
// which margins apply by default).
//
// The flag is also set when reflowing a frame whose computed BStart border
// padding is non-zero.
bool mIsBStartMarginRoot : 1;
// Set in the BlockReflowInput constructor when reflowing a "block margin
// root" frame (i.e. a frame with the NS_BLOCK_MARGIN_ROOT flag set, for
// which margins apply by default).
//
// The flag is also set when reflowing a frame whose computed BEnd border
// padding is non-zero.
bool mIsBEndMarginRoot : 1;
// Set if the BStart margin should be considered when placing a linebox that
// contains a block frame. It may be set as a side-effect of calling
// nsBlockFrame::ShouldApplyBStartMargin(); once set,
// ShouldApplyBStartMargin() uses it as a fast-path way to return whether
// the BStart margin should apply.
//
// If the flag hasn't been set in the block reflow input, then
// ShouldApplyBStartMargin() will crawl the line list to see if a block frame
// precedes the specified frame. If so, the BStart margin should be applied, and
// the flag is set to cache the result. (If not, the BStart margin will be
// applied as a result of the generational margin collapsing logic in
// nsBlockReflowContext::ComputeCollapsedBStartMargin(). In this case, the flag
// won't be set, so subsequent calls to ShouldApplyBStartMargin() will continue
// crawl the line list.)
//
// This flag is also set in the BlockReflowInput constructor if
// mIsBStartMarginRoot is set; that is, the frame being reflowed is a margin
// root by default.
bool mShouldApplyBStartMargin : 1;
bool mIsFirstInflow : 1;
// Set when mLineAdjacentToTop is valid.
bool mHasLineAdjacentToTop : 1;
// Set when the block has the equivalent of NS_BLOCK_FLOAT_MGR.
bool mBlockNeedsFloatManager : 1;
// Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
// the current line.
bool mIsLineLayoutEmpty : 1;
bool mIsOverflowContainer : 1;
// Set when our mPushedFloats list is stored on the block's property table.
bool mIsFloatListInBlockPropertyTable : 1;
// Set when the pref layout.float-fragments-inside-column.enabled is true.
bool mFloatFragmentsInsideColumnEnabled : 1;
};
public:
BlockReflowInput(const ReflowInput& aReflowInput,
nsPresContext* aPresContext,
nsBlockFrame* aFrame,
bool aBStartMarginRoot, bool aBEndMarginRoot,
bool aBlockNeedsFloatManager,
nscoord aConsumedBSize = NS_INTRINSICSIZE);
/**
* Get the available reflow space (the area not occupied by floats)
* for the current y coordinate. The available space is relative to
* our coordinate system, which is the content box, with (0, 0) in the
* upper left.
*
* Returns whether there are floats present at the given block-direction
* coordinate and within the inline size of the content rect.
*/
nsFlowAreaRect GetFloatAvailableSpace() const
{ return GetFloatAvailableSpace(mBCoord); }
nsFlowAreaRect GetFloatAvailableSpace(nscoord aBCoord) const
{ return GetFloatAvailableSpaceWithState(aBCoord, nullptr); }
nsFlowAreaRect
GetFloatAvailableSpaceWithState(nscoord aBCoord,
nsFloatManager::SavedState *aState) const;
nsFlowAreaRect
GetFloatAvailableSpaceForBSize(nscoord aBCoord, nscoord aBSize,
nsFloatManager::SavedState *aState) const;
/*
* The following functions all return true if they were able to
* place the float, false if the float did not fit in available
* space.
* aLineLayout is null when we are reflowing pushed floats (because
* they are not associated with a line box).
*/
bool AddFloat(nsLineLayout* aLineLayout,
nsIFrame* aFloat,
nscoord aAvailableISize);
bool FlowAndPlaceFloat(nsIFrame* aFloat);
void PlaceBelowCurrentLineFloats(nsFloatCacheFreeList& aFloats,
nsLineBox* aLine);
// Returns the first coordinate >= aBCoord that clears the
// floats indicated by aBreakType and has enough inline size between floats
// (or no floats remaining) to accomodate aReplacedBlock.
nscoord ClearFloats(nscoord aBCoord, mozilla::StyleClear aBreakType,
nsIFrame *aReplacedBlock = nullptr,
uint32_t aFlags = 0);
// Advances to the next band, i.e., the next horizontal stripe in
// which there is a different set of floats.
// Return false if it did not advance, which only happens for
// constrained heights (and means that we should get pushed to the
// next column/page).
bool AdvanceToNextBand(const mozilla::LogicalRect& aFloatAvailableSpace,
nscoord *aBCoord) const {
mozilla::WritingMode wm = mReflowInput.GetWritingMode();
if (aFloatAvailableSpace.BSize(wm) > 0) {
// See if there's room in the next band.
*aBCoord += aFloatAvailableSpace.BSize(wm);
} else {
if (mReflowInput.AvailableHeight() != NS_UNCONSTRAINEDSIZE) {
// Stop trying to clear here; we'll just get pushed to the
// next column or page and try again there.
return false;
}
NS_NOTREACHED("avail space rect with zero height!");
*aBCoord += 1;
}
return true;
}
bool ReplacedBlockFitsInAvailSpace(nsIFrame* aReplacedBlock,
const nsFlowAreaRect& aFloatAvailableSpace) const;
bool IsAdjacentWithTop() const {
return mBCoord == mBorderPadding.BStart(mReflowInput.GetWritingMode());
}
/**
* Return mBlock's computed physical border+padding with GetSkipSides applied.
*/
const mozilla::LogicalMargin& BorderPadding() const {
return mBorderPadding;
}
/**
* Retrieve the block-direction size "consumed" by any previous-in-flows.
*/
nscoord GetConsumedBSize();
// Reconstruct the previous block-end margin that goes before |aLine|.
void ReconstructMarginBefore(nsLineList::iterator aLine);
// Caller must have called GetAvailableSpace for the correct position
// (which need not be the current mBCoord).
void ComputeReplacedBlockOffsetsForFloats(nsIFrame* aFrame,
const mozilla::LogicalRect& aFloatAvailableSpace,
nscoord& aIStartResult,
nscoord& aIEndResult) const;
// Caller must have called GetAvailableSpace for the current mBCoord
void ComputeBlockAvailSpace(nsIFrame* aFrame,
const nsFlowAreaRect& aFloatAvailableSpace,
bool aBlockAvoidsFloats,
mozilla::LogicalRect& aResult);
void RecoverStateFrom(nsLineList::iterator aLine, nscoord aDeltaBCoord);
void AdvanceToNextLine() {
if (mFlags.mIsLineLayoutEmpty) {
mFlags.mIsLineLayoutEmpty = false;
} else {
mLineNumber++;
}
}
//----------------------------------------
// This state is the "global" state computed once for the reflow of
// the block.
// The block frame that is using this object
nsBlockFrame* mBlock;
nsPresContext* mPresContext;
const ReflowInput& mReflowInput;
nsFloatManager* mFloatManager;
// The coordinates within the float manager where the block is being
// placed <b>after</b> taking into account the blocks border and
// padding. This, therefore, represents the inner "content area" (in
// spacemanager coordinates) where child frames will be placed,
// including child blocks and floats.
nscoord mFloatManagerI, mFloatManagerB;
// XXX get rid of this
nsReflowStatus mReflowStatus;
// The float manager state as it was before the contents of this
// block. This is needed for positioning bullets, since we only want
// to move the bullet to flow around floats that were before this
// block, not floats inside of it.
nsFloatManager::SavedState mFloatManagerStateBefore;
nscoord mBEndEdge;
// The content area to reflow child frames within. This is within
// this frame's coordinate system and writing mode, which means
// mContentArea.IStart == BorderPadding().IStart and
// mContentArea.BStart == BorderPadding().BStart.
// The block size may be NS_UNCONSTRAINEDSIZE, which indicates that there
// is no page/column boundary below (the common case).
// mContentArea.BEnd() should only be called after checking that
// mContentArea.BSize is not NS_UNCONSTRAINEDSIZE; otherwise
// coordinate overflow may occur.
mozilla::LogicalRect mContentArea;
nscoord ContentIStart() const {
return mContentArea.IStart(mReflowInput.GetWritingMode());
}
nscoord ContentISize() const {
return mContentArea.ISize(mReflowInput.GetWritingMode());
}
nscoord ContentIEnd() const {
return mContentArea.IEnd(mReflowInput.GetWritingMode());
}
nscoord ContentBStart() const {
return mContentArea.BStart(mReflowInput.GetWritingMode());
}
nscoord ContentBSize() const {
return mContentArea.BSize(mReflowInput.GetWritingMode());
}
nscoord ContentBEnd() const {
return mContentArea.BEnd(mReflowInput.GetWritingMode());
}
mozilla::LogicalSize ContentSize(mozilla::WritingMode aWM) const {
mozilla::WritingMode wm = mReflowInput.GetWritingMode();
return mContentArea.Size(wm).ConvertTo(aWM, wm);
}
// Physical size. Use only for physical <-> logical coordinate conversion.
nsSize mContainerSize;
const nsSize& ContainerSize() const { return mContainerSize; }
// Continuation out-of-flow float frames that need to move to our
// next in flow are placed here during reflow. It's a pointer to
// a frame list stored in the block's property table.
nsFrameList *mPushedFloats;
// This method makes sure pushed floats are accessible to
// StealFrame. Call it before adding any frames to mPushedFloats.
void SetupPushedFloatList();
/**
* Append aFloatCont and its next-in-flows within the same block to
* mPushedFloats. aFloatCont should not be on any child list when
* making this call. Its next-in-flows will be removed from
* mBlock using StealFrame() before being added to mPushedFloats.
* All appended frames will be marked NS_FRAME_IS_PUSHED_FLOAT.
*/
void AppendPushedFloatChain(nsIFrame* aFloatCont);
// Track child overflow continuations.
nsOverflowContinuationTracker* mOverflowTracker;
//----------------------------------------
// This state is "running" state updated by the reflow of each line
// in the block. This same state is "recovered" when a line is not
// dirty and is passed over during incremental reflow.
// The current line being reflowed
// If it is mBlock->end_lines(), then it is invalid.
nsLineList::iterator mCurrentLine;
// When mHasLineAdjacentToTop is set, this refers to a line
// which we know is adjacent to the top of the block (in other words,
// all lines before it are empty and do not have clearance. This line is
// always before the current line.
nsLineList::iterator mLineAdjacentToTop;
// The current block-direction coordinate in the block
nscoord mBCoord;
// mBlock's computed physical border+padding with GetSkipSides applied.
mozilla::LogicalMargin mBorderPadding;
// The overflow areas of all floats placed so far
nsOverflowAreas mFloatOverflowAreas;
nsFloatCacheFreeList mFloatCacheFreeList;
// Previous child. This is used when pulling up a frame to update
// the sibling list.
nsIFrame* mPrevChild;
// The previous child frames collapsed bottom margin value.
nsCollapsingMargin mPrevBEndMargin;
// The current next-in-flow for the block. When lines are pulled
// from a next-in-flow, this is used to know which next-in-flow to
// pull from. When a next-in-flow is emptied of lines, we advance
// this to the next next-in-flow.
nsBlockFrame* mNextInFlow;
//----------------------------------------
// Temporary line-reflow state. This state is used during the reflow
// of a given line, but doesn't have meaning before or after.
// The list of floats that are "current-line" floats. These are
// added to the line after the line has been reflowed, to keep the
// list fiddling from being N^2.
nsFloatCacheFreeList mCurrentLineFloats;
// The list of floats which are "below current-line"
// floats. These are reflowed/placed after the line is reflowed
// and placed. Again, this is done to keep the list fiddling from
// being N^2.
nsFloatCacheFreeList mBelowCurrentLineFloats;
nscoord mMinLineHeight;
int32_t mLineNumber;
Flags mFlags;
StyleClear mFloatBreakType;
// The amount of computed block-direction size "consumed" by previous-in-flows.
nscoord mConsumedBSize;
// Cache the current line's BSize if nsBlockFrame::PlaceLine() fails to
// place the line. When redoing the line, it will be used to query the
// accurate float available space in AddFloat() and
// nsBlockFrame::PlaceLine().
mozilla::Maybe<nscoord> mLineBSize;
private:
bool CanPlaceFloat(nscoord aFloatISize,
const nsFlowAreaRect& aFloatAvailableSpace);
void PushFloatPastBreak(nsIFrame* aFloat);
void RecoverFloats(nsLineList::iterator aLine, nscoord aDeltaBCoord);
};
}; // namespace mozilla
#endif // BlockReflowInput_h

View file

@ -0,0 +1,159 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/* Utility code for performing CSS Box Alignment */
#include "CSSAlignUtils.h"
namespace mozilla {
static nscoord
SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin,
LogicalAxis aAxis, nscoord aCBSize)
{
nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM)
: aSize.ISize(aWM);
return aCBSize - (size + aMargin);
}
nscoord
CSSAlignUtils::AlignJustifySelf(uint8_t aAlignment, LogicalAxis aAxis,
AlignJustifyFlags aFlags,
nscoord aBaselineAdjust, nscoord aCBSize,
const ReflowInput& aRI,
const LogicalSize& aChildSize)
{
MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_AUTO,
"auto values should have resolved already");
MOZ_ASSERT(aAlignment != NS_STYLE_ALIGN_LEFT &&
aAlignment != NS_STYLE_ALIGN_RIGHT,
"caller should map that to the corresponding START/END");
// Promote aFlags to convenience bools:
const bool isOverflowSafe = !!(aFlags & AlignJustifyFlags::eOverflowSafe);
const bool isSameSide = !!(aFlags & AlignJustifyFlags::eSameSide);
// Map some alignment values to 'start' / 'end'.
switch (aAlignment) {
case NS_STYLE_ALIGN_SELF_START: // align/justify-self: self-start
aAlignment = MOZ_LIKELY(isSameSide) ? NS_STYLE_ALIGN_START
: NS_STYLE_ALIGN_END;
break;
case NS_STYLE_ALIGN_SELF_END: // align/justify-self: self-end
aAlignment = MOZ_LIKELY(isSameSide) ? NS_STYLE_ALIGN_END
: NS_STYLE_ALIGN_START;
break;
// flex-start/flex-end are the same as start/end, in most contexts.
// (They have special behavior in flex containers, so flex containers
// should map them to some other value before calling this method.)
case NS_STYLE_ALIGN_FLEX_START:
aAlignment = NS_STYLE_ALIGN_START;
break;
case NS_STYLE_ALIGN_FLEX_END:
aAlignment = NS_STYLE_ALIGN_END;
break;
}
// XXX try to condense this code a bit by adding the necessary convenience
// methods? (bug 1209710)
// Get the item's margin corresponding to the container's start/end side.
const LogicalMargin margin = aRI.ComputedLogicalMargin();
WritingMode wm = aRI.GetWritingMode();
nscoord marginStart, marginEnd;
if (aAxis == eLogicalAxisBlock) {
if (MOZ_LIKELY(isSameSide)) {
marginStart = margin.BStart(wm);
marginEnd = margin.BEnd(wm);
} else {
marginStart = margin.BEnd(wm);
marginEnd = margin.BStart(wm);
}
} else {
if (MOZ_LIKELY(isSameSide)) {
marginStart = margin.IStart(wm);
marginEnd = margin.IEnd(wm);
} else {
marginStart = margin.IEnd(wm);
marginEnd = margin.IStart(wm);
}
}
const auto& styleMargin = aRI.mStyleMargin->mMargin;
bool hasAutoMarginStart;
bool hasAutoMarginEnd;
if (aFlags & AlignJustifyFlags::eIgnoreAutoMargins) {
// (Note: ReflowInput will have treated "auto" margins as 0, so we
// don't need to do anything special to avoid expanding them.)
hasAutoMarginStart = hasAutoMarginEnd = false;
} else if (aAxis == eLogicalAxisBlock) {
hasAutoMarginStart = styleMargin.GetBStartUnit(wm) == eStyleUnit_Auto;
hasAutoMarginEnd = styleMargin.GetBEndUnit(wm) == eStyleUnit_Auto;
} else { /* aAxis == eLogicalAxisInline */
hasAutoMarginStart = styleMargin.GetIStartUnit(wm) == eStyleUnit_Auto;
hasAutoMarginEnd = styleMargin.GetIEndUnit(wm) == eStyleUnit_Auto;
}
// https://drafts.csswg.org/css-align-3/#overflow-values
// This implements <overflow-position> = 'safe'.
// And auto-margins: https://drafts.csswg.org/css-grid/#auto-margins
if ((MOZ_UNLIKELY(isOverflowSafe) && aAlignment != NS_STYLE_ALIGN_START) ||
hasAutoMarginStart || hasAutoMarginEnd) {
nscoord space = SpaceToFill(wm, aChildSize, marginStart + marginEnd,
aAxis, aCBSize);
// XXX we might want to include == 0 here as an optimization -
// I need to see what the baseline/last baseline code looks like first.
if (space < 0) {
// "Overflowing elements ignore their auto margins and overflow
// in the end directions"
aAlignment = NS_STYLE_ALIGN_START;
} else if (hasAutoMarginEnd) {
aAlignment = hasAutoMarginStart ? NS_STYLE_ALIGN_CENTER
: (isSameSide ? NS_STYLE_ALIGN_START
: NS_STYLE_ALIGN_END);
} else if (hasAutoMarginStart) {
aAlignment = isSameSide ? NS_STYLE_ALIGN_END : NS_STYLE_ALIGN_START;
}
}
// Determine the offset for the child frame (its border-box) which will
// achieve the requested alignment.
nscoord offset = 0;
switch (aAlignment) {
case NS_STYLE_ALIGN_BASELINE:
case NS_STYLE_ALIGN_LAST_BASELINE:
if (MOZ_LIKELY(isSameSide == (aAlignment == NS_STYLE_ALIGN_BASELINE))) {
offset = marginStart + aBaselineAdjust;
} else {
nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
: aChildSize.ISize(wm);
offset = aCBSize - (size + marginEnd) - aBaselineAdjust;
}
break;
case NS_STYLE_ALIGN_STRETCH:
MOZ_FALLTHROUGH; // ComputeSize() deals with it
case NS_STYLE_ALIGN_START:
offset = marginStart;
break;
case NS_STYLE_ALIGN_END: {
nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
: aChildSize.ISize(wm);
offset = aCBSize - (size + marginEnd);
break;
}
case NS_STYLE_ALIGN_CENTER: {
nscoord size = aAxis == eLogicalAxisBlock ? aChildSize.BSize(wm)
: aChildSize.ISize(wm);
offset = (aCBSize - size + marginStart - marginEnd) / 2;
break;
}
default:
MOZ_ASSERT_UNREACHABLE("unknown align-/justify-self value");
}
return offset;
}
} // namespace mozilla

View file

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/* Utility code for performing CSS Box Alignment */
#ifndef mozilla_CSSAlignUtils_h
#define mozilla_CSSAlignUtils_h
#include "mozilla/WritingModes.h"
namespace mozilla {
class CSSAlignUtils {
public:
/**
* Flags to customize the behavior of AlignJustifySelf:
*/
enum class AlignJustifyFlags {
eNoFlags = 0,
// Indicates that we have <overflow-position> = safe.
eOverflowSafe = 1 << 0,
// Indicates that the container's start side in aAxis is the same
// as the child's start side in the child's parallel axis.
eSameSide = 1 << 1,
// Indicates that AlignJustifySelf() shouldn't expand "auto" margins.
// (By default, AlignJustifySelf() *will* expand such margins, to fill the
// available space before any alignment is done.)
eIgnoreAutoMargins = 1 << 2,
};
/**
* This computes the aligned offset of a CSS-aligned child within its
* alignment container. The returned offset is distance between the
* logical "start" edge of the alignment container & the logical "start" edge
* of the aligned child (in terms of the alignment container's writing mode).
*
* @param aAlignment An enumerated value representing a keyword for
* "align-self" or "justify-self". The values
* NS_STYLE_ALIGN_{AUTO,LEFT,RIGHT} must *not* be
* passed here; this method expects the caller to have
* already resolved those to 'start', 'end', or 'stretch'.
* @param aAxis The container's axis in which we're doing alignment.
* @param aBaselineAdjust The amount to offset baseline-aligned children.
* @param aCBSize The size of the alignment container, in its aAxis.
* @param aRI A ReflowInput for the child.
* @param aChildSize The child's LogicalSize (in its own writing mode).
*/
static nscoord AlignJustifySelf(uint8_t aAlignment, LogicalAxis aAxis,
AlignJustifyFlags aFlags,
nscoord aBaselineAdjust, nscoord aCBSize,
const ReflowInput& aRI,
const LogicalSize& aChildSize);
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSAlignUtils::AlignJustifyFlags)
} // namespace mozilla
#endif // mozilla_CSSAlignUtils_h

View file

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "DetailsFrame.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "nsContentUtils.h"
#include "nsPlaceholderFrame.h"
#include "nsTextNode.h"
using namespace mozilla;
using namespace mozilla::dom;
NS_IMPL_FRAMEARENA_HELPERS(DetailsFrame)
NS_QUERYFRAME_HEAD(DetailsFrame)
NS_QUERYFRAME_ENTRY(DetailsFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
nsBlockFrame*
NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) DetailsFrame(aContext);
}
DetailsFrame::DetailsFrame(nsStyleContext* aContext)
: nsBlockFrame(aContext)
{
}
DetailsFrame::~DetailsFrame()
{
}
nsIAtom*
DetailsFrame::GetType() const
{
return nsGkAtoms::detailsFrame;
}
void
DetailsFrame::SetInitialChildList(ChildListID aListID, nsFrameList& aChildList)
{
#ifdef DEBUG
if (aListID == kPrincipalList) {
CheckValidMainSummary(aChildList);
}
#endif
nsBlockFrame::SetInitialChildList(aListID, aChildList);
}
#ifdef DEBUG
bool
DetailsFrame::CheckValidMainSummary(const nsFrameList& aFrameList) const
{
for (nsIFrame* child : aFrameList) {
HTMLSummaryElement* summary =
HTMLSummaryElement::FromContent(child->GetContent());
if (child == aFrameList.FirstChild()) {
if (summary && summary->IsMainSummary()) {
return true;
} else if (child->GetContent() == GetContent()) {
// The child frame's content is the same as our content, which means
// it's a kind of wrapper frame. Descend into its child list to find
// main summary.
if (CheckValidMainSummary(child->PrincipalChildList())) {
return true;
}
}
} else {
NS_ASSERTION(!summary || !summary->IsMainSummary(),
"Rest of the children are either not summary element "
"or are not the main summary!");
}
}
return false;
}
#endif
void
DetailsFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
nsContentUtils::DestroyAnonymousContent(&mDefaultSummary);
nsBlockFrame::DestroyFrom(aDestructRoot);
}
nsresult
DetailsFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
{
auto* details = HTMLDetailsElement::FromContent(GetContent());
if (details->GetFirstSummary()) {
return NS_OK;
}
// The <details> element lacks any direct <summary> child. Create a default
// <summary> element as an anonymous content.
nsNodeInfoManager* nodeInfoManager =
GetContent()->NodeInfo()->NodeInfoManager();
already_AddRefed<NodeInfo> nodeInfo =
nodeInfoManager->GetNodeInfo(nsGkAtoms::summary, nullptr, kNameSpaceID_XHTML,
nsIDOMNode::ELEMENT_NODE);
mDefaultSummary = new HTMLSummaryElement(nodeInfo);
nsXPIDLString defaultSummaryText;
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"DefaultSummary", defaultSummaryText);
RefPtr<nsTextNode> description = new nsTextNode(nodeInfoManager);
description->SetText(defaultSummaryText, false);
mDefaultSummary->AppendChildTo(description, false);
aElements.AppendElement(mDefaultSummary);
return NS_OK;
}
void
DetailsFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter)
{
if (mDefaultSummary) {
aElements.AppendElement(mDefaultSummary);
}
}

View file

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DetailsFrame_h
#define DetailsFrame_h
#include "nsBlockFrame.h"
#include "nsIAnonymousContentCreator.h"
class nsContainerFrame;
class nsStyleContext;
// DetailsFrame is generated by HTMLDetailsElement. See
// nsCSSFrameConstructor::ConstructDetailsFrame for the structure of a
// DetailsFrame.
//
class DetailsFrame final : public nsBlockFrame
, public nsIAnonymousContentCreator
{
public:
NS_DECL_FRAMEARENA_HELPERS
NS_DECL_QUERYFRAME_TARGET(DetailsFrame)
NS_DECL_QUERYFRAME
explicit DetailsFrame(nsStyleContext* aContext);
virtual ~DetailsFrame();
nsIAtom* GetType() const override;
#ifdef DEBUG_FRAME_DUMP
nsresult GetFrameName(nsAString& aResult) const override
{
return MakeFrameName(NS_LITERAL_STRING("Details"), aResult);
}
#endif
#ifdef DEBUG
// Check the frame of the main summary element is the first child in the frame
// list. Returns true if we found the main summary frame; false otherwise.
bool CheckValidMainSummary(const nsFrameList& aFrameList) const;
#endif
void SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) override;
void DestroyFrom(nsIFrame* aDestructRoot) override;
// nsIAnonymousContentCreator
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
private:
nsCOMPtr<nsIContent> mDefaultSummary;
};
#endif // DetailsFrame_h

View file

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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 "mozilla/layout/FrameChildList.h"
#include "nsIFrame.h"
namespace mozilla {
namespace layout {
FrameChildListIterator::FrameChildListIterator(const nsIFrame* aFrame)
: FrameChildListArrayIterator(mLists)
{
aFrame->GetChildLists(&mLists);
#ifdef DEBUG
// Make sure that there are no duplicate list IDs.
FrameChildListIDs ids;
uint32_t count = mLists.Length();
for (uint32_t i = 0; i < count; ++i) {
NS_ASSERTION(!ids.Contains(mLists[i].mID),
"Duplicate item found!");
ids |= mLists[i].mID;
}
#endif
}
#ifdef DEBUG_FRAME_DUMP
const char*
ChildListName(FrameChildListID aListID)
{
switch (aListID) {
case kPrincipalList: return "";
case kPopupList: return "PopupList";
case kCaptionList: return "CaptionList";
case kColGroupList: return "ColGroupList";
case kSelectPopupList: return "SelectPopupList";
case kAbsoluteList: return "AbsoluteList";
case kFixedList: return "FixedList";
case kOverflowList: return "OverflowList";
case kOverflowContainersList: return "OverflowContainersList";
case kExcessOverflowContainersList: return "ExcessOverflowContainersList";
case kOverflowOutOfFlowList: return "OverflowOutOfFlowList";
case kFloatList: return "FloatList";
case kBulletList: return "BulletList";
case kPushedFloatsList: return "PushedFloatsList";
case kBackdropList: return "BackdropList";
case kNoReflowPrincipalList: return "NoReflowPrincipalList";
}
NS_NOTREACHED("unknown list");
return "UNKNOWN_FRAME_CHILD_LIST";
}
#endif
} // namespace layout
} // namespace mozilla

View file

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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/. */
#ifndef FrameChildList_h_
#define FrameChildList_h_
#include "nsFrameList.h"
#include "nsTArray.h"
class nsIFrame;
namespace mozilla {
namespace layout {
// enum FrameChildListID lives in nsFrameList.h to solve circular dependencies.
#ifdef DEBUG_FRAME_DUMP
extern const char* ChildListName(FrameChildListID aListID);
#endif
class FrameChildListIDs {
friend class FrameChildListIterator;
public:
FrameChildListIDs() : mIDs(0) {}
FrameChildListIDs(const FrameChildListIDs& aOther) : mIDs(aOther.mIDs) {}
MOZ_IMPLICIT FrameChildListIDs(FrameChildListID aListID) : mIDs(aListID) {}
FrameChildListIDs operator|(FrameChildListIDs aOther) const {
return FrameChildListIDs(mIDs | aOther.mIDs);
}
FrameChildListIDs& operator|=(FrameChildListIDs aOther) {
mIDs |= aOther.mIDs;
return *this;
}
bool operator==(FrameChildListIDs aOther) const {
return mIDs == aOther.mIDs;
}
bool operator!=(FrameChildListIDs aOther) const {
return !(*this == aOther);
}
bool Contains(FrameChildListIDs aOther) const {
return (mIDs & aOther.mIDs) == aOther.mIDs;
}
protected:
explicit FrameChildListIDs(uint32_t aIDs) : mIDs(aIDs) {}
uint32_t mIDs;
};
class FrameChildList {
public:
FrameChildList(const nsFrameList& aList, FrameChildListID aID)
: mList(aList), mID(aID) {}
nsFrameList mList;
FrameChildListID mID;
};
/**
* A class to iterate frame child lists.
*/
class MOZ_STACK_CLASS FrameChildListArrayIterator {
public:
explicit FrameChildListArrayIterator(const nsTArray<FrameChildList>& aLists)
: mLists(aLists), mCurrentIndex(0) {}
bool IsDone() const { return mCurrentIndex >= mLists.Length(); }
FrameChildListID CurrentID() const {
NS_ASSERTION(!IsDone(), "CurrentID(): iterator at end");
return mLists[mCurrentIndex].mID;
}
const nsFrameList& CurrentList() const {
NS_ASSERTION(!IsDone(), "CurrentList(): iterator at end");
return mLists[mCurrentIndex].mList;
}
void Next() {
NS_ASSERTION(!IsDone(), "Next(): iterator at end");
++mCurrentIndex;
}
protected:
const nsTArray<FrameChildList>& mLists;
uint32_t mCurrentIndex;
};
/**
* A class for retrieving a frame's child lists and iterate them.
*/
class MOZ_STACK_CLASS FrameChildListIterator
: public FrameChildListArrayIterator {
public:
explicit FrameChildListIterator(const nsIFrame* aFrame);
protected:
AutoTArray<FrameChildList,4> mLists;
};
inline mozilla::layout::FrameChildListIDs
operator|(mozilla::layout::FrameChildListID aLeftOp,
mozilla::layout::FrameChildListID aRightOp)
{
return mozilla::layout::FrameChildListIDs(aLeftOp) |
mozilla::layout::FrameChildListIDs(aRightOp);
}
inline mozilla::layout::FrameChildListIDs
operator|(mozilla::layout::FrameChildListID aLeftOp,
mozilla::layout::FrameChildListIDs aRightOp)
{
return mozilla::layout::FrameChildListIDs(aLeftOp) | aRightOp;
}
} // namespace layout
} // namespace mozilla
inline void nsFrameList::AppendIfNonempty(
nsTArray<mozilla::layout::FrameChildList>* aLists,
mozilla::layout::FrameChildListID aListID) const
{
if (NotEmpty()) {
aLists->AppendElement(mozilla::layout::FrameChildList(*this, aListID));
}
}
#endif /* !defined(FrameChildList_h_) */

View file

@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_JustificationUtils_h_
#define mozilla_JustificationUtils_h_
#include "mozilla/Attributes.h"
#include "nsCoord.h"
namespace mozilla {
/**
* Jutification Algorithm
*
* The justification algorithm is based on expansion opportunities
* between justifiable clusters. By this algorithm, there is one
* expansion opportunity at each side of a justifiable cluster, and
* at most one opportunity between two clusters. For example, if there
* is a line in a Chinese document is: "你好世界hello world", then
* the expansion opportunities (marked as '*') would be:
*
* 你*好*世*界*hello*' '*world
*
* The spacing left in a line will then be distributed equally to each
* opportunities. Because we want that, only justifiable clusters get
* expanded, and the split point between two justifiable clusters would
* be at the middle of the spacing, each expansion opportunities will be
* filled by two justification gaps. The example above would be:
*
* 你 | 好 | 世 | 界 |hello| ' ' |world
*
* In the algorithm, information about expansion opportunities is stored
* in structure JustificationInfo, and the assignment of justification
* gaps is in structure JustificationAssignment.
*/
struct JustificationInfo
{
// Number of expansion opportunities inside a span. It doesn't include
// any opportunities between this span and the one before or after.
int32_t mInnerOpportunities;
// The justifiability of the start and end sides of the span.
bool mIsStartJustifiable;
bool mIsEndJustifiable;
constexpr JustificationInfo()
: mInnerOpportunities(0)
, mIsStartJustifiable(false)
, mIsEndJustifiable(false)
{
}
// Claim that the last opportunity should be cancelled
// because the trailing space just gets trimmed.
void CancelOpportunityForTrimmedSpace()
{
if (mInnerOpportunities > 0) {
mInnerOpportunities--;
} else {
// There is no inner opportunities, hence the whole frame must
// contain only the trimmed space, because any content before
// space would cause an inner opportunity. The space made each
// side justifiable, which should be cancelled now.
mIsStartJustifiable = false;
mIsEndJustifiable = false;
}
}
};
struct JustificationAssignment
{
// There are at most 2 gaps per end, so it is enough to use 2 bits.
uint8_t mGapsAtStart : 2;
uint8_t mGapsAtEnd : 2;
constexpr JustificationAssignment()
: mGapsAtStart(0)
, mGapsAtEnd(0)
{
}
int32_t TotalGaps() const { return mGapsAtStart + mGapsAtEnd; }
};
struct JustificationApplicationState
{
struct
{
// The total number of justification gaps to be processed.
int32_t mCount;
// The number of justification gaps which have been handled.
int32_t mHandled;
} mGaps;
struct
{
// The total spacing left in a line before justification.
nscoord mAvailable;
// The spacing has been consumed by handled justification gaps.
nscoord mConsumed;
} mWidth;
JustificationApplicationState(int32_t aGaps, nscoord aWidth)
{
mGaps.mCount = aGaps;
mGaps.mHandled = 0;
mWidth.mAvailable = aWidth;
mWidth.mConsumed = 0;
}
bool IsJustifiable() const
{
return mGaps.mCount > 0 && mWidth.mAvailable > 0;
}
nscoord Consume(int32_t aGaps)
{
mGaps.mHandled += aGaps;
nscoord newAllocate = (mWidth.mAvailable * mGaps.mHandled) / mGaps.mCount;
nscoord deltaWidth = newAllocate - mWidth.mConsumed;
mWidth.mConsumed = newAllocate;
return deltaWidth;
}
};
class JustificationUtils
{
public:
// Compute justification gaps should be applied on a unit.
static int32_t CountGaps(const JustificationInfo& aInfo,
const JustificationAssignment& aAssign)
{
// Justification gaps include two gaps for each inner opportunities
// and the gaps given assigned to the ends.
return aInfo.mInnerOpportunities * 2 + aAssign.TotalGaps();
}
};
} // namespace mozilla
#endif /* !defined(mozilla_JustificationUtils_h_) */

View file

@ -0,0 +1,801 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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 "MathMLTextRunFactory.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/BinarySearch.h"
#include "nsStyleConsts.h"
#include "nsTextFrameUtils.h"
#include "nsFontMetrics.h"
#include "nsDeviceContext.h"
#include "nsUnicodeScriptCodes.h"
using namespace mozilla;
/*
Entries for the mathvariant lookup tables. mKey represents the Unicode
character to be transformed and is used for searching the tables.
mReplacement represents the mapped mathvariant Unicode character.
*/
typedef struct
{
uint32_t mKey;
uint32_t mReplacement;
} MathVarMapping;
/*
Lookup tables for use with mathvariant mappings to transform a unicode
character point to another unicode character that indicates the proper output.
mKey represents one of two concepts.
1. In the Latin table it represents a hole in the mathematical alphanumeric
block, where the character that should occupy that position is located
elsewhere.
2. It represents an Arabic letter.
As a replacement, 0 is reserved to indicate no mapping was found.
*/
static const MathVarMapping gArabicInitialMapTable[] = {
{ 0x628, 0x1EE21 },
{ 0x62A, 0x1EE35 },
{ 0x62B, 0x1EE36 },
{ 0x62C, 0x1EE22 },
{ 0x62D, 0x1EE27 },
{ 0x62E, 0x1EE37 },
{ 0x633, 0x1EE2E },
{ 0x634, 0x1EE34 },
{ 0x635, 0x1EE31 },
{ 0x636, 0x1EE39 },
{ 0x639, 0x1EE2F },
{ 0x63A, 0x1EE3B },
{ 0x641, 0x1EE30 },
{ 0x642, 0x1EE32 },
{ 0x643, 0x1EE2A },
{ 0x644, 0x1EE2B },
{ 0x645, 0x1EE2C },
{ 0x646, 0x1EE2D },
{ 0x647, 0x1EE24 },
{ 0x64A, 0x1EE29 }
};
static const MathVarMapping gArabicTailedMapTable[] = {
{ 0x62C, 0x1EE42 },
{ 0x62D, 0x1EE47 },
{ 0x62E, 0x1EE57 },
{ 0x633, 0x1EE4E },
{ 0x634, 0x1EE54 },
{ 0x635, 0x1EE51 },
{ 0x636, 0x1EE59 },
{ 0x639, 0x1EE4F },
{ 0x63A, 0x1EE5B },
{ 0x642, 0x1EE52 },
{ 0x644, 0x1EE4B },
{ 0x646, 0x1EE4D },
{ 0x64A, 0x1EE49 },
{ 0x66F, 0x1EE5F },
{ 0x6BA, 0x1EE5D }
};
static const MathVarMapping gArabicStretchedMapTable[] = {
{ 0x628, 0x1EE61 },
{ 0x62A, 0x1EE75 },
{ 0x62B, 0x1EE76 },
{ 0x62C, 0x1EE62 },
{ 0x62D, 0x1EE67 },
{ 0x62E, 0x1EE77 },
{ 0x633, 0x1EE6E },
{ 0x634, 0x1EE74 },
{ 0x635, 0x1EE71 },
{ 0x636, 0x1EE79 },
{ 0x637, 0x1EE68 },
{ 0x638, 0x1EE7A },
{ 0x639, 0x1EE6F },
{ 0x63A, 0x1EE7B },
{ 0x641, 0x1EE70 },
{ 0x642, 0x1EE72 },
{ 0x643, 0x1EE6A },
{ 0x645, 0x1EE6C },
{ 0x646, 0x1EE6D },
{ 0x647, 0x1EE64 },
{ 0x64A, 0x1EE69 },
{ 0x66E, 0x1EE7C },
{ 0x6A1, 0x1EE7E }
};
static const MathVarMapping gArabicLoopedMapTable[] = {
{ 0x627, 0x1EE80 },
{ 0x628, 0x1EE81 },
{ 0x62A, 0x1EE95 },
{ 0x62B, 0x1EE96 },
{ 0x62C, 0x1EE82 },
{ 0x62D, 0x1EE87 },
{ 0x62E, 0x1EE97 },
{ 0x62F, 0x1EE83 },
{ 0x630, 0x1EE98 },
{ 0x631, 0x1EE93 },
{ 0x632, 0x1EE86 },
{ 0x633, 0x1EE8E },
{ 0x634, 0x1EE94 },
{ 0x635, 0x1EE91 },
{ 0x636, 0x1EE99 },
{ 0x637, 0x1EE88 },
{ 0x638, 0x1EE9A },
{ 0x639, 0x1EE8F },
{ 0x63A, 0x1EE9B },
{ 0x641, 0x1EE90 },
{ 0x642, 0x1EE92 },
{ 0x644, 0x1EE8B },
{ 0x645, 0x1EE8C },
{ 0x646, 0x1EE8D },
{ 0x647, 0x1EE84 },
{ 0x648, 0x1EE85 },
{ 0x64A, 0x1EE89 }
};
static const MathVarMapping gArabicDoubleMapTable[] = {
{ 0x628, 0x1EEA1 },
{ 0x62A, 0x1EEB5 },
{ 0x62B, 0x1EEB6 },
{ 0x62C, 0x1EEA2 },
{ 0x62D, 0x1EEA7 },
{ 0x62E, 0x1EEB7 },
{ 0x62F, 0x1EEA3 },
{ 0x630, 0x1EEB8 },
{ 0x631, 0x1EEB3 },
{ 0x632, 0x1EEA6 },
{ 0x633, 0x1EEAE },
{ 0x634, 0x1EEB4 },
{ 0x635, 0x1EEB1 },
{ 0x636, 0x1EEB9 },
{ 0x637, 0x1EEA8 },
{ 0x638, 0x1EEBA },
{ 0x639, 0x1EEAF },
{ 0x63A, 0x1EEBB },
{ 0x641, 0x1EEB0 },
{ 0x642, 0x1EEB2 },
{ 0x644, 0x1EEAB },
{ 0x645, 0x1EEAC },
{ 0x646, 0x1EEAD },
{ 0x648, 0x1EEA5 },
{ 0x64A, 0x1EEA9 }
};
static const MathVarMapping gLatinExceptionMapTable[] = {
{ 0x1D455, 0x210E },
{ 0x1D49D, 0x212C },
{ 0x1D4A0, 0x2130 },
{ 0x1D4A1, 0x2131 },
{ 0x1D4A3, 0x210B },
{ 0x1D4A4, 0x2110 },
{ 0x1D4A7, 0x2112 },
{ 0x1D4A8, 0x2133 },
{ 0x1D4AD, 0x211B },
{ 0x1D4BA, 0x212F },
{ 0x1D4BC, 0x210A },
{ 0x1D4C4, 0x2134 },
{ 0x1D506, 0x212D },
{ 0x1D50B, 0x210C },
{ 0x1D50C, 0x2111 },
{ 0x1D515, 0x211C },
{ 0x1D51D, 0x2128 },
{ 0x1D53A, 0x2102 },
{ 0x1D53F, 0x210D },
{ 0x1D545, 0x2115 },
{ 0x1D547, 0x2119 },
{ 0x1D548, 0x211A },
{ 0x1D549, 0x211D },
{ 0x1D551, 0x2124 }
};
namespace {
struct MathVarMappingWrapper
{
const MathVarMapping* const mTable;
explicit MathVarMappingWrapper(const MathVarMapping* aTable) : mTable(aTable) {}
uint32_t operator[](size_t index) const {
return mTable[index].mKey;
}
};
} // namespace
// Finds a MathVarMapping struct with the specified key (aKey) within aTable.
// aTable must be an array, whose length is specified by aNumElements
static uint32_t
MathvarMappingSearch(uint32_t aKey, const MathVarMapping* aTable, uint32_t aNumElements)
{
size_t index;
if (BinarySearch(MathVarMappingWrapper(aTable), 0, aNumElements, aKey, &index)) {
return aTable[index].mReplacement;
}
return 0;
}
#define GREEK_UPPER_THETA 0x03F4
#define HOLE_GREEK_UPPER_THETA 0x03A2
#define NABLA 0x2207
#define PARTIAL_DIFFERENTIAL 0x2202
#define GREEK_UPPER_ALPHA 0x0391
#define GREEK_UPPER_OMEGA 0x03A9
#define GREEK_LOWER_ALPHA 0x03B1
#define GREEK_LOWER_OMEGA 0x03C9
#define GREEK_LUNATE_EPSILON_SYMBOL 0x03F5
#define GREEK_THETA_SYMBOL 0x03D1
#define GREEK_KAPPA_SYMBOL 0x03F0
#define GREEK_PHI_SYMBOL 0x03D5
#define GREEK_RHO_SYMBOL 0x03F1
#define GREEK_PI_SYMBOL 0x03D6
#define GREEK_LETTER_DIGAMMA 0x03DC
#define GREEK_SMALL_LETTER_DIGAMMA 0x03DD
#define MATH_BOLD_CAPITAL_DIGAMMA 0x1D7CA
#define MATH_BOLD_SMALL_DIGAMMA 0x1D7CB
#define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
#define LATIN_SMALL_LETTER_DOTLESS_J 0x0237
#define MATH_ITALIC_SMALL_DOTLESS_I 0x1D6A4
#define MATH_ITALIC_SMALL_DOTLESS_J 0x1D6A5
#define MATH_BOLD_UPPER_A 0x1D400
#define MATH_ITALIC_UPPER_A 0x1D434
#define MATH_BOLD_SMALL_A 0x1D41A
#define MATH_BOLD_UPPER_ALPHA 0x1D6A8
#define MATH_BOLD_SMALL_ALPHA 0x1D6C2
#define MATH_ITALIC_UPPER_ALPHA 0x1D6E2
#define MATH_BOLD_DIGIT_ZERO 0x1D7CE
#define MATH_DOUBLE_STRUCK_ZERO 0x1D7D8
#define MATH_BOLD_UPPER_THETA 0x1D6B9
#define MATH_BOLD_NABLA 0x1D6C1
#define MATH_BOLD_PARTIAL_DIFFERENTIAL 0x1D6DB
#define MATH_BOLD_EPSILON_SYMBOL 0x1D6DC
#define MATH_BOLD_THETA_SYMBOL 0x1D6DD
#define MATH_BOLD_KAPPA_SYMBOL 0x1D6DE
#define MATH_BOLD_PHI_SYMBOL 0x1D6DF
#define MATH_BOLD_RHO_SYMBOL 0x1D6E0
#define MATH_BOLD_PI_SYMBOL 0x1D6E1
/*
Performs the character mapping needed to implement MathML's mathvariant
attribute. It takes a unicode character and maps it to its appropriate
mathvariant counterpart specified by aMathVar. The mapped character is
typically located within Unicode's mathematical blocks (0x1D***, 0x1EE**) but
there are exceptions which this function accounts for.
Characters without a valid mapping or valid aMathvar value are returned
unaltered. Characters already in the mathematical blocks (or are one of the
exceptions) are never transformed.
Acceptable values for aMathVar are specified in layout/style/nsStyleConsts.h.
The transformable characters can be found at:
http://lists.w3.org/Archives/Public/www-math/2013Sep/0012.html and
https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols
*/
static uint32_t
MathVariant(uint32_t aCh, uint8_t aMathVar)
{
uint32_t baseChar;
enum CharacterType {
kIsLatin,
kIsGreekish,
kIsNumber,
kIsArabic,
};
CharacterType varType;
int8_t multiplier;
if (aMathVar <= NS_MATHML_MATHVARIANT_NORMAL) {
// nothing to do here
return aCh;
}
if (aMathVar > NS_MATHML_MATHVARIANT_STRETCHED) {
NS_ASSERTION(false, "Illegal mathvariant value");
return aCh;
}
// Exceptional characters with at most one possible transformation
if (aCh == HOLE_GREEK_UPPER_THETA) {
// Nothing at this code point is transformed
return aCh;
}
if (aCh == GREEK_LETTER_DIGAMMA) {
if (aMathVar == NS_MATHML_MATHVARIANT_BOLD) {
return MATH_BOLD_CAPITAL_DIGAMMA;
}
return aCh;
}
if (aCh == GREEK_SMALL_LETTER_DIGAMMA) {
if (aMathVar == NS_MATHML_MATHVARIANT_BOLD) {
return MATH_BOLD_SMALL_DIGAMMA;
}
return aCh;
}
if (aCh == LATIN_SMALL_LETTER_DOTLESS_I) {
if (aMathVar == NS_MATHML_MATHVARIANT_ITALIC) {
return MATH_ITALIC_SMALL_DOTLESS_I;
}
return aCh;
}
if (aCh == LATIN_SMALL_LETTER_DOTLESS_J) {
if (aMathVar == NS_MATHML_MATHVARIANT_ITALIC) {
return MATH_ITALIC_SMALL_DOTLESS_J;
}
return aCh;
}
// The Unicode mathematical blocks are divided into four segments: Latin,
// Greek, numbers and Arabic. In the case of the first three
// baseChar represents the relative order in which the characters are
// encoded in the Unicode mathematical block, normalised to the first
// character of that sequence.
//
if ('A' <= aCh && aCh <= 'Z') {
baseChar = aCh - 'A';
varType = kIsLatin;
} else if ('a' <= aCh && aCh <= 'z') {
// Lowercase characters are placed immediately after the uppercase
// characters in the Unicode mathematical block. The constant subtraction
// represents the number of characters between the start of the sequence
// (capital A) and the first lowercase letter.
baseChar = MATH_BOLD_SMALL_A-MATH_BOLD_UPPER_A + aCh - 'a';
varType = kIsLatin;
} else if ('0' <= aCh && aCh <= '9') {
baseChar = aCh - '0';
varType = kIsNumber;
} else if (GREEK_UPPER_ALPHA <= aCh && aCh <= GREEK_UPPER_OMEGA) {
baseChar = aCh-GREEK_UPPER_ALPHA;
varType = kIsGreekish;
} else if (GREEK_LOWER_ALPHA <= aCh && aCh <= GREEK_LOWER_OMEGA) {
// Lowercase Greek comes after uppercase Greek.
// Note in this instance the presence of an additional character (Nabla)
// between the end of the uppercase Greek characters and the lowercase
// ones.
baseChar = MATH_BOLD_SMALL_ALPHA - MATH_BOLD_UPPER_ALPHA
+ aCh-GREEK_LOWER_ALPHA;
varType = kIsGreekish;
} else if (0x0600 <= aCh && aCh <= 0x06FF) {
// Arabic characters are defined within this range
varType = kIsArabic;
} else {
switch (aCh) {
case GREEK_UPPER_THETA:
baseChar = MATH_BOLD_UPPER_THETA-MATH_BOLD_UPPER_ALPHA;
break;
case NABLA:
baseChar = MATH_BOLD_NABLA-MATH_BOLD_UPPER_ALPHA;
break;
case PARTIAL_DIFFERENTIAL:
baseChar = MATH_BOLD_PARTIAL_DIFFERENTIAL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_LUNATE_EPSILON_SYMBOL:
baseChar = MATH_BOLD_EPSILON_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_THETA_SYMBOL:
baseChar = MATH_BOLD_THETA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_KAPPA_SYMBOL:
baseChar = MATH_BOLD_KAPPA_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_PHI_SYMBOL:
baseChar = MATH_BOLD_PHI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_RHO_SYMBOL:
baseChar = MATH_BOLD_RHO_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
case GREEK_PI_SYMBOL:
baseChar = MATH_BOLD_PI_SYMBOL - MATH_BOLD_UPPER_ALPHA;
break;
default:
return aCh;
}
varType = kIsGreekish;
}
if (varType == kIsNumber) {
switch (aMathVar) {
// Each possible number mathvariant is encoded in a single, contiguous
// block. For example the beginning of the double struck number range
// follows immediately after the end of the bold number range.
// multiplier represents the order of the sequences relative to the first
// one.
case NS_MATHML_MATHVARIANT_BOLD:
multiplier = 0;
break;
case NS_MATHML_MATHVARIANT_DOUBLE_STRUCK:
multiplier = 1;
break;
case NS_MATHML_MATHVARIANT_SANS_SERIF:
multiplier = 2;
break;
case NS_MATHML_MATHVARIANT_BOLD_SANS_SERIF:
multiplier = 3;
break;
case NS_MATHML_MATHVARIANT_MONOSPACE:
multiplier = 4;
break;
default:
// This mathvariant isn't defined for numbers or is otherwise normal
return aCh;
}
// As the ranges are contiguous, to find the desired mathvariant range it
// is sufficient to multiply the position within the sequence order
// (multiplier) with the period of the sequence (which is constant for all
// number sequences) and to add the character point of the first character
// within the number mathvariant range.
// To this the baseChar calculated earlier is added to obtain the final
// code point.
return baseChar+multiplier*(MATH_DOUBLE_STRUCK_ZERO-MATH_BOLD_DIGIT_ZERO)
+MATH_BOLD_DIGIT_ZERO;
} else if (varType == kIsGreekish) {
switch (aMathVar) {
case NS_MATHML_MATHVARIANT_BOLD:
multiplier = 0;
break;
case NS_MATHML_MATHVARIANT_ITALIC:
multiplier = 1;
break;
case NS_MATHML_MATHVARIANT_BOLD_ITALIC:
multiplier = 2;
break;
case NS_MATHML_MATHVARIANT_BOLD_SANS_SERIF:
multiplier = 3;
break;
case NS_MATHML_MATHVARIANT_SANS_SERIF_BOLD_ITALIC:
multiplier = 4;
break;
default:
// This mathvariant isn't defined for Greek or is otherwise normal
return aCh;
}
// See the kIsNumber case for an explanation of the following calculation
return baseChar + MATH_BOLD_UPPER_ALPHA +
multiplier*(MATH_ITALIC_UPPER_ALPHA - MATH_BOLD_UPPER_ALPHA);
}
uint32_t tempChar;
uint32_t newChar;
if (varType == kIsArabic) {
const MathVarMapping* mapTable;
uint32_t tableLength;
switch (aMathVar) {
/* The Arabic mathematical block is not continuous, nor does it have a
* monotonic mapping to the unencoded characters, requiring the use of a
* lookup table.
*/
case NS_MATHML_MATHVARIANT_INITIAL:
mapTable = gArabicInitialMapTable;
tableLength = ArrayLength(gArabicInitialMapTable);
break;
case NS_MATHML_MATHVARIANT_TAILED:
mapTable = gArabicTailedMapTable;
tableLength = ArrayLength(gArabicTailedMapTable);
break;
case NS_MATHML_MATHVARIANT_STRETCHED:
mapTable = gArabicStretchedMapTable;
tableLength = ArrayLength(gArabicStretchedMapTable);
break;
case NS_MATHML_MATHVARIANT_LOOPED:
mapTable = gArabicLoopedMapTable;
tableLength = ArrayLength(gArabicLoopedMapTable);
break;
case NS_MATHML_MATHVARIANT_DOUBLE_STRUCK:
mapTable = gArabicDoubleMapTable;
tableLength = ArrayLength(gArabicDoubleMapTable);
break;
default:
// No valid transformations exist
return aCh;
}
newChar = MathvarMappingSearch(aCh, mapTable, tableLength);
} else {
// Must be Latin
if (aMathVar > NS_MATHML_MATHVARIANT_MONOSPACE) {
// Latin doesn't support the Arabic mathvariants
return aCh;
}
multiplier = aMathVar - 2;
// This is possible because the values for NS_MATHML_MATHVARIANT_* are
// chosen to coincide with the order in which the encoded mathvariant
// characters are located within their unicode block (less an offset to
// avoid _NONE and _NORMAL variants)
// See the kIsNumber case for an explanation of the following calculation
tempChar = baseChar + MATH_BOLD_UPPER_A +
multiplier*(MATH_ITALIC_UPPER_A - MATH_BOLD_UPPER_A);
// There are roughly twenty characters that are located outside of the
// mathematical block, so the spaces where they ought to be are used
// as keys for a lookup table containing the correct character mappings.
newChar = MathvarMappingSearch(tempChar, gLatinExceptionMapTable,
ArrayLength(gLatinExceptionMapTable));
}
if (newChar) {
return newChar;
} else if (varType == kIsLatin) {
return tempChar;
} else {
// An Arabic character without a corresponding mapping
return aCh;
}
}
#define TT_SSTY TRUETYPE_TAG('s', 's', 't', 'y')
#define TT_DTLS TRUETYPE_TAG('d', 't', 'l', 's')
void
MathMLTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
mozilla::gfx::DrawTarget* aRefDrawTarget,
gfxMissingFontRecorder* aMFR)
{
gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
nsAutoString convertedString;
AutoTArray<bool,50> charsToMergeArray;
AutoTArray<bool,50> deletedCharsArray;
AutoTArray<RefPtr<nsTransformedCharStyle>,50> styleArray;
AutoTArray<uint8_t,50> canBreakBeforeArray;
bool mergeNeeded = false;
bool singleCharMI =
aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI;
uint32_t length = aTextRun->GetLength();
const char16_t* str = aTextRun->mString.BeginReading();
const nsTArray<RefPtr<nsTransformedCharStyle>>& styles = aTextRun->mStyles;
nsFont font;
if (length) {
font = styles[0]->mFont;
if (mSSTYScriptLevel || (mFlags & MATH_FONT_FEATURE_DTLS)) {
bool foundSSTY = false;
bool foundDTLS = false;
// We respect ssty settings explicitly set by the user
for (uint32_t i = 0; i < font.fontFeatureSettings.Length(); i++) {
if (font.fontFeatureSettings[i].mTag == TT_SSTY) {
foundSSTY = true;
} else if (font.fontFeatureSettings[i].mTag == TT_DTLS) {
foundDTLS = true;
}
}
if (mSSTYScriptLevel && !foundSSTY) {
uint8_t sstyLevel = 0;
float scriptScaling = pow(styles[0]->mScriptSizeMultiplier,
mSSTYScriptLevel);
static_assert(NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER < 1,
"Shouldn't it make things smaller?");
/*
An SSTY level of 2 is set if the scaling factor is less than or equal
to halfway between that for a scriptlevel of 1 (0.71) and that of a
scriptlevel of 2 (0.71^2), assuming the default script size multiplier.
An SSTY level of 1 is set if the script scaling factor is less than
or equal that for a scriptlevel of 1 assuming the default script size
multiplier.
User specified values of script size multiplier will change the scaling
factor which mSSTYScriptLevel values correspond to.
In the event that the script size multiplier actually makes things
larger, no change is made.
To opt out of this change, add the following to the stylesheet:
"font-feature-settings: 'ssty' 0"
*/
if (scriptScaling <= (NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER +
(NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER *
NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER))/2) {
// Currently only the first two ssty settings are used, so two is large
// as we go
sstyLevel = 2;
} else if (scriptScaling <= NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER) {
sstyLevel = 1;
}
if (sstyLevel) {
gfxFontFeature settingSSTY;
settingSSTY.mTag = TT_SSTY;
settingSSTY.mValue = sstyLevel;
font.fontFeatureSettings.AppendElement(settingSSTY);
}
}
/*
Apply the dtls font feature setting (dotless).
This gets applied to the base frame and all descendants of the base
frame of certain <mover> and <munderover> frames.
See nsMathMLmunderoverFrame.cpp for a full description.
To opt out of this change, add the following to the stylesheet:
"font-feature-settings: 'dtls' 0"
*/
if ((mFlags & MATH_FONT_FEATURE_DTLS) && !foundDTLS) {
gfxFontFeature settingDTLS;
settingDTLS.mTag = TT_DTLS;
settingDTLS.mValue = 1;
font.fontFeatureSettings.AppendElement(settingDTLS);
}
}
}
uint8_t mathVar = NS_MATHML_MATHVARIANT_NONE;
bool doMathvariantStyling = true;
for (uint32_t i = 0; i < length; ++i) {
int extraChars = 0;
mathVar = styles[i]->mMathVariant;
if (singleCharMI && mathVar == NS_MATHML_MATHVARIANT_NONE) {
// If the user has explicitly set a non-default value for fontstyle or
// fontweight, the italic mathvariant behaviour of <mi> is disabled
// This overrides the initial values specified in fontStyle, to avoid
// inconsistencies in which attributes allow CSS changes and which do not.
if (mFlags & MATH_FONT_WEIGHT_BOLD) {
font.weight = NS_FONT_WEIGHT_BOLD;
if (mFlags & MATH_FONT_STYLING_NORMAL) {
font.style = NS_FONT_STYLE_NORMAL;
} else {
font.style = NS_FONT_STYLE_ITALIC;
}
} else if (mFlags & MATH_FONT_STYLING_NORMAL) {
font.style = NS_FONT_STYLE_NORMAL;
font.weight = NS_FONT_WEIGHT_NORMAL;
} else {
mathVar = NS_MATHML_MATHVARIANT_ITALIC;
}
}
uint32_t ch = str[i];
if (NS_IS_HIGH_SURROGATE(ch) && i < length - 1 &&
NS_IS_LOW_SURROGATE(str[i + 1])) {
ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
}
uint32_t ch2 = MathVariant(ch, mathVar);
if (mathVar == NS_MATHML_MATHVARIANT_BOLD ||
mathVar == NS_MATHML_MATHVARIANT_BOLD_ITALIC ||
mathVar == NS_MATHML_MATHVARIANT_ITALIC) {
if (ch == ch2 && ch != 0x20 && ch != 0xA0) {
// Don't apply the CSS style if a character cannot be
// transformed. There is an exception for whitespace as it is both
// common and innocuous.
doMathvariantStyling = false;
}
if (ch2 != ch) {
// Bug 930504. Some platforms do not have fonts for Mathematical
// Alphanumeric Symbols. Hence we check whether the transformed
// character is actually available.
uint8_t matchType;
RefPtr<gfxFont> mathFont = fontGroup->
FindFontForChar(ch2, 0, 0, unicode::Script::COMMON, nullptr, &matchType);
if (mathFont) {
// Don't apply the CSS style if there is a math font for at least one
// of the transformed character in this text run.
doMathvariantStyling = false;
} else {
// We fallback to the original character.
ch2 = ch;
if (aMFR) {
aMFR->RecordScript(unicode::Script::MATHEMATICAL_NOTATION);
}
}
}
}
deletedCharsArray.AppendElement(false);
charsToMergeArray.AppendElement(false);
styleArray.AppendElement(styles[i]);
canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
if (IS_IN_BMP(ch2)) {
convertedString.Append(ch2);
} else {
convertedString.Append(H_SURROGATE(ch2));
convertedString.Append(L_SURROGATE(ch2));
++extraChars;
if (!IS_IN_BMP(ch)) {
deletedCharsArray.AppendElement(true); // not exactly deleted, but
// the trailing surrogate is skipped
++i;
}
}
while (extraChars-- > 0) {
mergeNeeded = true;
charsToMergeArray.AppendElement(true);
styleArray.AppendElement(styles[i]);
canBreakBeforeArray.AppendElement(false);
}
}
uint32_t flags;
gfxTextRunFactory::Parameters innerParams =
GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
RefPtr<nsTransformedTextRun> transformedChild;
RefPtr<gfxTextRun> cachedChild;
gfxTextRun* child;
if (mathVar == NS_MATHML_MATHVARIANT_BOLD && doMathvariantStyling) {
font.style = NS_FONT_STYLE_NORMAL;
font.weight = NS_FONT_WEIGHT_BOLD;
} else if (mathVar == NS_MATHML_MATHVARIANT_ITALIC && doMathvariantStyling) {
font.style = NS_FONT_STYLE_ITALIC;
font.weight = NS_FONT_WEIGHT_NORMAL;
} else if (mathVar == NS_MATHML_MATHVARIANT_BOLD_ITALIC &&
doMathvariantStyling) {
font.style = NS_FONT_STYLE_ITALIC;
font.weight = NS_FONT_WEIGHT_BOLD;
} else if (mathVar != NS_MATHML_MATHVARIANT_NONE) {
// Mathvariant overrides fontstyle and fontweight
// Need to check to see if mathvariant is actually applied as this function
// is used for other purposes.
font.style = NS_FONT_STYLE_NORMAL;
font.weight = NS_FONT_WEIGHT_NORMAL;
}
gfxFontGroup* newFontGroup = nullptr;
// Get the correct gfxFontGroup that corresponds to the earlier font changes.
if (length) {
font.size = NSToCoordRound(font.size * mFontInflation);
nsPresContext* pc = styles[0]->mPresContext;
nsFontMetrics::Params params;
params.language = styles[0]->mLanguage;
params.explicitLanguage = styles[0]->mExplicitLanguage;
params.userFontSet = pc->GetUserFontSet();
params.textPerf = pc->GetTextPerfMetrics();
RefPtr<nsFontMetrics> metrics =
pc->DeviceContext()->GetMetricsFor(font, params);
newFontGroup = metrics->GetThebesFontGroup();
}
if (!newFontGroup) {
// If we can't get a new font group, fall back to the old one. Rendering
// will be incorrect, but not significantly so.
newFontGroup = fontGroup;
}
if (mInnerTransformingTextRunFactory) {
transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(),
&innerParams, newFontGroup, flags, Move(styleArray), false);
child = transformedChild.get();
} else {
cachedChild = newFontGroup->MakeTextRun(
convertedString.BeginReading(), convertedString.Length(),
&innerParams, flags, aMFR);
child = cachedChild.get();
}
if (!child)
return;
typedef gfxTextRun::Range Range;
// Copy potential linebreaks into child so they're preserved
// (and also child will be shaped appropriately)
NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
"Dropped characters or break-before values somewhere!");
Range range(0, uint32_t(canBreakBeforeArray.Length()));
child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
if (transformedChild) {
transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
}
if (mergeNeeded) {
// Now merge multiple characters into one multi-glyph character as required
NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
"source length mismatch");
NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
"destination length mismatch");
MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
deletedCharsArray.Elements());
} else {
// No merging to do, so just copy; this produces a more optimized textrun.
// We can't steal the data because the child may be cached and stealing
// the data would break the cache.
aTextRun->ResetGlyphRuns();
aTextRun->CopyGlyphDataFrom(child, Range(child), 0);
}
}

View file

@ -0,0 +1,42 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#ifndef MATHMLTEXTRUNFACTORY_H_
#define MATHMLTEXTRUNFACTORY_H_
#include "mozilla/UniquePtr.h"
#include "nsTextRunTransformations.h"
/**
* Builds textruns that render their text with MathML specific renderings.
*/
class MathMLTextRunFactory : public nsTransformingTextRunFactory {
public:
MathMLTextRunFactory(UniquePtr<nsTransformingTextRunFactory> aInnerTransformingTextRunFactory,
uint32_t aFlags, uint8_t aSSTYScriptLevel,
float aFontInflation)
: mInnerTransformingTextRunFactory(Move(aInnerTransformingTextRunFactory)),
mFlags(aFlags),
mFontInflation(aFontInflation),
mSSTYScriptLevel(aSSTYScriptLevel) {}
virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
mozilla::gfx::DrawTarget* aRefDrawTarget,
gfxMissingFontRecorder* aMFR) override;
enum {
// Style effects which may override single character <mi> behaviour
MATH_FONT_STYLING_NORMAL = 0x1, // fontstyle="normal" has been set.
MATH_FONT_WEIGHT_BOLD = 0x2, // fontweight="bold" has been set.
MATH_FONT_FEATURE_DTLS = 0x4, // font feature dtls should be set
};
protected:
UniquePtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
uint32_t mFlags;
float mFontInflation;
uint8_t mSSTYScriptLevel;
};
#endif /*MATHMLTEXTRUNFACTORY_H_*/

File diff suppressed because it is too large Load diff

1017
layout/generic/ReflowInput.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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/. */
/* struct containing the output from nsIFrame::Reflow */
#include "mozilla/ReflowOutput.h"
#include "mozilla/ReflowInput.h"
void
nsOverflowAreas::UnionWith(const nsOverflowAreas& aOther)
{
// FIXME: We should probably change scrollable overflow to use
// UnionRectIncludeEmpty (but leave visual overflow using UnionRect).
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
mRects[otype].UnionRect(mRects[otype], aOther.mRects[otype]);
}
}
void
nsOverflowAreas::UnionAllWith(const nsRect& aRect)
{
// FIXME: We should probably change scrollable overflow to use
// UnionRectIncludeEmpty (but leave visual overflow using UnionRect).
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
mRects[otype].UnionRect(mRects[otype], aRect);
}
}
void
nsOverflowAreas::SetAllTo(const nsRect& aRect)
{
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
mRects[otype] = aRect;
}
}
ReflowOutput::ReflowOutput(const ReflowInput& aState,
uint32_t aFlags)
: mISize(0)
, mBSize(0)
, mBlockStartAscent(ASK_FOR_BASELINE)
, mFlags(aFlags)
, mWritingMode(aState.GetWritingMode())
{
}
void
ReflowOutput::SetOverflowAreasToDesiredBounds()
{
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
mOverflowAreas.Overflow(otype).SetRect(0, 0, Width(), Height());
}
}
void
ReflowOutput::UnionOverflowAreasWithDesiredBounds()
{
// FIXME: We should probably change scrollable overflow to use
// UnionRectIncludeEmpty (but leave visual overflow using UnionRect).
nsRect rect(0, 0, Width(), Height());
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect& o = mOverflowAreas.Overflow(otype);
o.UnionRect(o, rect);
}
}

View file

@ -0,0 +1,348 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/* struct containing the output from nsIFrame::Reflow */
#ifndef mozilla_ReflowOutput_h
#define mozilla_ReflowOutput_h
#include "mozilla/WritingModes.h"
#include "nsBoundingMetrics.h"
#include "nsRect.h"
//----------------------------------------------------------------------
namespace mozilla {
struct ReflowInput;
} // namespace mozilla
// Option flags
#define NS_REFLOW_CALC_BOUNDING_METRICS 0x0001
/**
* When we store overflow areas as an array of scrollable and visual
* overflow, we use these indices.
*
* eOverflowType_LENGTH is needed (for gcc 4.5.*, at least) to ensure
* that 2 is a valid value of nsOverflowType for use in
* NS_FOR_FRAME_OVERFLOW_TYPES.
*/
enum nsOverflowType { eVisualOverflow, eScrollableOverflow,
eOverflowType_LENGTH };
#define NS_FOR_FRAME_OVERFLOW_TYPES(var_) \
for (nsOverflowType var_ = nsOverflowType(0); var_ < 2; \
var_ = nsOverflowType(var_ + 1))
struct nsOverflowAreas {
private:
nsRect mRects[2];
public:
nsRect& Overflow(size_t aIndex) {
NS_ASSERTION(aIndex < 2, "index out of range");
return mRects[aIndex];
}
const nsRect& Overflow(size_t aIndex) const {
NS_ASSERTION(aIndex < 2, "index out of range");
return mRects[aIndex];
}
nsRect& VisualOverflow() { return mRects[eVisualOverflow]; }
const nsRect& VisualOverflow() const { return mRects[eVisualOverflow]; }
nsRect& ScrollableOverflow() { return mRects[eScrollableOverflow]; }
const nsRect& ScrollableOverflow() const { return mRects[eScrollableOverflow]; }
nsOverflowAreas() {
// default-initializes to zero due to nsRect's default constructor
}
nsOverflowAreas(const nsRect& aVisualOverflow,
const nsRect& aScrollableOverflow)
{
mRects[eVisualOverflow] = aVisualOverflow;
mRects[eScrollableOverflow] = aScrollableOverflow;
}
nsOverflowAreas(const nsOverflowAreas& aOther) {
*this = aOther;
}
nsOverflowAreas& operator=(const nsOverflowAreas& aOther) {
mRects[0] = aOther.mRects[0];
mRects[1] = aOther.mRects[1];
return *this;
}
bool operator==(const nsOverflowAreas& aOther) const {
// Scrollable overflow is a point-set rectangle and visual overflow
// is a pixel-set rectangle.
return VisualOverflow().IsEqualInterior(aOther.VisualOverflow()) &&
ScrollableOverflow().IsEqualEdges(aOther.ScrollableOverflow());
}
bool operator!=(const nsOverflowAreas& aOther) const {
return !(*this == aOther);
}
nsOverflowAreas operator+(const nsPoint& aPoint) const {
nsOverflowAreas result(*this);
result += aPoint;
return result;
}
nsOverflowAreas& operator+=(const nsPoint& aPoint) {
mRects[0] += aPoint;
mRects[1] += aPoint;
return *this;
}
void Clear() {
mRects[0].SetRect(0, 0, 0, 0);
mRects[1].SetRect(0, 0, 0, 0);
}
// Mutates |this| by unioning both overflow areas with |aOther|.
void UnionWith(const nsOverflowAreas& aOther);
// Mutates |this| by unioning both overflow areas with |aRect|.
void UnionAllWith(const nsRect& aRect);
// Mutates |this| by setting both overflow areas to |aRect|.
void SetAllTo(const nsRect& aRect);
};
/**
* An nsCollapsingMargin represents a vertical collapsing margin between
* blocks as described in section 8.3.1 of CSS2,
* <URL: http://www.w3.org/TR/REC-CSS2/box.html#collapsing-margins >.
*
* All adjacent vertical margins collapse, and the resulting margin is
* the sum of the largest positive margin included and the smallest (most
* negative) negative margin included.
*/
struct nsCollapsingMargin {
private:
nscoord mMostPos; // the largest positive margin included
nscoord mMostNeg; // the smallest negative margin included
public:
nsCollapsingMargin()
: mMostPos(0),
mMostNeg(0)
{
}
nsCollapsingMargin(const nsCollapsingMargin& aOther)
: mMostPos(aOther.mMostPos),
mMostNeg(aOther.mMostNeg)
{
}
bool operator==(const nsCollapsingMargin& aOther)
{
return mMostPos == aOther.mMostPos &&
mMostNeg == aOther.mMostNeg;
}
bool operator!=(const nsCollapsingMargin& aOther)
{
return !(*this == aOther);
}
nsCollapsingMargin& operator=(const nsCollapsingMargin& aOther)
{
mMostPos = aOther.mMostPos;
mMostNeg = aOther.mMostNeg;
return *this;
}
void Include(nscoord aCoord)
{
if (aCoord > mMostPos)
mMostPos = aCoord;
else if (aCoord < mMostNeg)
mMostNeg = aCoord;
}
void Include(const nsCollapsingMargin& aOther)
{
if (aOther.mMostPos > mMostPos)
mMostPos = aOther.mMostPos;
if (aOther.mMostNeg < mMostNeg)
mMostNeg = aOther.mMostNeg;
}
void Zero()
{
mMostPos = 0;
mMostNeg = 0;
}
bool IsZero() const
{
return (mMostPos == 0) && (mMostNeg == 0);
}
nscoord get() const
{
return mMostPos + mMostNeg;
}
};
namespace mozilla {
/**
* Reflow metrics used to return the frame's desired size and alignment
* information.
*
* @see #Reflow()
*/
class ReflowOutput {
public:
// XXXldb Should |aFlags| generally be passed from parent to child?
// Some places do it, and some don't. |aFlags| should perhaps go away
// entirely.
// XXX width/height/ascent are OUT parameters and so they shouldn't
// have to be initialized, but there are some bad frame classes that
// aren't properly setting them when returning from Reflow()...
explicit ReflowOutput(mozilla::WritingMode aWritingMode, uint32_t aFlags = 0)
: mISize(0)
, mBSize(0)
, mBlockStartAscent(ASK_FOR_BASELINE)
, mFlags(aFlags)
, mWritingMode(aWritingMode)
{}
explicit ReflowOutput(const ReflowInput& aState, uint32_t aFlags = 0);
// ISize and BSize are logical-coordinate dimensions:
// ISize is the size in the writing mode's inline direction (which equates to
// width in horizontal writing modes, height in vertical ones), and BSize is
// the size in the block-progression direction.
nscoord ISize(mozilla::WritingMode aWritingMode) const {
NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
"mismatched writing mode");
return mISize;
}
nscoord BSize(mozilla::WritingMode aWritingMode) const {
NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
"mismatched writing mode");
return mBSize;
}
mozilla::LogicalSize Size(mozilla::WritingMode aWritingMode) const {
NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
"mismatched writing mode");
return mozilla::LogicalSize(aWritingMode, mISize, mBSize);
}
nscoord& ISize(mozilla::WritingMode aWritingMode) {
NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
"mismatched writing mode");
return mISize;
}
nscoord& BSize(mozilla::WritingMode aWritingMode) {
NS_ASSERTION(!aWritingMode.IsOrthogonalTo(mWritingMode),
"mismatched writing mode");
return mBSize;
}
// Set inline and block size from a LogicalSize, converting to our
// writing mode as necessary.
void SetSize(mozilla::WritingMode aWM, mozilla::LogicalSize aSize)
{
mozilla::LogicalSize convertedSize = aSize.ConvertTo(mWritingMode, aWM);
mBSize = convertedSize.BSize(mWritingMode);
mISize = convertedSize.ISize(mWritingMode);
}
// Set both inline and block size to zero -- no need for a writing mode!
void ClearSize()
{
mISize = mBSize = 0;
}
// Width and Height are physical dimensions, independent of writing mode.
// Accessing these is slightly more expensive than accessing the logical
// dimensions (once vertical writing mode support is enabled); as far as
// possible, client code should work purely with logical dimensions.
nscoord Width() const { return mWritingMode.IsVertical() ? mBSize : mISize; }
nscoord Height() const { return mWritingMode.IsVertical() ? mISize : mBSize; }
// It's only meaningful to consider "ascent" on the block-start side of the
// frame, so no need to pass a writing mode argument
nscoord BlockStartAscent() const
{
return mBlockStartAscent;
}
nscoord& Width() { return mWritingMode.IsVertical() ? mBSize : mISize; }
nscoord& Height() { return mWritingMode.IsVertical() ? mISize : mBSize; }
nsSize PhysicalSize()
{
return Size(mWritingMode).GetPhysicalSize(mWritingMode);
}
void SetBlockStartAscent(nscoord aAscent)
{
mBlockStartAscent = aAscent;
}
enum { ASK_FOR_BASELINE = nscoord_MAX };
// Metrics that _exactly_ enclose the text to allow precise MathML placements.
// If the NS_REFLOW_CALC_BOUNDING_METRICS flag is set, then the caller is
// requesting that you also compute additional details about your inner
// bounding box and italic correction. For example, the bounding box of
// msup is the smallest rectangle that _exactly_ encloses both the text
// of the base and the text of the superscript.
nsBoundingMetrics mBoundingMetrics; // [OUT]
// Carried out block-end margin values. This is the collapsed
// (generational) block-end margin value.
nsCollapsingMargin mCarriedOutBEndMargin;
// For frames that have content that overflow their content area
// (HasOverflowAreas() is true) these rectangles represent the total
// area of the frame including visible overflow, i.e., don't include
// overflowing content that is hidden. The rects are in the local
// coordinate space of the frame, and should be at least as big as the
// desired size. If there is no content that overflows, then the
// overflow area is identical to the desired size and should be {0, 0,
// width, height}.
nsOverflowAreas mOverflowAreas;
nsRect& VisualOverflow()
{ return mOverflowAreas.VisualOverflow(); }
const nsRect& VisualOverflow() const
{ return mOverflowAreas.VisualOverflow(); }
nsRect& ScrollableOverflow()
{ return mOverflowAreas.ScrollableOverflow(); }
const nsRect& ScrollableOverflow() const
{ return mOverflowAreas.ScrollableOverflow(); }
// Set all of mOverflowAreas to (0, 0, width, height).
void SetOverflowAreasToDesiredBounds();
// Union all of mOverflowAreas with (0, 0, width, height).
void UnionOverflowAreasWithDesiredBounds();
mozilla::WritingMode GetWritingMode() const { return mWritingMode; }
private:
nscoord mISize, mBSize; // [OUT] desired width and height (border-box)
nscoord mBlockStartAscent; // [OUT] baseline (in Block direction), or ASK_FOR_BASELINE
public:
uint32_t mFlags;
private:
mozilla::WritingMode mWritingMode;
};
} // mozilla namespace
#endif // mozilla_ReflowOutput_h

View file

@ -0,0 +1,203 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RubyUtils.h"
#include "nsRubyFrame.h"
#include "nsRubyBaseFrame.h"
#include "nsRubyTextFrame.h"
#include "nsRubyBaseContainerFrame.h"
#include "nsRubyTextContainerFrame.h"
using namespace mozilla;
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ReservedISize, nscoord)
/* static */ void
RubyUtils::SetReservedISize(nsIFrame* aFrame, nscoord aISize)
{
MOZ_ASSERT(IsExpandableRubyBox(aFrame));
aFrame->Properties().Set(ReservedISize(), aISize);
}
/* static */ void
RubyUtils::ClearReservedISize(nsIFrame* aFrame)
{
MOZ_ASSERT(IsExpandableRubyBox(aFrame));
aFrame->Properties().Remove(ReservedISize());
}
/* static */ nscoord
RubyUtils::GetReservedISize(nsIFrame* aFrame)
{
MOZ_ASSERT(IsExpandableRubyBox(aFrame));
return aFrame->Properties().Get(ReservedISize());
}
AutoRubyTextContainerArray::AutoRubyTextContainerArray(
nsRubyBaseContainerFrame* aBaseContainer)
{
for (nsIFrame* frame = aBaseContainer->GetNextSibling();
frame && frame->GetType() == nsGkAtoms::rubyTextContainerFrame;
frame = frame->GetNextSibling()) {
AppendElement(static_cast<nsRubyTextContainerFrame*>(frame));
}
}
nsIFrame*
RubyColumn::Iterator::operator*() const
{
nsIFrame* frame;
if (mIndex == -1) {
frame = mColumn.mBaseFrame;
} else {
frame = mColumn.mTextFrames[mIndex];
}
MOZ_ASSERT(frame, "Frame here cannot be null");
return frame;
}
void
RubyColumn::Iterator::SkipUntilExistingFrame()
{
if (mIndex == -1) {
if (mColumn.mBaseFrame) {
return;
}
++mIndex;
}
int32_t numTextFrames = mColumn.mTextFrames.Length();
for (; mIndex < numTextFrames; ++mIndex) {
if (mColumn.mTextFrames[mIndex]) {
break;
}
}
}
RubySegmentEnumerator::RubySegmentEnumerator(nsRubyFrame* aRubyFrame)
{
nsIFrame* frame = aRubyFrame->PrincipalChildList().FirstChild();
MOZ_ASSERT(!frame ||
frame->GetType() == nsGkAtoms::rubyBaseContainerFrame);
mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
}
void
RubySegmentEnumerator::Next()
{
MOZ_ASSERT(mBaseContainer);
nsIFrame* frame = mBaseContainer->GetNextSibling();
while (frame && frame->GetType() != nsGkAtoms::rubyBaseContainerFrame) {
frame = frame->GetNextSibling();
}
mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
}
RubyColumnEnumerator::RubyColumnEnumerator(
nsRubyBaseContainerFrame* aBaseContainer,
const AutoRubyTextContainerArray& aTextContainers)
: mAtIntraLevelWhitespace(false)
{
const uint32_t rtcCount = aTextContainers.Length();
mFrames.SetCapacity(rtcCount + 1);
nsIFrame* rbFrame = aBaseContainer->PrincipalChildList().FirstChild();
MOZ_ASSERT(!rbFrame || rbFrame->GetType() == nsGkAtoms::rubyBaseFrame);
mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rbFrame));
for (uint32_t i = 0; i < rtcCount; i++) {
nsRubyTextContainerFrame* container = aTextContainers[i];
// If the container is for span, leave a nullptr here.
// Spans do not take part in pairing.
nsIFrame* rtFrame = !container->IsSpanContainer() ?
container->PrincipalChildList().FirstChild() : nullptr;
MOZ_ASSERT(!rtFrame || rtFrame->GetType() == nsGkAtoms::rubyTextFrame);
mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rtFrame));
}
// We have to init mAtIntraLevelWhitespace to be correct for the
// first column. There are two ways we could end up with intra-level
// whitespace in our first colum:
// 1. The current segment itself is an inter-segment whitespace;
// 2. If our ruby segment is split across multiple lines, and some
// intra-level whitespace happens to fall right after a line-break.
// Each line will get its own nsRubyBaseContainerFrame, and the
// container right after the line-break will end up with its first
// column containing that intra-level whitespace.
for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
nsRubyContentFrame* frame = mFrames[i];
if (frame && frame->IsIntraLevelWhitespace()) {
mAtIntraLevelWhitespace = true;
break;
}
}
}
void
RubyColumnEnumerator::Next()
{
bool advancingToIntraLevelWhitespace = false;
for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
nsRubyContentFrame* frame = mFrames[i];
// If we've got intra-level whitespace frames at some levels in the
// current ruby column, we "faked" an anonymous box for all other
// levels for this column. So when we advance off this column, we
// don't advance any of the frames in those levels, because we're
// just advancing across the "fake" frames.
if (frame && (!mAtIntraLevelWhitespace ||
frame->IsIntraLevelWhitespace())) {
nsIFrame* nextSibling = frame->GetNextSibling();
MOZ_ASSERT(!nextSibling || nextSibling->GetType() == frame->GetType(),
"Frame type should be identical among a level");
mFrames[i] = frame = static_cast<nsRubyContentFrame*>(nextSibling);
if (!advancingToIntraLevelWhitespace &&
frame && frame->IsIntraLevelWhitespace()) {
advancingToIntraLevelWhitespace = true;
}
}
}
MOZ_ASSERT(!advancingToIntraLevelWhitespace || !mAtIntraLevelWhitespace,
"Should never have adjacent intra-level whitespace columns");
mAtIntraLevelWhitespace = advancingToIntraLevelWhitespace;
}
bool
RubyColumnEnumerator::AtEnd() const
{
for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
if (mFrames[i]) {
return false;
}
}
return true;
}
nsRubyContentFrame*
RubyColumnEnumerator::GetFrameAtLevel(uint32_t aIndex) const
{
// If the current ruby column is for intra-level whitespaces, we
// return nullptr for any levels that do not have an actual intra-
// level whitespace frame in this column. This nullptr represents
// an anonymous empty intra-level whitespace box. (In this case,
// it's important that we NOT return mFrames[aIndex], because it's
// really part of the next column, not the current one.)
nsRubyContentFrame* frame = mFrames[aIndex];
return !mAtIntraLevelWhitespace ||
(frame && frame->IsIntraLevelWhitespace()) ? frame : nullptr;
}
void
RubyColumnEnumerator::GetColumn(RubyColumn& aColumn) const
{
nsRubyContentFrame* rbFrame = GetFrameAtLevel(0);
MOZ_ASSERT(!rbFrame || rbFrame->GetType() == nsGkAtoms::rubyBaseFrame);
aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(rbFrame);
aColumn.mTextFrames.ClearAndRetainStorage();
for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
nsRubyContentFrame* rtFrame = GetFrameAtLevel(i);
MOZ_ASSERT(!rtFrame || rtFrame->GetType() == nsGkAtoms::rubyTextFrame);
aColumn.mTextFrames.AppendElement(static_cast<nsRubyTextFrame*>(rtFrame));
}
aColumn.mIsIntraLevelWhitespace = mAtIntraLevelWhitespace;
}

234
layout/generic/RubyUtils.h Normal file
View file

@ -0,0 +1,234 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_RubyUtils_h_
#define mozilla_RubyUtils_h_
#include "nsTArray.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
#define RTC_ARRAY_SIZE 1
class nsRubyFrame;
class nsRubyBaseFrame;
class nsRubyTextFrame;
class nsRubyContentFrame;
class nsRubyBaseContainerFrame;
class nsRubyTextContainerFrame;
namespace mozilla {
/**
* Reserved ISize
*
* With some exceptions, each ruby internal box has two isizes, which
* are the reflowed isize and the final isize. The reflowed isize is
* what a box itself needs. It is determined when the box gets reflowed.
*
* The final isize is what a box should be as the final result. For a
* ruby base/text box, the final isize is the size of its ruby column.
* For a ruby base/text container, the final isize is the size of its
* ruby segment. The final isize is never smaller than the reflowed
* isize. It is initially determined when a ruby column/segment gets
* fully reflowed, and may be advanced when a box is expanded, e.g.
* for justification.
*
* The difference between the reflowed isize and the final isize is
* reserved in the line layout after reflowing a box, hence it is called
* "Reserved ISize" here. It is used to expand the ruby boxes from their
* reflowed isize to the final isize during alignment of the line.
*
* There are three exceptions for the final isize:
* 1. A ruby text container has a larger final isize only if it is for
* a span or collapsed annotations.
* 2. A ruby base container has a larger final isize only if at least
* one of its ruby text containers does.
* 3. If a ruby text container has a larger final isize, its children
* must not have.
*/
class RubyUtils
{
public:
static inline bool IsRubyContentBox(nsIAtom* aFrameType)
{
return aFrameType == nsGkAtoms::rubyBaseFrame ||
aFrameType == nsGkAtoms::rubyTextFrame;
}
static inline bool IsRubyContainerBox(nsIAtom* aFrameType)
{
return aFrameType == nsGkAtoms::rubyBaseContainerFrame ||
aFrameType == nsGkAtoms::rubyTextContainerFrame;
}
static inline bool IsRubyBox(nsIAtom* aFrameType)
{
return aFrameType == nsGkAtoms::rubyFrame ||
IsRubyContentBox(aFrameType) || IsRubyContainerBox(aFrameType);
}
static inline bool IsExpandableRubyBox(nsIFrame* aFrame)
{
nsIAtom* type = aFrame->GetType();
return IsRubyContentBox(type) || IsRubyContainerBox(type);
}
static inline bool IsRubyPseudo(nsIAtom* aPseudo)
{
return aPseudo == nsCSSAnonBoxes::ruby ||
aPseudo == nsCSSAnonBoxes::rubyBase ||
aPseudo == nsCSSAnonBoxes::rubyText ||
aPseudo == nsCSSAnonBoxes::rubyBaseContainer ||
aPseudo == nsCSSAnonBoxes::rubyTextContainer;
}
static void SetReservedISize(nsIFrame* aFrame, nscoord aISize);
static void ClearReservedISize(nsIFrame* aFrame);
static nscoord GetReservedISize(nsIFrame* aFrame);
};
/**
* This array stores all ruby text containers of the ruby segment
* of the given ruby base container.
*/
class MOZ_RAII AutoRubyTextContainerArray final
: public AutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE>
{
public:
explicit AutoRubyTextContainerArray(nsRubyBaseContainerFrame* aBaseContainer);
};
/**
* This enumerator enumerates each ruby segment.
*/
class MOZ_STACK_CLASS RubySegmentEnumerator
{
public:
explicit RubySegmentEnumerator(nsRubyFrame* aRubyFrame);
void Next();
bool AtEnd() const { return !mBaseContainer; }
nsRubyBaseContainerFrame* GetBaseContainer() const
{
return mBaseContainer;
}
private:
nsRubyBaseContainerFrame* mBaseContainer;
};
/**
* Ruby column is a unit consists of one ruby base and all ruby
* annotations paired with it.
* See http://dev.w3.org/csswg/css-ruby/#ruby-pairing
*/
struct MOZ_STACK_CLASS RubyColumn
{
nsRubyBaseFrame* mBaseFrame;
AutoTArray<nsRubyTextFrame*, RTC_ARRAY_SIZE> mTextFrames;
bool mIsIntraLevelWhitespace;
RubyColumn() : mBaseFrame(nullptr), mIsIntraLevelWhitespace(false) { }
// Helper class to support iteration across the frames within a single
// RubyColumn (the column's ruby base and its annotations).
class MOZ_STACK_CLASS Iterator
{
public:
nsIFrame* operator*() const;
Iterator& operator++() { ++mIndex; SkipUntilExistingFrame(); return *this; }
Iterator operator++(int) { auto ret = *this; ++*this; return ret; }
friend bool operator==(const Iterator& aIter1, const Iterator& aIter2)
{
MOZ_ASSERT(&aIter1.mColumn == &aIter2.mColumn,
"Should only compare iterators of the same ruby column");
return aIter1.mIndex == aIter2.mIndex;
}
friend bool operator!=(const Iterator& aIter1, const Iterator& aIter2)
{
return !(aIter1 == aIter2);
}
private:
Iterator(const RubyColumn& aColumn, int32_t aIndex)
: mColumn(aColumn)
, mIndex(aIndex)
{
MOZ_ASSERT(aIndex == -1 ||
(aIndex >= 0 &&
aIndex <= int32_t(aColumn.mTextFrames.Length())));
SkipUntilExistingFrame();
}
friend struct RubyColumn; // for the constructor
void SkipUntilExistingFrame();
const RubyColumn& mColumn;
// -1 means the ruby base frame,
// non-negative means the index of ruby text frame
// a value of mTextFrames.Length() means we're done iterating
int32_t mIndex = -1;
};
Iterator begin() const { return Iterator(*this, -1); }
Iterator end() const { return Iterator(*this, mTextFrames.Length()); }
Iterator cbegin() const { return begin(); }
Iterator cend() const { return end(); }
};
/**
* This enumerator enumerates ruby columns in a segment.
*/
class MOZ_STACK_CLASS RubyColumnEnumerator
{
public:
RubyColumnEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
const AutoRubyTextContainerArray& aRTCFrames);
void Next();
bool AtEnd() const;
uint32_t GetLevelCount() const { return mFrames.Length(); }
nsRubyContentFrame* GetFrameAtLevel(uint32_t aIndex) const;
void GetColumn(RubyColumn& aColumn) const;
private:
// Frames in this array are NOT necessary part of the current column.
// When in doubt, use GetFrameAtLevel to access it.
// See GetFrameAtLevel() and Next() for more info.
AutoTArray<nsRubyContentFrame*, RTC_ARRAY_SIZE + 1> mFrames;
// Whether we are on a column for intra-level whitespaces
bool mAtIntraLevelWhitespace;
};
/**
* Stores block-axis leadings produced from ruby annotations.
*/
struct RubyBlockLeadings
{
nscoord mStart = 0;
nscoord mEnd = 0;
void Reset() {
mStart = mEnd = 0;
}
void Update(nscoord aStart, nscoord aEnd) {
mStart = std::max(mStart, aStart);
mEnd = std::max(mEnd, aEnd);
}
void Update(const RubyBlockLeadings& aOther) {
Update(aOther.mStart, aOther.mEnd);
}
};
} // namespace mozilla
#endif /* !defined(mozilla_RubyUtils_h_) */

View file

@ -0,0 +1,311 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "FrameMetrics.h"
#include "ScrollSnap.h"
#include "gfxPrefs.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "nsLineLayout.h"
namespace mozilla {
using layers::ScrollSnapInfo;
/**
* Stores candidate snapping edges.
*/
class SnappingEdgeCallback {
public:
virtual void AddHorizontalEdge(nscoord aEdge) = 0;
virtual void AddVerticalEdge(nscoord aEdge) = 0;
virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval,
nscoord aOffset) = 0;
virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval,
nscoord aOffset) = 0;
};
/**
* Keeps track of the current best edge to snap to. The criteria for
* adding an edge depends on the scrolling unit.
*/
class CalcSnapPoints : public SnappingEdgeCallback {
public:
CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
const nsPoint& aDestination,
const nsPoint& aStartPos);
virtual void AddHorizontalEdge(nscoord aEdge) override;
virtual void AddVerticalEdge(nscoord aEdge) override;
virtual void AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
override;
virtual void AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
override;
void AddEdge(nscoord aEdge,
nscoord aDestination,
nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge,
bool* aEdgeFound);
void AddEdgeInterval(nscoord aInterval,
nscoord aMinPos,
nscoord aMaxPos,
nscoord aOffset,
nscoord aDestination,
nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge,
bool* aEdgeFound);
nsPoint GetBestEdge() const;
protected:
nsIScrollableFrame::ScrollUnit mUnit;
nsPoint mDestination; // gives the position after scrolling but before snapping
nsPoint mStartPos; // gives the position before scrolling
nsIntPoint mScrollingDirection; // always -1, 0, or 1
nsPoint mBestEdge; // keeps track of the position of the current best edge
bool mHorizontalEdgeFound; // true if mBestEdge.x is storing a valid horizontal edge
bool mVerticalEdgeFound; // true if mBestEdge.y is storing a valid vertical edge
};
CalcSnapPoints::CalcSnapPoints(nsIScrollableFrame::ScrollUnit aUnit,
const nsPoint& aDestination,
const nsPoint& aStartPos)
{
mUnit = aUnit;
mDestination = aDestination;
mStartPos = aStartPos;
nsPoint direction = aDestination - aStartPos;
mScrollingDirection = nsIntPoint(0,0);
if (direction.x < 0) {
mScrollingDirection.x = -1;
}
if (direction.x > 0) {
mScrollingDirection.x = 1;
}
if (direction.y < 0) {
mScrollingDirection.y = -1;
}
if (direction.y > 0) {
mScrollingDirection.y = 1;
}
mBestEdge = aDestination;
mHorizontalEdgeFound = false;
mVerticalEdgeFound = false;
}
nsPoint
CalcSnapPoints::GetBestEdge() const
{
return nsPoint(mVerticalEdgeFound ? mBestEdge.x : mStartPos.x,
mHorizontalEdgeFound ? mBestEdge.y : mStartPos.y);
}
void
CalcSnapPoints::AddHorizontalEdge(nscoord aEdge)
{
AddEdge(aEdge, mDestination.y, mStartPos.y, mScrollingDirection.y, &mBestEdge.y,
&mHorizontalEdgeFound);
}
void
CalcSnapPoints::AddVerticalEdge(nscoord aEdge)
{
AddEdge(aEdge, mDestination.x, mStartPos.x, mScrollingDirection.x, &mBestEdge.x,
&mVerticalEdgeFound);
}
void
CalcSnapPoints::AddHorizontalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
{
AddEdgeInterval(aInterval, aScrollRange.y, aScrollRange.YMost(), aOffset,
mDestination.y, mStartPos.y, mScrollingDirection.y,
&mBestEdge.y, &mHorizontalEdgeFound);
}
void
CalcSnapPoints::AddVerticalEdgeInterval(const nsRect &aScrollRange,
nscoord aInterval, nscoord aOffset)
{
AddEdgeInterval(aInterval, aScrollRange.x, aScrollRange.XMost(), aOffset,
mDestination.x, mStartPos.x, mScrollingDirection.x,
&mBestEdge.x, &mVerticalEdgeFound);
}
void
CalcSnapPoints::AddEdge(nscoord aEdge, nscoord aDestination, nscoord aStartPos,
nscoord aScrollingDirection, nscoord* aBestEdge,
bool *aEdgeFound)
{
// nsIScrollableFrame::DEVICE_PIXELS indicates that we are releasing a drag
// gesture or any other user input event that sets an absolute scroll
// position. In this case, scroll snapping is expected to travel in any
// direction. Otherwise, we will restrict the direction of the scroll
// snapping movement based on aScrollingDirection.
if (mUnit != nsIScrollableFrame::DEVICE_PIXELS) {
// Unless DEVICE_PIXELS, we only want to snap to points ahead of the
// direction we are scrolling
if (aScrollingDirection == 0) {
// The scroll direction is neutral - will not hit a snap point.
return;
}
// nsIScrollableFrame::WHOLE indicates that we are navigating to "home" or
// "end". In this case, we will always select the first or last snap point
// regardless of the direction of the scroll. Otherwise, we will select
// scroll snapping points only in the direction specified by
// aScrollingDirection.
if (mUnit != nsIScrollableFrame::WHOLE) {
// Direction of the edge from the current position (before scrolling) in
// the direction of scrolling
nscoord direction = (aEdge - aStartPos) * aScrollingDirection;
if (direction <= 0) {
// The edge is not in the direction we are scrolling, skip it.
return;
}
}
}
if (!*aEdgeFound) {
*aBestEdge = aEdge;
*aEdgeFound = true;
return;
}
if (mUnit == nsIScrollableFrame::DEVICE_PIXELS ||
mUnit == nsIScrollableFrame::LINES) {
if (std::abs(aEdge - aDestination) < std::abs(*aBestEdge - aDestination)) {
*aBestEdge = aEdge;
}
} else if (mUnit == nsIScrollableFrame::PAGES) {
// distance to the edge from the scrolling destination in the direction of scrolling
nscoord overshoot = (aEdge - aDestination) * aScrollingDirection;
// distance to the current best edge from the scrolling destination in the direction of scrolling
nscoord curOvershoot = (*aBestEdge - aDestination) * aScrollingDirection;
// edges between the current position and the scrolling destination are favoured
// to preserve context
if (overshoot < 0 && (overshoot > curOvershoot || curOvershoot >= 0)) {
*aBestEdge = aEdge;
}
// if there are no edges between the current position and the scrolling destination
// the closest edge beyond the destination is used
if (overshoot > 0 && overshoot < curOvershoot) {
*aBestEdge = aEdge;
}
} else if (mUnit == nsIScrollableFrame::WHOLE) {
// the edge closest to the top/bottom/left/right is used, depending on scrolling direction
if (aScrollingDirection > 0 && aEdge > *aBestEdge) {
*aBestEdge = aEdge;
} else if (aScrollingDirection < 0 && aEdge < *aBestEdge) {
*aBestEdge = aEdge;
}
} else {
NS_ERROR("Invalid scroll mode");
return;
}
}
void
CalcSnapPoints::AddEdgeInterval(nscoord aInterval, nscoord aMinPos,
nscoord aMaxPos, nscoord aOffset,
nscoord aDestination, nscoord aStartPos,
nscoord aScrollingDirection,
nscoord* aBestEdge, bool *aEdgeFound)
{
if (aInterval == 0) {
// When interval is 0, there are no scroll snap points.
// Avoid division by zero and bail.
return;
}
// The only possible candidate interval snap points are the edges immediately
// surrounding aDestination.
// aDestination must be clamped to the scroll
// range in order to handle cases where the best matching snap point would
// result in scrolling out of bounds. This clamping must be prior to
// selecting the two interval edges.
nscoord clamped = std::max(std::min(aDestination, aMaxPos), aMinPos);
// Add each edge in the interval immediately before aTarget and after aTarget
// Do not add edges that are out of range.
nscoord r = (clamped + aOffset) % aInterval;
if (r < aMinPos) {
r += aInterval;
}
nscoord edge = clamped - r;
if (edge >= aMinPos && edge <= aMaxPos) {
AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
aEdgeFound);
}
edge += aInterval;
if (edge >= aMinPos && edge <= aMaxPos) {
AddEdge(edge, aDestination, aStartPos, aScrollingDirection, aBestEdge,
aEdgeFound);
}
}
static void
ProcessScrollSnapCoordinates(SnappingEdgeCallback& aCallback,
const nsTArray<nsPoint>& aScrollSnapCoordinates,
const nsPoint& aScrollSnapDestination) {
for (nsPoint snapCoords : aScrollSnapCoordinates) {
// Make them relative to the scroll snap destination.
snapCoords -= aScrollSnapDestination;
aCallback.AddVerticalEdge(snapCoords.x);
aCallback.AddHorizontalEdge(snapCoords.y);
}
}
Maybe<nsPoint> ScrollSnapUtils::GetSnapPointForDestination(
const ScrollSnapInfo& aSnapInfo,
nsIScrollableFrame::ScrollUnit aUnit,
const nsSize& aScrollPortSize,
const nsRect& aScrollRange,
const nsPoint& aStartPos,
const nsPoint& aDestination)
{
if (aSnapInfo.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_NONE &&
aSnapInfo.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_NONE) {
return Nothing();
}
nsPoint destPos = aSnapInfo.mScrollSnapDestination;
CalcSnapPoints calcSnapPoints(aUnit, aDestination, aStartPos);
if (aSnapInfo.mScrollSnapIntervalX.isSome()) {
nscoord interval = aSnapInfo.mScrollSnapIntervalX.value();
calcSnapPoints.AddVerticalEdgeInterval(aScrollRange, interval, destPos.x);
}
if (aSnapInfo.mScrollSnapIntervalY.isSome()) {
nscoord interval = aSnapInfo.mScrollSnapIntervalY.value();
calcSnapPoints.AddHorizontalEdgeInterval(aScrollRange, interval, destPos.y);
}
ProcessScrollSnapCoordinates(calcSnapPoints, aSnapInfo.mScrollSnapCoordinates, destPos);
bool snapped = false;
nsPoint finalPos = calcSnapPoints.GetBestEdge();
nscoord proximityThreshold = gfxPrefs::ScrollSnapProximityThreshold();
proximityThreshold = nsPresContext::CSSPixelsToAppUnits(proximityThreshold);
if (aSnapInfo.mScrollSnapTypeY == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
std::abs(aDestination.y - finalPos.y) > proximityThreshold) {
finalPos.y = aDestination.y;
} else {
snapped = true;
}
if (aSnapInfo.mScrollSnapTypeX == NS_STYLE_SCROLL_SNAP_TYPE_PROXIMITY &&
std::abs(aDestination.x - finalPos.x) > proximityThreshold) {
finalPos.x = aDestination.x;
} else {
snapped = true;
}
return snapped ? Some(finalPos) : Nothing();
}
} // namespace mozilla

View file

@ -0,0 +1,41 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_layout_ScrollSnap_h_
#define mozilla_layout_ScrollSnap_h_
namespace mozilla {
namespace layers {
struct ScrollSnapInfo;
}
struct ScrollSnapUtils {
/**
* GetSnapPointForDestination determines which point to snap to after
* scrolling. |aStartPos| gives the position before scrolling and
* |aDestination| gives the position after scrolling, with no snapping.
* Behaviour is dependent on the value of |aUnit|.
* |aSnapInfo|, |aScrollPortSize|, and |aScrollRange| are characteristics
* of the scroll frame for which snapping is being performed.
* If a suitable snap point could be found, it is returned. Otherwise, an
* empty Maybe is returned.
* IMPORTANT NOTE: This function is designed to be called both on and off
* the main thread. If modifying its implementation, be sure
* not to touch main-thread-only data structures without
* appropriate locking.
*/
static Maybe<nsPoint> GetSnapPointForDestination(
const layers::ScrollSnapInfo& aSnapInfo,
nsIScrollableFrame::ScrollUnit aUnit,
const nsSize& aScrollPortSize,
const nsRect& aScrollRange,
const nsPoint& aStartPos,
const nsPoint& aDestination);
};
} // namespace mozilla
#endif // mozilla_layout_ScrollSnap_h_

View file

@ -0,0 +1,97 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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 "ScrollVelocityQueue.h"
#include "gfxPrefs.h"
#include "nsPresContext.h"
#include "nsRefreshDriver.h"
namespace mozilla {
namespace layout {
void
ScrollVelocityQueue::Sample(const nsPoint& aScrollPosition)
{
float flingSensitivity = gfxPrefs::ScrollSnapPredictionSensitivity();
int maxVelocity = gfxPrefs::ScrollSnapPredictionMaxVelocity();
maxVelocity = nsPresContext::CSSPixelsToAppUnits(maxVelocity);
int maxOffset = maxVelocity * flingSensitivity;
TimeStamp currentRefreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
if (mSampleTime.IsNull()) {
mAccumulator = nsPoint();
} else {
uint32_t durationMs = (currentRefreshTime - mSampleTime).ToMilliseconds();
if (durationMs > gfxPrefs::APZVelocityRelevanceTime()) {
mAccumulator = nsPoint();
mQueue.Clear();
} else if (durationMs == 0) {
mAccumulator += aScrollPosition - mLastPosition;
} else {
nsPoint velocity = mAccumulator * 1000 / durationMs;
velocity.Clamp(maxVelocity);
mQueue.AppendElement(std::make_pair(durationMs, velocity));
mAccumulator = aScrollPosition - mLastPosition;
}
}
mAccumulator.Clamp(maxOffset);
mSampleTime = currentRefreshTime;
mLastPosition = aScrollPosition;
TrimQueue();
}
void
ScrollVelocityQueue::TrimQueue()
{
if (mSampleTime.IsNull()) {
// There are no samples, nothing to do here.
return;
}
TimeStamp currentRefreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
nsPoint velocity;
uint32_t timeDelta = (currentRefreshTime - mSampleTime).ToMilliseconds();
for (int i = mQueue.Length() - 1; i >= 0; i--) {
timeDelta += mQueue[i].first;
if (timeDelta >= gfxPrefs::APZVelocityRelevanceTime()) {
// The rest of the samples have expired and should be dropped
for (; i >= 0; i--) {
mQueue.RemoveElementAt(0);
}
}
}
}
void
ScrollVelocityQueue::Reset()
{
mAccumulator = nsPoint();
mSampleTime = TimeStamp();
mQueue.Clear();
}
/**
Calculate the velocity of the scroll frame, in appunits / second.
*/
nsPoint
ScrollVelocityQueue::GetVelocity()
{
TrimQueue();
if (mQueue.Length() == 0) {
// If getting the scroll velocity before any scrolling has occurred,
// the velocity must be (0, 0)
return nsPoint();
}
nsPoint velocity;
for (int i = mQueue.Length() - 1; i >= 0; i--) {
velocity += mQueue[i].second;
}
return velocity / mQueue.Length();;
}
} // namespace layout
} // namespace mozilla

View file

@ -0,0 +1,92 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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/. */
#ifndef ScrollVelocityQueue_h_
#define ScrollVelocityQueue_h_
#include "nsTArray.h"
#include "nsPoint.h"
#include "mozilla/TimeStamp.h"
class nsPresContext;
namespace mozilla {
namespace layout {
/**
* ScrollVelocityQueue is used to determine the current velocity of a
* scroll frame, derived from scroll position samples.
*
* Using the last iteration's scroll position, stored in mLastPosition, a
* delta of the scroll position is calculated and accumulated in mAccumulator
* until the refresh driver returns a new timestamp for MostRecentRefresh().
*
* When there is a new timestamp from the refresh driver, the accumulated
* change in scroll position is divided by the delta of the timestamp to
* get an average velocity over that period. This velocity is pushed into
* mQueue as a std::pair associating each velocity with the
* duration over which it was sampled.
*
* Samples are removed from mQueue, leaving only those necessary to determine
* the average velocity over the recent relevant period, which has a duration
* set by the apz.velocity_relevance_time_ms preference.
*
* The velocity of each sample is clamped to a value set by the
* layout.css.scroll-snap.prediction-max-velocity.
*
* As the average velocity will later be integrated over a duration set by
* the layout.css.scroll-snap.prediction-sensitivity preference and the
* velocity samples are clamped to a set value, the maximum expected scroll
* offset can be calculated. This maximum offset is used to clamp
* mAccumulator, eliminating samples that would otherwise result in scroll
* snap position selection that is not consistent with the user's perception
* of scroll velocity.
*/
class ScrollVelocityQueue final {
public:
explicit ScrollVelocityQueue(nsPresContext *aPresContext)
: mPresContext(aPresContext) {}
// Sample() is to be called periodically when scroll movement occurs, to
// record samples of scroll position used later by GetVelocity().
void Sample(const nsPoint& aScrollPosition);
// Discards velocity samples, resulting in velocity of 0 returned by
// GetVelocity until move scroll position updates.
void Reset();
// Get scroll velocity averaged from recent movement, in appunits / second
nsPoint GetVelocity();
private:
// A queue of (duration, velocity) pairs; these are the historical average
// velocities over the given durations. Durations are in milliseconds,
// velocities are in app units per second.
nsTArray<std::pair<uint32_t, nsPoint> > mQueue;
// Accumulates the distance and direction travelled by the scroll frame since
// mSampleTime.
nsPoint mAccumulator;
// Time that mAccumulator was last reset and began accumulating.
TimeStamp mSampleTime;
// Scroll offset at the mAccumulator was last reset and began
// accumulating.
nsPoint mLastPosition;
// PresContext of the containing frame, used to get timebase
nsPresContext* mPresContext;
// Remove samples from mQueue that no longer contribute to GetVelocity()
// due to their age
void TrimQueue();
};
} // namespace layout
} // namespace mozilla
#endif /* !defined(ScrollVelocityQueue_h_) */

View file

@ -0,0 +1,469 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "ScrollbarActivity.h"
#include "nsIScrollbarMediator.h"
#include "nsIContent.h"
#include "nsICSSDeclaration.h"
#include "nsIDOMEvent.h"
#include "nsIDOMCSSStyleDeclaration.h"
#include "nsIFrame.h"
#include "nsContentUtils.h"
#include "nsAString.h"
#include "nsQueryFrame.h"
#include "nsComponentManagerUtils.h"
#include "nsStyledElement.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Preferences.h"
namespace mozilla {
namespace layout {
NS_IMPL_ISUPPORTS(ScrollbarActivity, nsIDOMEventListener)
static bool
GetForceAlwaysVisiblePref()
{
static bool sForceAlwaysVisible;
static bool sForceAlwaysVisiblePrefCached = false;
if (!sForceAlwaysVisiblePrefCached) {
Preferences::AddBoolVarCache(&sForceAlwaysVisible,
"layout.testing.overlay-scrollbars.always-visible");
sForceAlwaysVisiblePrefCached = true;
}
return sForceAlwaysVisible;
}
void
ScrollbarActivity::QueryLookAndFeelVals()
{
// Fade animation constants
mScrollbarFadeBeginDelay =
LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeBeginDelay);
mScrollbarFadeDuration =
LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarFadeDuration);
// Controls whether we keep the mouse move listener so we can display the
// scrollbars whenever the user moves the mouse within the scroll area.
mDisplayOnMouseMove =
LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollbarDisplayOnMouseMove);
}
void
ScrollbarActivity::Destroy()
{
StopListeningForScrollbarEvents();
StopListeningForScrollAreaEvents();
UnregisterFromRefreshDriver();
CancelFadeBeginTimer();
}
void
ScrollbarActivity::ActivityOccurred()
{
ActivityStarted();
ActivityStopped();
}
void
ScrollbarActivity::ActivityStarted()
{
mNestedActivityCounter++;
CancelFadeBeginTimer();
if (!SetIsFading(false)) {
return;
}
UnregisterFromRefreshDriver();
StartListeningForScrollbarEvents();
StartListeningForScrollAreaEvents();
SetIsActive(true);
NS_ASSERTION(mIsActive, "need to be active during activity");
NS_ASSERTION(!mIsFading, "must not be fading during activity");
}
void
ScrollbarActivity::ActivityStopped()
{
if (!IsActivityOngoing()) {
// This can happen if there was a frame reconstruction while the activity
// was ongoing. In this case we just do nothing. We should probably handle
// this case better.
return;
}
NS_ASSERTION(mIsActive, "need to be active during activity");
NS_ASSERTION(!mIsFading, "must not be fading during ongoing activity");
mNestedActivityCounter--;
if (!IsActivityOngoing()) {
StartFadeBeginTimer();
NS_ASSERTION(mIsActive, "need to be active right after activity");
NS_ASSERTION(!mIsFading, "must not be fading right after activity");
}
}
NS_IMETHODIMP
ScrollbarActivity::HandleEvent(nsIDOMEvent* aEvent)
{
if (!mDisplayOnMouseMove && !mIsActive)
return NS_OK;
nsAutoString type;
aEvent->GetType(type);
if (type.EqualsLiteral("mousemove")) {
// Mouse motions anywhere in the scrollable frame should keep the
// scrollbars visible.
ActivityOccurred();
return NS_OK;
}
nsCOMPtr<nsIDOMEventTarget> target;
aEvent->GetOriginalTarget(getter_AddRefs(target));
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
HandleEventForScrollbar(type, targetContent, GetHorizontalScrollbar(),
&mHScrollbarHovered);
HandleEventForScrollbar(type, targetContent, GetVerticalScrollbar(),
&mVScrollbarHovered);
return NS_OK;
}
void
ScrollbarActivity::WillRefresh(TimeStamp aTime)
{
NS_ASSERTION(mIsActive, "should only fade while scrollbars are visible");
NS_ASSERTION(!IsActivityOngoing(), "why weren't we unregistered from the refresh driver when scrollbar activity started?");
NS_ASSERTION(mIsFading, "should only animate fading during fade");
if (!UpdateOpacity(aTime)) {
return;
}
if (!IsStillFading(aTime)) {
EndFade();
}
}
bool
ScrollbarActivity::IsStillFading(TimeStamp aTime)
{
return !mFadeBeginTime.IsNull() && (aTime - mFadeBeginTime < FadeDuration());
}
void
ScrollbarActivity::HandleEventForScrollbar(const nsAString& aType,
nsIContent* aTarget,
nsIContent* aScrollbar,
bool* aStoredHoverState)
{
if (!aTarget || !aScrollbar ||
!nsContentUtils::ContentIsDescendantOf(aTarget, aScrollbar))
return;
if (aType.EqualsLiteral("mousedown")) {
ActivityStarted();
} else if (aType.EqualsLiteral("mouseup")) {
ActivityStopped();
} else if (aType.EqualsLiteral("mouseover") ||
aType.EqualsLiteral("mouseout")) {
bool newHoveredState = aType.EqualsLiteral("mouseover");
if (newHoveredState && !*aStoredHoverState) {
ActivityStarted();
HoveredScrollbar(aScrollbar);
} else if (*aStoredHoverState && !newHoveredState) {
ActivityStopped();
// Don't call HoveredScrollbar(nullptr) here because we want the hover
// attribute to stick until the scrollbars are hidden.
}
*aStoredHoverState = newHoveredState;
}
}
void
ScrollbarActivity::StartListeningForScrollbarEvents()
{
if (mListeningForScrollbarEvents)
return;
mHorizontalScrollbar = do_QueryInterface(GetHorizontalScrollbar());
mVerticalScrollbar = do_QueryInterface(GetVerticalScrollbar());
AddScrollbarEventListeners(mHorizontalScrollbar);
AddScrollbarEventListeners(mVerticalScrollbar);
mListeningForScrollbarEvents = true;
}
void
ScrollbarActivity::StopListeningForScrollbarEvents()
{
if (!mListeningForScrollbarEvents)
return;
RemoveScrollbarEventListeners(mHorizontalScrollbar);
RemoveScrollbarEventListeners(mVerticalScrollbar);
mHorizontalScrollbar = nullptr;
mVerticalScrollbar = nullptr;
mListeningForScrollbarEvents = false;
}
void
ScrollbarActivity::StartListeningForScrollAreaEvents()
{
if (mListeningForScrollAreaEvents)
return;
nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget
= do_QueryInterface(scrollArea->GetContent());
if (scrollAreaTarget) {
scrollAreaTarget->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
true);
}
mListeningForScrollAreaEvents = true;
}
void
ScrollbarActivity::StopListeningForScrollAreaEvents()
{
if (!mListeningForScrollAreaEvents)
return;
nsIFrame* scrollArea = do_QueryFrame(mScrollableFrame);
nsCOMPtr<nsIDOMEventTarget> scrollAreaTarget = do_QueryInterface(scrollArea->GetContent());
if (scrollAreaTarget) {
scrollAreaTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, true);
}
mListeningForScrollAreaEvents = false;
}
void
ScrollbarActivity::AddScrollbarEventListeners(nsIDOMEventTarget* aScrollbar)
{
if (aScrollbar) {
aScrollbar->AddEventListener(NS_LITERAL_STRING("mousedown"), this, true);
aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseup"), this, true);
aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseover"), this, true);
aScrollbar->AddEventListener(NS_LITERAL_STRING("mouseout"), this, true);
}
}
void
ScrollbarActivity::RemoveScrollbarEventListeners(nsIDOMEventTarget* aScrollbar)
{
if (aScrollbar) {
aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, true);
aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, true);
aScrollbar->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, true);
}
}
void
ScrollbarActivity::BeginFade()
{
NS_ASSERTION(mIsActive, "can't begin fade when we're already inactive");
NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade begin timer cancelled when scrollbar activity started?");
NS_ASSERTION(!mIsFading, "shouldn't be fading just yet");
CancelFadeBeginTimer();
mFadeBeginTime = TimeStamp::Now();
if (!SetIsFading(true)) {
return;
}
RegisterWithRefreshDriver();
NS_ASSERTION(mIsActive, "only fade while scrollbars are visible");
NS_ASSERTION(mIsFading, "should be fading now");
}
void
ScrollbarActivity::EndFade()
{
NS_ASSERTION(mIsActive, "still need to be active at this point");
NS_ASSERTION(!IsActivityOngoing(), "why wasn't the fade end timer cancelled when scrollbar activity started?");
if (!SetIsFading(false)) {
return;
}
SetIsActive(false);
UnregisterFromRefreshDriver();
StopListeningForScrollbarEvents();
if (!mDisplayOnMouseMove) {
StopListeningForScrollAreaEvents();
}
NS_ASSERTION(!mIsActive, "should have gone inactive after fade end");
NS_ASSERTION(!mIsFading, "shouldn't be fading anymore");
}
void
ScrollbarActivity::RegisterWithRefreshDriver()
{
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (refreshDriver) {
refreshDriver->AddRefreshObserver(this, Flush_Style);
}
}
void
ScrollbarActivity::UnregisterFromRefreshDriver()
{
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (refreshDriver) {
refreshDriver->RemoveRefreshObserver(this, Flush_Style);
}
}
static void
SetBooleanAttribute(nsIContent* aContent, nsIAtom* aAttribute, bool aValue)
{
if (aContent) {
if (aValue) {
aContent->SetAttr(kNameSpaceID_None, aAttribute,
NS_LITERAL_STRING("true"), true);
} else {
aContent->UnsetAttr(kNameSpaceID_None, aAttribute, true);
}
}
}
void
ScrollbarActivity::SetIsActive(bool aNewActive)
{
if (mIsActive == aNewActive)
return;
mIsActive = aNewActive;
if (!mIsActive) {
// Clear sticky scrollbar hover status.
HoveredScrollbar(nullptr);
}
SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::active, mIsActive);
SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::active, mIsActive);
}
static void
SetOpacityOnElement(nsIContent* aContent, double aOpacity)
{
nsCOMPtr<nsStyledElement> inlineStyleContent =
do_QueryInterface(aContent);
if (inlineStyleContent) {
nsICSSDeclaration* decl = inlineStyleContent->Style();
nsAutoString str;
str.AppendFloat(aOpacity);
decl->SetProperty(NS_LITERAL_STRING("opacity"), str, EmptyString());
}
}
bool
ScrollbarActivity::UpdateOpacity(TimeStamp aTime)
{
// Avoid division by zero if mScrollbarFadeDuration is zero, just jump
// to the end of the fade animation
double progress = mScrollbarFadeDuration
? ((aTime - mFadeBeginTime) / FadeDuration())
: 1.0;
double opacity = 1.0 - std::max(0.0, std::min(1.0, progress));
// 'this' may be getting destroyed during SetOpacityOnElement calls.
nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame)));
SetOpacityOnElement(GetHorizontalScrollbar(), opacity);
if (!weakFrame.IsAlive()) {
return false;
}
SetOpacityOnElement(GetVerticalScrollbar(), opacity);
if (!weakFrame.IsAlive()) {
return false;
}
return true;
}
static void
UnsetOpacityOnElement(nsIContent* aContent)
{
nsCOMPtr<nsStyledElement> inlineStyleContent =
do_QueryInterface(aContent);
if (inlineStyleContent) {
nsICSSDeclaration* decl = inlineStyleContent->Style();
nsAutoString dummy;
decl->RemoveProperty(NS_LITERAL_STRING("opacity"), dummy);
}
}
bool
ScrollbarActivity::SetIsFading(bool aNewFading)
{
if (mIsFading == aNewFading)
return true;
mIsFading = aNewFading;
if (!mIsFading) {
mFadeBeginTime = TimeStamp();
// 'this' may be getting destroyed during UnsetOpacityOnElement calls.
nsWeakFrame weakFrame((do_QueryFrame(mScrollableFrame)));
UnsetOpacityOnElement(GetHorizontalScrollbar());
if (!weakFrame.IsAlive()) {
return false;
}
UnsetOpacityOnElement(GetVerticalScrollbar());
if (!weakFrame.IsAlive()) {
return false;
}
}
return true;
}
void
ScrollbarActivity::StartFadeBeginTimer()
{
if (GetForceAlwaysVisiblePref()) {
return;
}
if (!mFadeBeginTimer) {
mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1");
}
mFadeBeginTimer->InitWithNamedFuncCallback(
FadeBeginTimerFired, this, mScrollbarFadeBeginDelay,
nsITimer::TYPE_ONE_SHOT, "ScrollbarActivity::FadeBeginTimerFired");
}
void
ScrollbarActivity::CancelFadeBeginTimer()
{
if (mFadeBeginTimer) {
mFadeBeginTimer->Cancel();
}
}
void
ScrollbarActivity::HoveredScrollbar(nsIContent* aScrollbar)
{
SetBooleanAttribute(GetHorizontalScrollbar(), nsGkAtoms::hover, false);
SetBooleanAttribute(GetVerticalScrollbar(), nsGkAtoms::hover, false);
SetBooleanAttribute(aScrollbar, nsGkAtoms::hover, true);
}
nsRefreshDriver*
ScrollbarActivity::GetRefreshDriver()
{
nsIFrame* scrollableFrame = do_QueryFrame(mScrollableFrame);
return scrollableFrame->PresContext()->RefreshDriver();
}
nsIContent*
ScrollbarActivity::GetScrollbarContent(bool aVertical)
{
nsIFrame* box = mScrollableFrame->GetScrollbarBox(aVertical);
return box ? box->GetContent() : nullptr;
}
} // namespace layout
} // namespace mozilla

View file

@ -0,0 +1,160 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef ScrollbarActivity_h___
#define ScrollbarActivity_h___
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsIDOMEventListener.h"
#include "mozilla/TimeStamp.h"
#include "nsRefreshDriver.h"
class nsIContent;
class nsIScrollbarMediator;
class nsITimer;
namespace mozilla {
namespace layout {
/**
* ScrollbarActivity
*
* This class manages scrollbar behavior that imitates the native Mac OS X
* Lion overlay scrollbar behavior: Scrollbars are only shown while "scrollbar
* activity" occurs, and they're hidden with a fade animation after a short
* delay.
*
* Scrollbar activity has these states:
* - inactive:
* Scrollbars are hidden.
* - ongoing activity:
* Scrollbars are visible and being operated on in some way, for example
* because they're hovered or pressed.
* - active, but waiting for fade out
* Scrollbars are still completely visible but are about to fade away.
* - fading out
* Scrollbars are subject to a fade-out animation.
*
* Initial scrollbar activity needs to be reported by the scrollbar holder that
* owns the ScrollbarActivity instance. This needs to happen via a call to
* ActivityOccurred(), for example when the current scroll position or the size
* of the scroll area changes.
*
* As soon as scrollbars are visible, the ScrollbarActivity class manages the
* rest of the activity behavior: It ensures that mouse motions inside the
* scroll area keep the scrollbars visible, and that scrollbars don't fade away
* while they're being hovered / dragged. It also sets a sticky hover attribute
* on the most recently hovered scrollbar.
*
* ScrollbarActivity falls into hibernation after the scrollbars have faded
* out. It only starts acting after the next call to ActivityOccurred() /
* ActivityStarted().
*/
class ScrollbarActivity final : public nsIDOMEventListener,
public nsARefreshObserver
{
public:
explicit ScrollbarActivity(nsIScrollbarMediator* aScrollableFrame)
: mScrollableFrame(aScrollableFrame)
, mNestedActivityCounter(0)
, mIsActive(false)
, mIsFading(false)
, mListeningForScrollbarEvents(false)
, mListeningForScrollAreaEvents(false)
, mHScrollbarHovered(false)
, mVScrollbarHovered(false)
, mDisplayOnMouseMove(false)
, mScrollbarFadeBeginDelay(0)
, mScrollbarFadeDuration(0)
{
QueryLookAndFeelVals();
}
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
void Destroy();
void ActivityOccurred();
void ActivityStarted();
void ActivityStopped();
virtual void WillRefresh(TimeStamp aTime) override;
static void FadeBeginTimerFired(nsITimer* aTimer, void* aSelf) {
RefPtr<ScrollbarActivity> scrollbarActivity(
reinterpret_cast<ScrollbarActivity*>(aSelf));
scrollbarActivity->BeginFade();
}
protected:
virtual ~ScrollbarActivity() {}
bool IsActivityOngoing()
{ return mNestedActivityCounter > 0; }
bool IsStillFading(TimeStamp aTime);
void QueryLookAndFeelVals();
void HandleEventForScrollbar(const nsAString& aType,
nsIContent* aTarget,
nsIContent* aScrollbar,
bool* aStoredHoverState);
void SetIsActive(bool aNewActive);
bool SetIsFading(bool aNewFading); // returns false if 'this' was destroyed
void BeginFade();
void EndFade();
void StartFadeBeginTimer();
void CancelFadeBeginTimer();
void StartListeningForScrollbarEvents();
void StartListeningForScrollAreaEvents();
void StopListeningForScrollbarEvents();
void StopListeningForScrollAreaEvents();
void AddScrollbarEventListeners(nsIDOMEventTarget* aScrollbar);
void RemoveScrollbarEventListeners(nsIDOMEventTarget* aScrollbar);
void RegisterWithRefreshDriver();
void UnregisterFromRefreshDriver();
bool UpdateOpacity(TimeStamp aTime); // returns false if 'this' was destroyed
void HoveredScrollbar(nsIContent* aScrollbar);
nsRefreshDriver* GetRefreshDriver();
nsIContent* GetScrollbarContent(bool aVertical);
nsIContent* GetHorizontalScrollbar() { return GetScrollbarContent(false); }
nsIContent* GetVerticalScrollbar() { return GetScrollbarContent(true); }
const TimeDuration FadeDuration() {
return TimeDuration::FromMilliseconds(mScrollbarFadeDuration);
}
nsIScrollbarMediator* mScrollableFrame;
TimeStamp mFadeBeginTime;
nsCOMPtr<nsITimer> mFadeBeginTimer;
nsCOMPtr<nsIDOMEventTarget> mHorizontalScrollbar; // null while inactive
nsCOMPtr<nsIDOMEventTarget> mVerticalScrollbar; // null while inactive
int mNestedActivityCounter;
bool mIsActive;
bool mIsFading;
bool mListeningForScrollbarEvents;
bool mListeningForScrollAreaEvents;
bool mHScrollbarHovered;
bool mVScrollbarHovered;
// LookAndFeel values we load on creation
bool mDisplayOnMouseMove;
int mScrollbarFadeBeginDelay;
int mScrollbarFadeDuration;
};
} // namespace layout
} // namespace mozilla
#endif /* ScrollbarActivity_h___ */

420
layout/generic/Selection.h Normal file
View file

@ -0,0 +1,420 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_Selection_h__
#define mozilla_Selection_h__
#include "nsIWeakReference.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/TextRange.h"
#include "nsISelection.h"
#include "nsISelectionController.h"
#include "nsISelectionListener.h"
#include "nsISelectionPrivate.h"
#include "nsRange.h"
#include "nsThreadUtils.h"
#include "nsWrapperCache.h"
struct CachedOffsetForFrame;
class nsAutoScrollTimer;
class nsIContentIterator;
class nsIFrame;
class nsFrameSelection;
struct SelectionDetails;
class nsCopySupport;
class nsHTMLCopyEncoder;
namespace mozilla {
class ErrorResult;
struct AutoPrepareFocusRange;
} // namespace mozilla
struct RangeData
{
explicit RangeData(nsRange* aRange)
: mRange(aRange)
{}
RefPtr<nsRange> mRange;
mozilla::TextRangeStyle mTextRangeStyle;
};
// Note, the ownership of mozilla::dom::Selection depends on which way the
// object is created. When nsFrameSelection has created Selection,
// addreffing/releasing the Selection object is aggregated to nsFrameSelection.
// Otherwise normal addref/release is used. This ensures that nsFrameSelection
// is never deleted before its Selections.
namespace mozilla {
namespace dom {
class Selection final : public nsISelectionPrivate,
public nsWrapperCache,
public nsSupportsWeakReference
{
protected:
virtual ~Selection();
public:
Selection();
explicit Selection(nsFrameSelection *aList);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelectionPrivate)
NS_DECL_NSISELECTION
NS_DECL_NSISELECTIONPRIVATE
virtual Selection* AsSelection() override { return this; }
nsresult EndBatchChangesInternal(int16_t aReason = nsISelectionListener::NO_REASON);
nsIDocument* GetParentObject() const;
// utility methods for scrolling the selection into view
nsPresContext* GetPresContext() const;
nsIPresShell* GetPresShell() const;
nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
// Returns a rect containing the selection region, and frame that that
// position is relative to. For SELECTION_ANCHOR_REGION or
// SELECTION_FOCUS_REGION the rect is a zero-width rectangle. For
// SELECTION_WHOLE_SELECTION the rect contains both the anchor and focus
// region rects.
nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect *aRect);
// Returns the position of the region (SELECTION_ANCHOR_REGION or
// SELECTION_FOCUS_REGION only), and frame that that position is relative to.
// The 'position' is a zero-width rectangle.
nsIFrame* GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect *aRect);
nsresult PostScrollSelectionIntoViewEvent(
SelectionRegion aRegion,
int32_t aFlags,
nsIPresShell::ScrollAxis aVertical,
nsIPresShell::ScrollAxis aHorizontal);
enum {
SCROLL_SYNCHRONOUS = 1<<1,
SCROLL_FIRST_ANCESTOR_ONLY = 1<<2,
SCROLL_DO_FLUSH = 1<<3, // only matters if SCROLL_SYNCHRONOUS is passed too
SCROLL_OVERFLOW_HIDDEN = 1<<5,
SCROLL_FOR_CARET_MOVE = 1<<6
};
// If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
// the scroll event fires so we make sure to scroll to the right place.
// Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
// flush layout and you MUST hold a strong ref on 'this' for the duration
// of this call. This might destroy arbitrary layout objects.
nsresult ScrollIntoView(SelectionRegion aRegion,
nsIPresShell::ScrollAxis aVertical =
nsIPresShell::ScrollAxis(),
nsIPresShell::ScrollAxis aHorizontal =
nsIPresShell::ScrollAxis(),
int32_t aFlags = 0);
nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
nsTArray<RangeData>* aOutput);
/**
* AddItem adds aRange to this Selection. If mUserInitiated is true,
* then aRange is first scanned for -moz-user-select:none nodes and split up
* into multiple ranges to exclude those before adding the resulting ranges
* to this Selection.
*/
nsresult AddItem(nsRange* aRange, int32_t* aOutIndex, bool aNoStartSelect = false);
nsresult RemoveItem(nsRange* aRange);
nsresult RemoveCollapsedRanges();
nsresult Clear(nsPresContext* aPresContext);
nsresult Collapse(nsINode* aParentNode, int32_t aOffset);
nsresult Extend(nsINode* aParentNode, int32_t aOffset);
nsRange* GetRangeAt(int32_t aIndex) const;
// Get the anchor-to-focus range if we don't care which end is
// anchor and which end is focus.
const nsRange* GetAnchorFocusRange() const {
return mAnchorFocusRange;
}
nsDirection GetDirection(){return mDirection;}
void SetDirection(nsDirection aDir){mDirection = aDir;}
nsresult SetAnchorFocusToRange(nsRange *aRange);
void ReplaceAnchorFocusRange(nsRange *aRange);
void AdjustAnchorFocusForMultiRange(nsDirection aDirection);
// NS_IMETHOD GetPrimaryFrameForRangeEndpoint(nsIDOMNode *aNode, int32_t aOffset, bool aIsEndNode, nsIFrame **aResultFrame);
NS_IMETHOD GetPrimaryFrameForAnchorNode(nsIFrame **aResultFrame);
NS_IMETHOD GetPrimaryFrameForFocusNode(nsIFrame **aResultFrame, int32_t *aOffset, bool aVisual);
NS_IMETHOD LookUpSelection(nsIContent *aContent,
int32_t aContentOffset,
int32_t aContentLength,
SelectionDetails** aReturnDetails,
SelectionType aSelectionType,
bool aSlowCheck);
NS_IMETHOD Repaint(nsPresContext* aPresContext);
// Note: StartAutoScrollTimer might destroy arbitrary frames etc.
nsresult StartAutoScrollTimer(nsIFrame *aFrame,
nsPoint& aPoint,
uint32_t aDelay);
nsresult StopAutoScrollTimer();
JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
// WebIDL methods
nsINode* GetAnchorNode();
uint32_t AnchorOffset();
nsINode* GetFocusNode();
uint32_t FocusOffset();
bool IsCollapsed() const;
void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
void CollapseToStart(mozilla::ErrorResult& aRv);
void CollapseToEnd(mozilla::ErrorResult& aRv);
void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
void DeleteFromDocument(mozilla::ErrorResult& aRv);
uint32_t RangeCount() const
{
return mRanges.Length();
}
nsRange* GetRangeAt(uint32_t aIndex, mozilla::ErrorResult& aRv);
void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
void RemoveRange(nsRange& aRange, mozilla::ErrorResult& aRv);
void RemoveAllRanges(mozilla::ErrorResult& aRv);
void Stringify(nsAString& aResult);
bool ContainsNode(nsINode& aNode, bool aPartlyContained, mozilla::ErrorResult& aRv);
/**
* Check to see if the given point is contained within the selection area. In
* particular, this iterates through all the rects that make up the selection,
* not just the bounding box, and checks to see if the given point is contained
* in any one of them.
* @param aPoint The point to check, relative to the root frame.
*/
bool ContainsPoint(const nsPoint& aPoint);
void Modify(const nsAString& aAlter, const nsAString& aDirection,
const nsAString& aGranularity, mozilla::ErrorResult& aRv);
bool GetInterlinePosition(mozilla::ErrorResult& aRv);
void SetInterlinePosition(bool aValue, mozilla::ErrorResult& aRv);
Nullable<int16_t> GetCaretBidiLevel(mozilla::ErrorResult& aRv) const;
void SetCaretBidiLevel(const Nullable<int16_t>& aCaretBidiLevel, mozilla::ErrorResult& aRv);
void ToStringWithFormat(const nsAString& aFormatType,
uint32_t aFlags,
int32_t aWrapColumn,
nsAString& aReturn,
mozilla::ErrorResult& aRv);
void AddSelectionListener(nsISelectionListener* aListener,
mozilla::ErrorResult& aRv);
void RemoveSelectionListener(nsISelectionListener* aListener,
mozilla::ErrorResult& aRv);
RawSelectionType RawType() const
{
return ToRawSelectionType(mSelectionType);
}
SelectionType Type() const { return mSelectionType; }
void GetRangesForInterval(nsINode& aBeginNode, int32_t aBeginOffset,
nsINode& aEndNode, int32_t aEndOffset,
bool aAllowAdjacent,
nsTArray<RefPtr<nsRange>>& aReturn,
mozilla::ErrorResult& aRv);
void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
int16_t aVPercent, int16_t aHPercent,
mozilla::ErrorResult& aRv);
void AddSelectionChangeBlocker();
void RemoveSelectionChangeBlocker();
bool IsBlockingSelectionChangeEvents() const;
private:
friend class ::nsAutoScrollTimer;
// Note: DoAutoScroll might destroy arbitrary frames etc.
nsresult DoAutoScroll(nsIFrame *aFrame, nsPoint& aPoint);
// XXX Please don't add additional uses of this method, it's only for
// XXX supporting broken code (bug 1245883) in the following classes:
friend class ::nsCopySupport;
friend class ::nsHTMLCopyEncoder;
void AddRangeInternal(nsRange& aRange, nsIDocument* aDocument, ErrorResult&);
public:
SelectionType GetType() const { return mSelectionType; }
void SetType(SelectionType aSelectionType)
{
mSelectionType = aSelectionType;
}
nsresult NotifySelectionListeners();
friend struct AutoUserInitiated;
struct MOZ_RAII AutoUserInitiated
{
explicit AutoUserInitiated(Selection* aSelection
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mSavedValue(aSelection->mUserInitiated)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
aSelection->mUserInitiated = true;
}
AutoRestore<bool> mSavedValue;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
private:
friend struct mozilla::AutoPrepareFocusRange;
class ScrollSelectionIntoViewEvent;
friend class ScrollSelectionIntoViewEvent;
class ScrollSelectionIntoViewEvent : public Runnable {
public:
NS_DECL_NSIRUNNABLE
ScrollSelectionIntoViewEvent(Selection* aSelection,
SelectionRegion aRegion,
nsIPresShell::ScrollAxis aVertical,
nsIPresShell::ScrollAxis aHorizontal,
int32_t aFlags)
: mSelection(aSelection),
mRegion(aRegion),
mVerticalScroll(aVertical),
mHorizontalScroll(aHorizontal),
mFlags(aFlags) {
NS_ASSERTION(aSelection, "null parameter");
}
void Revoke() { mSelection = nullptr; }
private:
Selection *mSelection;
SelectionRegion mRegion;
nsIPresShell::ScrollAxis mVerticalScroll;
nsIPresShell::ScrollAxis mHorizontalScroll;
int32_t mFlags;
};
void setAnchorFocusRange(int32_t aIndex); // pass in index into mRanges;
// negative value clears
// mAnchorFocusRange
nsresult SelectAllFramesForContent(nsIContentIterator *aInnerIter,
nsIContent *aContent,
bool aSelected);
nsresult selectFrames(nsPresContext* aPresContext, nsRange *aRange, bool aSelect);
nsresult getTableCellLocationFromRange(nsRange *aRange, int32_t *aSelectionType, int32_t *aRow, int32_t *aCol);
nsresult addTableCellRange(nsRange *aRange, bool *aDidAddRange, int32_t *aOutIndex);
nsresult FindInsertionPoint(
nsTArray<RangeData>* aElementArray,
nsINode* aPointNode, int32_t aPointOffset,
nsresult (*aComparator)(nsINode*,int32_t,nsRange*,int32_t*),
int32_t* aPoint);
bool EqualsRangeAtPoint(nsINode* aBeginNode, int32_t aBeginOffset,
nsINode* aEndNode, int32_t aEndOffset,
int32_t aRangeIndex);
nsresult GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset,
nsINode* aEndNode, int32_t aEndOffset,
bool aAllowAdjacent,
int32_t* aStartIndex, int32_t* aEndIndex);
RangeData* FindRangeData(nsIDOMRange* aRange);
void UserSelectRangesToAdd(nsRange* aItem, nsTArray<RefPtr<nsRange> >& rangesToAdd);
/**
* Helper method for AddItem.
*/
nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex);
// These are the ranges inside this selection. They are kept sorted in order
// of DOM start position.
//
// This data structure is sorted by the range beginnings. As the ranges are
// disjoint, it is also implicitly sorted by the range endings. This allows
// us to perform binary searches when searching for existence of a range,
// giving us O(log n) search time.
//
// Inserting a new range requires finding the overlapping interval, requiring
// two binary searches plus up to an additional 6 DOM comparisons. If this
// proves to be a performance concern, then an interval tree may be a
// possible solution, allowing the calculation of the overlap interval in
// O(log n) time, though this would require rebalancing and other overhead.
nsTArray<RangeData> mRanges;
RefPtr<nsRange> mAnchorFocusRange;
RefPtr<nsFrameSelection> mFrameSelection;
RefPtr<nsAutoScrollTimer> mAutoScrollTimer;
nsCOMArray<nsISelectionListener> mSelectionListeners;
nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
CachedOffsetForFrame *mCachedOffsetForFrame;
nsDirection mDirection;
SelectionType mSelectionType;
/**
* True if the current selection operation was initiated by user action.
* It determines whether we exclude -moz-user-select:none nodes or not,
* as well as whether selectstart events will be fired.
*/
bool mUserInitiated;
// Non-zero if we don't want any changes we make to the selection to be
// visible to content. If non-zero, content won't be notified about changes.
uint32_t mSelectionChangeBlockerCount;
};
// Stack-class to turn on/off selection batching.
class MOZ_STACK_CLASS SelectionBatcher final
{
private:
RefPtr<Selection> mSelection;
public:
explicit SelectionBatcher(Selection* aSelection)
{
mSelection = aSelection;
if (mSelection) {
mSelection->StartBatchChanges();
}
}
~SelectionBatcher()
{
if (mSelection) {
mSelection->EndBatchChangesInternal();
}
}
};
class MOZ_RAII AutoHideSelectionChanges final
{
private:
RefPtr<Selection> mSelection;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
explicit AutoHideSelectionChanges(const nsFrameSelection* aFrame);
explicit AutoHideSelectionChanges(Selection* aSelection
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mSelection(aSelection)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
mSelection = aSelection;
if (mSelection) {
mSelection->AddSelectionChangeBlocker();
}
}
~AutoHideSelectionChanges()
{
if (mSelection) {
mSelection->RemoveSelectionChangeBlocker();
}
}
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_Selection_h__

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_SelectionChangeListener_h_
#define mozilla_SelectionChangeListener_h_
#include "nsISelectionListener.h"
#include "nsISelectionPrivate.h"
#include "mozilla/Attributes.h"
namespace mozilla {
namespace dom {
class SelectionChangeListener final : public nsISelectionListener
{
public:
// SelectionChangeListener has to participate in cycle collection because
// it holds strong references to nsINodes in its mOldRanges array.
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SelectionChangeListener)
NS_DECL_NSISELECTIONLISTENER
// This field is used to keep track of the ranges which were present in the
// selection when the selectionchange event was previously fired. This allows
// for the selectionchange event to only be fired when a selection is actually
// changed.
struct RawRangeData
{
// These properties are not void*s to avoid the potential situation where the
// nsINode is freed, and a new nsINode is allocated with the same address, which
// could potentially break the comparison logic. In reality, this is extremely
// unlikely to occur (potentially impossible), but these nsCOMPtrs are safer.
// They are never dereferenced.
nsCOMPtr<nsINode> mStartParent;
nsCOMPtr<nsINode> mEndParent;
// XXX These are int32_ts on nsRange, but uint32_ts in the return value
// of GetStart_, so I use uint32_ts here. See bug 1194256.
uint32_t mStartOffset;
uint32_t mEndOffset;
explicit RawRangeData(const nsRange* aRange);
bool Equals(const nsRange* aRange);
};
private:
nsTArray<RawRangeData> mOldRanges;
~SelectionChangeListener() {}
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_SelectionChangeListener_h_

View file

@ -0,0 +1,391 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* compute sticky positioning, both during reflow and when the scrolling
* container scrolls
*/
#include "StickyScrollContainer.h"
#include "nsIFrame.h"
#include "nsIScrollableFrame.h"
#include "nsLayoutUtils.h"
#include "RestyleTracker.h"
namespace mozilla {
NS_DECLARE_FRAME_PROPERTY_DELETABLE(StickyScrollContainerProperty,
StickyScrollContainer)
StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame)
: mScrollFrame(aScrollFrame)
, mScrollPosition()
{
mScrollFrame->AddScrollPositionListener(this);
}
StickyScrollContainer::~StickyScrollContainer()
{
mScrollFrame->RemoveScrollPositionListener(this);
}
// static
StickyScrollContainer*
StickyScrollContainer::GetStickyScrollContainerForFrame(nsIFrame* aFrame)
{
nsIScrollableFrame* scrollFrame =
nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
if (!scrollFrame) {
// We might not find any, for instance in the case of
// <html style="position: fixed">
return nullptr;
}
FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame))->
Properties();
StickyScrollContainer* s = props.Get(StickyScrollContainerProperty());
if (!s) {
s = new StickyScrollContainer(scrollFrame);
props.Set(StickyScrollContainerProperty(), s);
}
return s;
}
// static
void
StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary(nsIFrame* aFrame,
nsIFrame* aOldParent)
{
nsIScrollableFrame* oldScrollFrame =
nsLayoutUtils::GetNearestScrollableFrame(aOldParent,
nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
if (!oldScrollFrame) {
// XXX maybe aFrame has sticky descendants that can be sticky now, but
// we aren't going to handle that.
return;
}
FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(oldScrollFrame))->
Properties();
StickyScrollContainer* oldSSC = props.Get(StickyScrollContainerProperty());
if (!oldSSC) {
// aOldParent had no sticky descendants, so aFrame doesn't have any sticky
// descendants, and we're done here.
return;
}
auto i = oldSSC->mFrames.Length();
while (i-- > 0) {
nsIFrame* f = oldSSC->mFrames[i];
StickyScrollContainer* newSSC = GetStickyScrollContainerForFrame(f);
if (newSSC != oldSSC) {
oldSSC->RemoveFrame(f);
if (newSSC) {
newSSC->AddFrame(f);
}
}
}
}
// static
StickyScrollContainer*
StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame)
{
FrameProperties props = aFrame->Properties();
return props.Get(StickyScrollContainerProperty());
}
static nscoord
ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset,
nscoord aPercentBasis)
{
if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) {
return NS_AUTOOFFSET;
} else {
return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis,
aOffset.Get(aSide));
}
}
// static
void
StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame)
{
nsIScrollableFrame* scrollableFrame =
nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
nsLayoutUtils::SCROLLABLE_SAME_DOC |
nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
if (!scrollableFrame) {
// Bail.
return;
}
nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()->
GetContentRectRelativeToSelf().Size();
nsMargin computedOffsets;
const nsStylePosition* position = aFrame->StylePosition();
computedOffsets.left = ComputeStickySideOffset(eSideLeft, position->mOffset,
scrollContainerSize.width);
computedOffsets.right = ComputeStickySideOffset(eSideRight, position->mOffset,
scrollContainerSize.width);
computedOffsets.top = ComputeStickySideOffset(eSideTop, position->mOffset,
scrollContainerSize.height);
computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset,
scrollContainerSize.height);
// Store the offset
FrameProperties props = aFrame->Properties();
nsMargin* offsets = props.Get(nsIFrame::ComputedOffsetProperty());
if (offsets) {
*offsets = computedOffsets;
} else {
props.Set(nsIFrame::ComputedOffsetProperty(),
new nsMargin(computedOffsets));
}
}
void
StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
nsRect* aContain) const
{
NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
"Can't sticky position individual continuations");
aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
const nsMargin* computedOffsets =
aFrame->Properties().Get(nsIFrame::ComputedOffsetProperty());
if (!computedOffsets) {
// We haven't reflowed the scroll frame yet, so offsets haven't been
// computed. Bail.
return;
}
nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
nsIFrame* cbFrame = aFrame->GetContainingBlock();
NS_ASSERTION(cbFrame == scrolledFrame ||
nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
"Scroll frame should be an ancestor of the containing block");
nsRect rect =
nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame->GetParent());
// Containing block limits for the position of aFrame relative to its parent.
// The margin box of the sticky element stays within the content box of the
// contaning-block element.
if (cbFrame != scrolledFrame) {
*aContain = nsLayoutUtils::
GetAllInFlowRectsUnion(cbFrame, aFrame->GetParent(),
nsLayoutUtils::RECTS_USE_CONTENT_BOX);
nsRect marginRect = nsLayoutUtils::
GetAllInFlowRectsUnion(aFrame, aFrame->GetParent(),
nsLayoutUtils::RECTS_USE_MARGIN_BOX);
// Deflate aContain by the difference between the union of aFrame's
// continuations' margin boxes and the union of their border boxes, so that
// by keeping aFrame within aContain, we keep the union of the margin boxes
// within the containing block's content box.
aContain->Deflate(marginRect - rect);
// Deflate aContain by the border-box size, to form a constraint on the
// upper-left corner of aFrame and continuations.
aContain->Deflate(nsMargin(0, rect.width, rect.height, 0));
}
nsMargin sfPadding = scrolledFrame->GetUsedPadding();
nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame);
// Top
if (computedOffsets->top != NS_AUTOOFFSET) {
aStick->SetTopEdge(mScrollPosition.y + sfPadding.top +
computedOffsets->top - sfOffset.y);
}
nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size();
// Bottom
if (computedOffsets->bottom != NS_AUTOOFFSET &&
(computedOffsets->top == NS_AUTOOFFSET ||
rect.height <= sfSize.height - computedOffsets->TopBottom())) {
aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height -
computedOffsets->bottom - rect.height - sfOffset.y);
}
uint8_t direction = cbFrame->StyleVisibility()->mDirection;
// Left
if (computedOffsets->left != NS_AUTOOFFSET &&
(computedOffsets->right == NS_AUTOOFFSET ||
direction == NS_STYLE_DIRECTION_LTR ||
rect.width <= sfSize.width - computedOffsets->LeftRight())) {
aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left +
computedOffsets->left - sfOffset.x);
}
// Right
if (computedOffsets->right != NS_AUTOOFFSET &&
(computedOffsets->left == NS_AUTOOFFSET ||
direction == NS_STYLE_DIRECTION_RTL ||
rect.width <= sfSize.width - computedOffsets->LeftRight())) {
aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width -
computedOffsets->right - rect.width - sfOffset.x);
}
// These limits are for the bounding box of aFrame's continuations. Convert
// to limits for aFrame itself.
nsPoint frameOffset = aFrame->GetPosition() - rect.TopLeft();
aStick->MoveBy(frameOffset);
aContain->MoveBy(frameOffset);
}
nsPoint
StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const
{
nsRect stick;
nsRect contain;
ComputeStickyLimits(aFrame, &stick, &contain);
nsPoint position = aFrame->GetNormalPosition();
// For each sticky direction (top, bottom, left, right), move the frame along
// the appropriate axis, based on the scroll position, but limit this to keep
// the element's margin box within the containing block.
position.y = std::max(position.y, std::min(stick.y, contain.YMost()));
position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
return position;
}
void
StickyScrollContainer::GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter,
nsRect* aInner) const
{
// We need to use the first in flow; continuation frames should not move
// relative to each other and should get identical scroll ranges.
// Also, ComputeStickyLimits requires this.
nsIFrame *firstCont =
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
nsRect stick;
nsRect contain;
ComputeStickyLimits(firstCont, &stick, &contain);
aOuter->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
aInner->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
const nsPoint normalPosition = firstCont->GetNormalPosition();
// Bottom and top
if (stick.YMost() != nscoord_MAX/2) {
aOuter->SetTopEdge(contain.y - stick.YMost());
aInner->SetTopEdge(normalPosition.y - stick.YMost());
}
if (stick.y != nscoord_MIN/2) {
aInner->SetBottomEdge(normalPosition.y - stick.y);
aOuter->SetBottomEdge(contain.YMost() - stick.y);
}
// Right and left
if (stick.XMost() != nscoord_MAX/2) {
aOuter->SetLeftEdge(contain.x - stick.XMost());
aInner->SetLeftEdge(normalPosition.x - stick.XMost());
}
if (stick.x != nscoord_MIN/2) {
aInner->SetRightEdge(normalPosition.x - stick.x);
aOuter->SetRightEdge(contain.XMost() - stick.x);
}
// Make sure |inner| does not extend outside of |outer|. (The consumers of
// the Layers API, to which this information is propagated, expect this
// invariant to hold.) The calculated value of |inner| can sometimes extend
// outside of |outer|, for example due to margin collapsing, since
// GetNormalPosition() returns the actual position after margin collapsing,
// while |contain| is calculated based on the frame's GetUsedMargin() which
// is pre-collapsing.
// Note that this doesn't necessarily solve all problems stemming from
// comparing pre- and post-collapsing margins (TODO: find a proper solution).
*aInner = aInner->Intersect(*aOuter);
}
void
StickyScrollContainer::PositionContinuations(nsIFrame* aFrame)
{
NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame),
"Should be starting from the first continuation");
nsPoint translation = ComputePosition(aFrame) - aFrame->GetNormalPosition();
// Move all continuation frames by the same amount.
for (nsIFrame* cont = aFrame; cont;
cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
cont->SetPosition(cont->GetNormalPosition() + translation);
}
}
void
StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
nsIFrame* aSubtreeRoot)
{
#ifdef DEBUG
{
nsIFrame* scrollFrameAsFrame = do_QueryFrame(mScrollFrame);
NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == scrollFrameAsFrame,
"If reflowing, should be reflowing the scroll frame");
}
#endif
mScrollPosition = aScrollPosition;
OverflowChangedTracker oct;
oct.SetSubtreeRoot(aSubtreeRoot);
for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
nsIFrame* f = mFrames[i];
if (!nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(f)) {
// This frame was added in nsFrame::Init before we knew it wasn't
// the first ib-split-sibling.
mFrames.RemoveElementAt(i);
--i;
continue;
}
if (aSubtreeRoot) {
// Reflowing the scroll frame, so recompute offsets.
ComputeStickyOffsets(f);
}
// mFrames will only contain first continuations, because we filter in
// nsIFrame::Init.
PositionContinuations(f);
f = f->GetParent();
if (f != aSubtreeRoot) {
for (nsIFrame* cont = f; cont;
cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
oct.AddFrame(cont, OverflowChangedTracker::CHILDREN_CHANGED);
}
}
}
oct.Flush();
}
void
StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY)
{
}
void
StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY)
{
UpdatePositions(nsPoint(aX, aY), nullptr);
}
} // namespace mozilla

View file

@ -0,0 +1,110 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* compute sticky positioning, both during reflow and when the scrolling
* container scrolls
*/
#ifndef StickyScrollContainer_h
#define StickyScrollContainer_h
#include "nsPoint.h"
#include "nsTArray.h"
#include "nsIScrollPositionListener.h"
struct nsRect;
class nsIFrame;
class nsIScrollableFrame;
namespace mozilla {
class StickyScrollContainer final : public nsIScrollPositionListener
{
public:
/**
* Find (and create if necessary) the StickyScrollContainer associated with
* the scroll container of the given frame, if a scroll container exists.
*/
static StickyScrollContainer* GetStickyScrollContainerForFrame(nsIFrame* aFrame);
/**
* Find the StickyScrollContainer associated with the given scroll frame,
* if it exists.
*/
static StickyScrollContainer* GetStickyScrollContainerForScrollFrame(nsIFrame* aScrollFrame);
/**
* aFrame may have moved into or out of a scroll frame's frame subtree.
*/
static void NotifyReparentedFrameAcrossScrollFrameBoundary(nsIFrame* aFrame,
nsIFrame* aOldParent);
void AddFrame(nsIFrame* aFrame) {
mFrames.AppendElement(aFrame);
}
void RemoveFrame(nsIFrame* aFrame) {
mFrames.RemoveElement(aFrame);
}
nsIScrollableFrame* ScrollFrame() const {
return mScrollFrame;
}
// Compute the offsets for a sticky position element
static void ComputeStickyOffsets(nsIFrame* aFrame);
/**
* Compute the position of a sticky positioned frame, based on information
* stored in its properties along with our scroll frame and scroll position.
*/
nsPoint ComputePosition(nsIFrame* aFrame) const;
/**
* Compute where a frame should not scroll with the page, represented by the
* difference of two rectangles.
*/
void GetScrollRanges(nsIFrame* aFrame, nsRect* aOuter, nsRect* aInner) const;
/**
* Compute and set the position of a frame and its following continuations.
*/
void PositionContinuations(nsIFrame* aFrame);
/**
* Compute and set the position of all sticky frames, given the current
* scroll position of the scroll frame. If not in reflow, aSubtreeRoot should
* be null; otherwise, overflow-area updates will be limited to not affect
* aSubtreeRoot or its ancestors.
*/
void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot);
// nsIScrollPositionListener
virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override;
virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
~StickyScrollContainer();
private:
explicit StickyScrollContainer(nsIScrollableFrame* aScrollFrame);
/**
* Compute two rectangles that determine sticky positioning: |aStick|, based
* on the scroll container, and |aContain|, based on the containing block.
* Sticky positioning keeps the frame position (its upper-left corner) always
* within |aContain| and secondarily within |aStick|.
*/
void ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
nsRect* aContain) const;
nsIScrollableFrame* const mScrollFrame;
nsTArray<nsIFrame*> mFrames;
nsPoint mScrollPosition;
};
} // namespace mozilla
#endif /* StickyScrollContainer_h */

View file

@ -0,0 +1,837 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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 "TextOverflow.h"
#include <algorithm>
// Please maintain alphabetical order below
#include "nsBlockFrame.h"
#include "nsCaret.h"
#include "nsContentUtils.h"
#include "nsCSSAnonBoxes.h"
#include "nsFontMetrics.h"
#include "nsGfxScrollFrame.h"
#include "nsIScrollableFrame.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsRect.h"
#include "nsRenderingContext.h"
#include "nsTextFrame.h"
#include "nsIFrameInlines.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Likely.h"
#include "nsISelection.h"
namespace mozilla {
namespace css {
class LazyReferenceRenderingDrawTargetGetterFromFrame final :
public gfxFontGroup::LazyReferenceDrawTargetGetter {
public:
typedef mozilla::gfx::DrawTarget DrawTarget;
explicit LazyReferenceRenderingDrawTargetGetterFromFrame(nsIFrame* aFrame)
: mFrame(aFrame) {}
virtual already_AddRefed<DrawTarget> GetRefDrawTarget() override
{
RefPtr<gfxContext> ctx =
mFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
return dt.forget();
}
private:
nsIFrame* mFrame;
};
static gfxTextRun*
GetEllipsisTextRun(nsIFrame* aFrame)
{
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
LazyReferenceRenderingDrawTargetGetterFromFrame lazyRefDrawTargetGetter(aFrame);
return fm->GetThebesFontGroup()->GetEllipsisTextRun(
aFrame->PresContext()->AppUnitsPerDevPixel(),
nsLayoutUtils::GetTextRunOrientFlagsForStyle(aFrame->StyleContext()),
lazyRefDrawTargetGetter);
}
static nsIFrame*
GetSelfOrNearestBlock(nsIFrame* aFrame)
{
return nsLayoutUtils::GetAsBlock(aFrame) ? aFrame :
nsLayoutUtils::FindNearestBlockAncestor(aFrame);
}
// Return true if the frame is an atomic inline-level element.
// It's not supposed to be called for block frames since we never
// process block descendants for text-overflow.
static bool
IsAtomicElement(nsIFrame* aFrame, const nsIAtom* aFrameType)
{
NS_PRECONDITION(!nsLayoutUtils::GetAsBlock(aFrame) ||
!aFrame->IsBlockOutside(),
"unexpected block frame");
NS_PRECONDITION(aFrameType != nsGkAtoms::placeholderFrame,
"unexpected placeholder frame");
return !aFrame->IsFrameOfType(nsIFrame::eLineParticipant);
}
static bool
IsFullyClipped(nsTextFrame* aFrame, nscoord aLeft, nscoord aRight,
nscoord* aSnappedLeft, nscoord* aSnappedRight)
{
*aSnappedLeft = aLeft;
*aSnappedRight = aRight;
if (aLeft <= 0 && aRight <= 0) {
return false;
}
return !aFrame->MeasureCharClippedText(aLeft, aRight,
aSnappedLeft, aSnappedRight);
}
static bool
IsInlineAxisOverflowVisible(nsIFrame* aFrame)
{
NS_PRECONDITION(nsLayoutUtils::GetAsBlock(aFrame) != nullptr,
"expected a block frame");
nsIFrame* f = aFrame;
while (f && f->StyleContext()->GetPseudo() &&
f->GetType() != nsGkAtoms::scrollFrame) {
f = f->GetParent();
}
if (!f) {
return true;
}
auto overflow = aFrame->GetWritingMode().IsVertical() ?
f->StyleDisplay()->mOverflowY : f->StyleDisplay()->mOverflowX;
return overflow == NS_STYLE_OVERFLOW_VISIBLE;
}
static void
ClipMarker(const nsRect& aContentArea,
const nsRect& aMarkerRect,
DisplayListClipState::AutoSaveRestore& aClipState)
{
nscoord rightOverflow = aMarkerRect.XMost() - aContentArea.XMost();
nsRect markerRect = aMarkerRect;
if (rightOverflow > 0) {
// Marker overflows on the right side (content width < marker width).
markerRect.width -= rightOverflow;
aClipState.ClipContentDescendants(markerRect);
} else {
nscoord leftOverflow = aContentArea.x - aMarkerRect.x;
if (leftOverflow > 0) {
// Marker overflows on the left side
markerRect.width -= leftOverflow;
markerRect.x += leftOverflow;
aClipState.ClipContentDescendants(markerRect);
}
}
}
static void
InflateIStart(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
{
nscoord iend = aRect->IEnd(aWM);
aRect->IStart(aWM) -= aDelta;
aRect->ISize(aWM) = std::max(iend - aRect->IStart(aWM), 0);
}
static void
InflateIEnd(WritingMode aWM, LogicalRect* aRect, nscoord aDelta)
{
aRect->ISize(aWM) = std::max(aRect->ISize(aWM) + aDelta, 0);
}
static bool
IsFrameDescendantOfAny(nsIFrame* aChild,
const TextOverflow::FrameHashtable& aSetOfFrames,
nsIFrame* aCommonAncestor)
{
for (nsIFrame* f = aChild; f && f != aCommonAncestor;
f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
if (aSetOfFrames.GetEntry(f)) {
return true;
}
}
return false;
}
class nsDisplayTextOverflowMarker : public nsDisplayItem
{
public:
nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
const nsRect& aRect, nscoord aAscent,
const nsStyleTextOverflowSide* aStyle,
uint32_t aIndex)
: nsDisplayItem(aBuilder, aFrame), mRect(aRect),
mStyle(aStyle), mAscent(aAscent), mIndex(aIndex) {
MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
}
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayTextOverflowMarker() {
MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
}
#endif
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) override {
*aSnap = false;
nsRect shadowRect =
nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
return mRect.Union(shadowRect);
}
virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
{
if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
// On OS X, web authors can turn off subpixel text rendering using the
// CSS property -moz-osx-font-smoothing. If they do that, we don't need
// to use component alpha layers for the affected text.
if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
return nsRect();
}
}
bool snap;
return GetBounds(aBuilder, &snap);
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx) override;
virtual uint32_t GetPerFrameKey() override {
return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
}
void PaintTextToContext(nsRenderingContext* aCtx,
nsPoint aOffsetFromRect);
NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
private:
nsRect mRect; // in reference frame coordinates
const nsStyleTextOverflowSide* mStyle;
nscoord mAscent; // baseline for the marker text in mRect
uint32_t mIndex;
};
static void
PaintTextShadowCallback(nsRenderingContext* aCtx,
nsPoint aShadowOffset,
const nscolor& aShadowColor,
void* aData)
{
reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
PaintTextToContext(aCtx, aShadowOffset);
}
void
nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx)
{
nscolor foregroundColor = nsLayoutUtils::
GetColor(mFrame, eCSSProperty__webkit_text_fill_color);
// Paint the text-shadows for the overflow marker
nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
foregroundColor, PaintTextShadowCallback,
(void*)this);
aCtx->ThebesContext()->SetColor(gfx::Color::FromABGR(foregroundColor));
PaintTextToContext(aCtx, nsPoint(0, 0));
}
void
nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx,
nsPoint aOffsetFromRect)
{
WritingMode wm = mFrame->GetWritingMode();
nsPoint pt(mRect.x, mRect.y);
if (wm.IsVertical()) {
if (wm.IsVerticalLR()) {
pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
mFrame, aCtx->ThebesContext(), pt.x, mAscent));
} else {
pt.x = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineX(
mFrame, aCtx->ThebesContext(), pt.x + mRect.width, -mAscent));
}
} else {
pt.y = NSToCoordFloor(nsLayoutUtils::GetSnappedBaselineY(
mFrame, aCtx->ThebesContext(), pt.y, mAscent));
}
pt += aOffsetFromRect;
if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
if (textRun) {
NS_ASSERTION(!textRun->IsRightToLeft(),
"Ellipsis textruns should always be LTR!");
gfxPoint gfxPt(pt.x, pt.y);
textRun->Draw(gfxTextRun::Range(textRun), gfxPt,
gfxTextRun::DrawParams(aCtx->ThebesContext()));
}
} else {
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame);
nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle->mString.get(),
mStyle->mString.Length(), pt);
}
}
TextOverflow::TextOverflow(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame)
: mContentArea(aBlockFrame->GetWritingMode(),
aBlockFrame->GetContentRectRelativeToSelf(),
aBlockFrame->GetSize())
, mBuilder(aBuilder)
, mBlock(aBlockFrame)
, mScrollableFrame(nsLayoutUtils::GetScrollableFrameFor(aBlockFrame))
, mBlockSize(aBlockFrame->GetSize())
, mBlockWM(aBlockFrame->GetWritingMode())
, mAdjustForPixelSnapping(false)
{
#ifdef MOZ_XUL
if (!mScrollableFrame) {
nsIAtom* pseudoType = aBlockFrame->StyleContext()->GetPseudo();
if (pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
mScrollableFrame =
nsLayoutUtils::GetScrollableFrameFor(aBlockFrame->GetParent());
// nsXULScrollFrame::ClampAndSetBounds rounds to nearest pixels
// for RTL blocks (also for overflow:hidden), so we need to move
// the edges 1px outward in ExamineLineFrames to avoid triggering
// a text-overflow marker in this case.
mAdjustForPixelSnapping = !mBlockWM.IsBidiLTR();
}
}
#endif
mCanHaveInlineAxisScrollbar = false;
if (mScrollableFrame) {
auto scrollbarStyle = mBlockWM.IsVertical() ?
mScrollableFrame->GetScrollbarStyles().mVertical :
mScrollableFrame->GetScrollbarStyles().mHorizontal;
mCanHaveInlineAxisScrollbar = scrollbarStyle != NS_STYLE_OVERFLOW_HIDDEN;
if (!mAdjustForPixelSnapping) {
// Scrolling to the end position can leave some text still overflowing due
// to pixel snapping behaviour in our scrolling code.
mAdjustForPixelSnapping = mCanHaveInlineAxisScrollbar;
}
// Use a null containerSize to convert a vector from logical to physical.
const nsSize nullContainerSize;
mContentArea.MoveBy(mBlockWM,
LogicalPoint(mBlockWM,
mScrollableFrame->GetScrollPosition(),
nullContainerSize));
nsIFrame* scrollFrame = do_QueryFrame(mScrollableFrame);
scrollFrame->AddStateBits(NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL);
}
uint8_t direction = aBlockFrame->StyleVisibility()->mDirection;
const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
if (mBlockWM.IsBidiLTR()) {
mIStart.Init(style->mTextOverflow.GetLeft(direction));
mIEnd.Init(style->mTextOverflow.GetRight(direction));
} else {
mIStart.Init(style->mTextOverflow.GetRight(direction));
mIEnd.Init(style->mTextOverflow.GetLeft(direction));
}
// The left/right marker string is setup in ExamineLineFrames when a line
// has overflow on that side.
}
/* static */ TextOverflow*
TextOverflow::WillProcessLines(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame)
{
if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) {
return nullptr;
}
nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(aBlockFrame);
if (scrollableFrame && scrollableFrame->IsTransformingByAPZ()) {
// If the APZ is actively scrolling this, don't bother with markers.
return nullptr;
}
return new TextOverflow(aBuilder, aBlockFrame);
}
void
TextOverflow::ExamineFrameSubtree(nsIFrame* aFrame,
const LogicalRect& aContentArea,
const LogicalRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges)
{
const nsIAtom* frameType = aFrame->GetType();
if (frameType == nsGkAtoms::brFrame ||
frameType == nsGkAtoms::placeholderFrame) {
return;
}
const bool isAtomic = IsAtomicElement(aFrame, frameType);
if (aFrame->StyleVisibility()->IsVisible()) {
LogicalRect childRect =
GetLogicalScrollableOverflowRectRelativeToBlock(aFrame);
bool overflowIStart =
childRect.IStart(mBlockWM) < aContentArea.IStart(mBlockWM);
bool overflowIEnd =
childRect.IEnd(mBlockWM) > aContentArea.IEnd(mBlockWM);
if (overflowIStart) {
mIStart.mHasOverflow = true;
}
if (overflowIEnd) {
mIEnd.mHasOverflow = true;
}
if (isAtomic && ((mIStart.mActive && overflowIStart) ||
(mIEnd.mActive && overflowIEnd))) {
aFramesToHide->PutEntry(aFrame);
} else if (isAtomic || frameType == nsGkAtoms::textFrame) {
AnalyzeMarkerEdges(aFrame, frameType, aInsideMarkersArea,
aFramesToHide, aAlignmentEdges,
aFoundVisibleTextOrAtomic,
aClippedMarkerEdges);
}
}
if (isAtomic) {
return;
}
for (nsIFrame* child : aFrame->PrincipalChildList()) {
ExamineFrameSubtree(child, aContentArea, aInsideMarkersArea,
aFramesToHide, aAlignmentEdges,
aFoundVisibleTextOrAtomic,
aClippedMarkerEdges);
}
}
void
TextOverflow::AnalyzeMarkerEdges(nsIFrame* aFrame,
const nsIAtom* aFrameType,
const LogicalRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges)
{
LogicalRect borderRect(mBlockWM,
nsRect(aFrame->GetOffsetTo(mBlock),
aFrame->GetSize()),
mBlockSize);
nscoord istartOverlap = std::max(
aInsideMarkersArea.IStart(mBlockWM) - borderRect.IStart(mBlockWM), 0);
nscoord iendOverlap = std::max(
borderRect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM), 0);
bool insideIStartEdge =
aInsideMarkersArea.IStart(mBlockWM) <= borderRect.IEnd(mBlockWM);
bool insideIEndEdge =
borderRect.IStart(mBlockWM) <= aInsideMarkersArea.IEnd(mBlockWM);
if (istartOverlap > 0) {
aClippedMarkerEdges->AccumulateIStart(mBlockWM, borderRect);
if (!mIStart.mActive) {
istartOverlap = 0;
}
}
if (iendOverlap > 0) {
aClippedMarkerEdges->AccumulateIEnd(mBlockWM, borderRect);
if (!mIEnd.mActive) {
iendOverlap = 0;
}
}
if ((istartOverlap > 0 && insideIStartEdge) ||
(iendOverlap > 0 && insideIEndEdge)) {
if (aFrameType == nsGkAtoms::textFrame) {
if (aInsideMarkersArea.IStart(mBlockWM) <
aInsideMarkersArea.IEnd(mBlockWM)) {
// a clipped text frame and there is some room between the markers
nscoord snappedIStart, snappedIEnd;
auto textFrame = static_cast<nsTextFrame*>(aFrame);
bool isFullyClipped = mBlockWM.IsBidiLTR() ?
IsFullyClipped(textFrame, istartOverlap, iendOverlap,
&snappedIStart, &snappedIEnd) :
IsFullyClipped(textFrame, iendOverlap, istartOverlap,
&snappedIEnd, &snappedIStart);
if (!isFullyClipped) {
LogicalRect snappedRect = borderRect;
if (istartOverlap > 0) {
snappedRect.IStart(mBlockWM) += snappedIStart;
snappedRect.ISize(mBlockWM) -= snappedIStart;
}
if (iendOverlap > 0) {
snappedRect.ISize(mBlockWM) -= snappedIEnd;
}
aAlignmentEdges->Accumulate(mBlockWM, snappedRect);
*aFoundVisibleTextOrAtomic = true;
}
}
} else {
aFramesToHide->PutEntry(aFrame);
}
} else if (!insideIStartEdge || !insideIEndEdge) {
// frame is outside
if (IsAtomicElement(aFrame, aFrameType)) {
aFramesToHide->PutEntry(aFrame);
}
} else {
// frame is inside
aAlignmentEdges->Accumulate(mBlockWM, borderRect);
*aFoundVisibleTextOrAtomic = true;
}
}
void
TextOverflow::ExamineLineFrames(nsLineBox* aLine,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges)
{
// No ellipsing for 'clip' style.
bool suppressIStart = mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
bool suppressIEnd = mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
if (mCanHaveInlineAxisScrollbar) {
LogicalPoint pos(mBlockWM, mScrollableFrame->GetScrollPosition(),
mBlockSize);
LogicalRect scrollRange(mBlockWM, mScrollableFrame->GetScrollRange(),
mBlockSize);
// No ellipsing when nothing to scroll to on that side (this includes
// overflow:auto that doesn't trigger a horizontal scrollbar).
if (pos.I(mBlockWM) <= scrollRange.IStart(mBlockWM)) {
suppressIStart = true;
}
if (pos.I(mBlockWM) >= scrollRange.IEnd(mBlockWM)) {
suppressIEnd = true;
}
}
LogicalRect contentArea = mContentArea;
const nscoord scrollAdjust = mAdjustForPixelSnapping ?
mBlock->PresContext()->AppUnitsPerDevPixel() : 0;
InflateIStart(mBlockWM, &contentArea, scrollAdjust);
InflateIEnd(mBlockWM, &contentArea, scrollAdjust);
LogicalRect lineRect(mBlockWM, aLine->GetScrollableOverflowArea(),
mBlockSize);
const bool istartOverflow =
!suppressIStart && lineRect.IStart(mBlockWM) < contentArea.IStart(mBlockWM);
const bool iendOverflow =
!suppressIEnd && lineRect.IEnd(mBlockWM) > contentArea.IEnd(mBlockWM);
if (!istartOverflow && !iendOverflow) {
// The line does not overflow on a side we should ellipsize.
return;
}
int pass = 0;
bool retryEmptyLine = true;
bool guessIStart = istartOverflow;
bool guessIEnd = iendOverflow;
mIStart.mActive = istartOverflow;
mIEnd.mActive = iendOverflow;
bool clippedIStartMarker = false;
bool clippedIEndMarker = false;
do {
// Setup marker strings as needed.
if (guessIStart) {
mIStart.SetupString(mBlock);
}
if (guessIEnd) {
mIEnd.SetupString(mBlock);
}
// If there is insufficient space for both markers then keep the one on the
// end side per the block's 'direction'.
nscoord istartMarkerISize = mIStart.mActive ? mIStart.mISize : 0;
nscoord iendMarkerISize = mIEnd.mActive ? mIEnd.mISize : 0;
if (istartMarkerISize && iendMarkerISize &&
istartMarkerISize + iendMarkerISize > contentArea.ISize(mBlockWM)) {
istartMarkerISize = 0;
}
// Calculate the area between the potential markers aligned at the
// block's edge.
LogicalRect insideMarkersArea = mContentArea;
if (guessIStart) {
InflateIStart(mBlockWM, &insideMarkersArea, -istartMarkerISize);
}
if (guessIEnd) {
InflateIEnd(mBlockWM, &insideMarkersArea, -iendMarkerISize);
}
// Analyze the frames on aLine for the overflow situation at the content
// edges and at the edges of the area between the markers.
bool foundVisibleTextOrAtomic = false;
int32_t n = aLine->GetChildCount();
nsIFrame* child = aLine->mFirstChild;
InnerClipEdges clippedMarkerEdges;
for (; n-- > 0; child = child->GetNextSibling()) {
ExamineFrameSubtree(child, contentArea, insideMarkersArea,
aFramesToHide, aAlignmentEdges,
&foundVisibleTextOrAtomic,
&clippedMarkerEdges);
}
if (!foundVisibleTextOrAtomic && retryEmptyLine) {
aAlignmentEdges->mAssigned = false;
aFramesToHide->Clear();
pass = -1;
if (mIStart.IsNeeded() && mIStart.mActive && !clippedIStartMarker) {
if (clippedMarkerEdges.mAssignedIStart &&
clippedMarkerEdges.mIStart > mContentArea.IStart(mBlockWM)) {
mIStart.mISize =
clippedMarkerEdges.mIStart - mContentArea.IStart(mBlockWM);
NS_ASSERTION(mIStart.mISize < mIStart.mIntrinsicISize,
"clipping a marker should make it strictly smaller");
clippedIStartMarker = true;
} else {
mIStart.mActive = guessIStart = false;
}
continue;
}
if (mIEnd.IsNeeded() && mIEnd.mActive && !clippedIEndMarker) {
if (clippedMarkerEdges.mAssignedIEnd &&
mContentArea.IEnd(mBlockWM) > clippedMarkerEdges.mIEnd) {
mIEnd.mISize = mContentArea.IEnd(mBlockWM) - clippedMarkerEdges.mIEnd;
NS_ASSERTION(mIEnd.mISize < mIEnd.mIntrinsicISize,
"clipping a marker should make it strictly smaller");
clippedIEndMarker = true;
} else {
mIEnd.mActive = guessIEnd = false;
}
continue;
}
// The line simply has no visible content even without markers,
// so examine the line again without suppressing markers.
retryEmptyLine = false;
mIStart.mISize = mIStart.mIntrinsicISize;
mIStart.mActive = guessIStart = istartOverflow;
mIEnd.mISize = mIEnd.mIntrinsicISize;
mIEnd.mActive = guessIEnd = iendOverflow;
continue;
}
if (guessIStart == (mIStart.mActive && mIStart.IsNeeded()) &&
guessIEnd == (mIEnd.mActive && mIEnd.IsNeeded())) {
break;
} else {
guessIStart = mIStart.mActive && mIStart.IsNeeded();
guessIEnd = mIEnd.mActive && mIEnd.IsNeeded();
mIStart.Reset();
mIEnd.Reset();
aFramesToHide->Clear();
}
NS_ASSERTION(pass == 0, "2nd pass should never guess wrong");
} while (++pass != 2);
if (!istartOverflow || !mIStart.mActive) {
mIStart.Reset();
}
if (!iendOverflow || !mIEnd.mActive) {
mIEnd.Reset();
}
}
void
TextOverflow::ProcessLine(const nsDisplayListSet& aLists,
nsLineBox* aLine)
{
NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP,
"TextOverflow with 'clip' for both sides");
mIStart.Reset();
mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
mIEnd.Reset();
mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
FrameHashtable framesToHide(64);
AlignmentEdges alignmentEdges;
ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
bool needIStart = mIStart.IsNeeded();
bool needIEnd = mIEnd.IsNeeded();
if (!needIStart && !needIEnd) {
return;
}
NS_ASSERTION(mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
!needIStart, "left marker for 'clip'");
NS_ASSERTION(mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
!needIEnd, "right marker for 'clip'");
// If there is insufficient space for both markers then keep the one on the
// end side per the block's 'direction'.
if (needIStart && needIEnd &&
mIStart.mISize + mIEnd.mISize > mContentArea.ISize(mBlockWM)) {
needIStart = false;
}
LogicalRect insideMarkersArea = mContentArea;
if (needIStart) {
InflateIStart(mBlockWM, &insideMarkersArea, -mIStart.mISize);
}
if (needIEnd) {
InflateIEnd(mBlockWM, &insideMarkersArea, -mIEnd.mISize);
}
if (!mCanHaveInlineAxisScrollbar && alignmentEdges.mAssigned) {
LogicalRect alignmentRect(mBlockWM, alignmentEdges.mIStart,
insideMarkersArea.BStart(mBlockWM),
alignmentEdges.ISize(), 1);
insideMarkersArea.IntersectRect(insideMarkersArea, alignmentRect);
}
// Clip and remove display items as needed at the final marker edges.
nsDisplayList* lists[] = { aLists.Content(), aLists.PositionedDescendants() };
for (uint32_t i = 0; i < ArrayLength(lists); ++i) {
PruneDisplayListContents(lists[i], framesToHide, insideMarkersArea);
}
CreateMarkers(aLine, needIStart, needIEnd, insideMarkersArea);
}
void
TextOverflow::PruneDisplayListContents(nsDisplayList* aList,
const FrameHashtable& aFramesToHide,
const LogicalRect& aInsideMarkersArea)
{
nsDisplayList saved;
nsDisplayItem* item;
while ((item = aList->RemoveBottom())) {
nsIFrame* itemFrame = item->Frame();
if (IsFrameDescendantOfAny(itemFrame, aFramesToHide, mBlock)) {
item->~nsDisplayItem();
continue;
}
nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
if (wrapper) {
if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
}
}
nsCharClipDisplayItem* charClip = itemFrame ?
nsCharClipDisplayItem::CheckCast(item) : nullptr;
if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
LogicalRect rect =
GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
if (mIStart.IsNeeded()) {
nscoord istart =
aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
if (istart > 0) {
(mBlockWM.IsBidiLTR() ?
charClip->mVisIStartEdge : charClip->mVisIEndEdge) = istart;
}
}
if (mIEnd.IsNeeded()) {
nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
if (iend > 0) {
(mBlockWM.IsBidiLTR() ?
charClip->mVisIEndEdge : charClip->mVisIStartEdge) = iend;
}
}
}
saved.AppendToTop(item);
}
aList->AppendToTop(&saved);
}
/* static */ bool
TextOverflow::HasClippedOverflow(nsIFrame* aBlockFrame)
{
const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
return style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
}
/* static */ bool
TextOverflow::CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame)
{
// Nothing to do for text-overflow:clip or if 'overflow-x/y:visible' or if
// we're just building items for event processing or frame visibility.
if (HasClippedOverflow(aBlockFrame) ||
IsInlineAxisOverflowVisible(aBlockFrame) ||
aBuilder->IsForEventDelivery() ||
aBuilder->IsForFrameVisibility()) {
return false;
}
// Skip ComboboxControlFrame because it would clip the drop-down arrow.
// Its anon block inherits 'text-overflow' and does what is expected.
if (aBlockFrame->GetType() == nsGkAtoms::comboboxControlFrame) {
return false;
}
// Inhibit the markers if a descendant content owns the caret.
RefPtr<nsCaret> caret = aBlockFrame->PresContext()->PresShell()->GetCaret();
if (caret && caret->IsVisible()) {
nsCOMPtr<nsISelection> domSelection = caret->GetSelection();
if (domSelection) {
nsCOMPtr<nsIDOMNode> node;
domSelection->GetFocusNode(getter_AddRefs(node));
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
if (content && nsContentUtils::ContentIsDescendantOf(content,
aBlockFrame->GetContent())) {
return false;
}
}
}
return true;
}
void
TextOverflow::CreateMarkers(const nsLineBox* aLine,
bool aCreateIStart, bool aCreateIEnd,
const mozilla::LogicalRect& aInsideMarkersArea)
{
if (aCreateIStart) {
DisplayListClipState::AutoSaveRestore clipState(mBuilder);
LogicalRect markerLogicalRect(
mBlockWM, aInsideMarkersArea.IStart(mBlockWM) - mIStart.mIntrinsicISize,
aLine->BStart(), mIStart.mIntrinsicISize, aLine->BSize());
nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
nsRect markerRect =
markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
ClipMarker(mContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
markerRect, clipState);
nsDisplayItem* marker = new (mBuilder)
nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
aLine->GetLogicalAscent(), mIStart.mStyle, 0);
mMarkerList.AppendNewToTop(marker);
}
if (aCreateIEnd) {
DisplayListClipState::AutoSaveRestore clipState(mBuilder);
LogicalRect markerLogicalRect(
mBlockWM, aInsideMarkersArea.IEnd(mBlockWM), aLine->BStart(),
mIEnd.mIntrinsicISize, aLine->BSize());
nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
nsRect markerRect =
markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
ClipMarker(mContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
markerRect, clipState);
nsDisplayItem* marker = new (mBuilder)
nsDisplayTextOverflowMarker(mBuilder, mBlock, markerRect,
aLine->GetLogicalAscent(), mIEnd.mStyle, 1);
mMarkerList.AppendNewToTop(marker);
}
}
void
TextOverflow::Marker::SetupString(nsIFrame* aFrame)
{
if (mInitialized) {
return;
}
if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
if (textRun) {
mISize = textRun->GetAdvanceWidth();
} else {
mISize = 0;
}
} else {
nsRenderingContext rc(
aFrame->PresContext()->PresShell()->CreateReferenceRenderingContext());
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
*fm, rc);
}
mIntrinsicISize = mISize;
mInitialized = true;
}
} // namespace css
} // namespace mozilla

View file

@ -0,0 +1,259 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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/. */
#ifndef TextOverflow_h_
#define TextOverflow_h_
#include "nsDisplayList.h"
#include "nsTHashtable.h"
#include "mozilla/Likely.h"
#include "mozilla/WritingModes.h"
#include <algorithm>
class nsIScrollableFrame;
class nsLineBox;
namespace mozilla {
namespace css {
/**
* A class for rendering CSS3 text-overflow.
* Usage:
* 1. allocate an object using WillProcessLines
* 2. then call ProcessLine for each line you are building display lists for
*/
class TextOverflow {
public:
/**
* Allocate an object for text-overflow processing.
* @return nullptr if no processing is necessary. The caller owns the object.
*/
static TextOverflow* WillProcessLines(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame);
/**
* Analyze the display lists for text overflow and what kind of item is at
* the content edges. Add display items for text-overflow markers as needed
* and remove or clip items that would overlap a marker.
*/
void ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine);
/**
* Get the resulting text-overflow markers (the list may be empty).
* @return a DisplayList containing any text-overflow markers.
*/
nsDisplayList& GetMarkers() { return mMarkerList; }
/**
* @return true if aBlockFrmae has text-overflow:clip on both sides.
*/
static bool HasClippedOverflow(nsIFrame* aBlockFrame);
/**
* @return true if aBlockFrame needs analysis for text overflow.
*/
static bool CanHaveTextOverflow(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame);
typedef nsTHashtable<nsPtrHashKey<nsIFrame> > FrameHashtable;
protected:
TextOverflow(nsDisplayListBuilder* aBuilder,
nsIFrame* aBlockFrame);
typedef mozilla::WritingMode WritingMode;
typedef mozilla::LogicalRect LogicalRect;
struct AlignmentEdges {
AlignmentEdges() : mAssigned(false) {}
void Accumulate(WritingMode aWM, const LogicalRect& aRect)
{
if (MOZ_LIKELY(mAssigned)) {
mIStart = std::min(mIStart, aRect.IStart(aWM));
mIEnd = std::max(mIEnd, aRect.IEnd(aWM));
} else {
mIStart = aRect.IStart(aWM);
mIEnd = aRect.IEnd(aWM);
mAssigned = true;
}
}
nscoord ISize() { return mIEnd - mIStart; }
nscoord mIStart;
nscoord mIEnd;
bool mAssigned;
};
struct InnerClipEdges {
InnerClipEdges() : mAssignedIStart(false), mAssignedIEnd(false) {}
void AccumulateIStart(WritingMode aWM, const LogicalRect& aRect)
{
if (MOZ_LIKELY(mAssignedIStart)) {
mIStart = std::max(mIStart, aRect.IStart(aWM));
} else {
mIStart = aRect.IStart(aWM);
mAssignedIStart = true;
}
}
void AccumulateIEnd(WritingMode aWM, const LogicalRect& aRect)
{
if (MOZ_LIKELY(mAssignedIEnd)) {
mIEnd = std::min(mIEnd, aRect.IEnd(aWM));
} else {
mIEnd = aRect.IEnd(aWM);
mAssignedIEnd = true;
}
}
nscoord mIStart;
nscoord mIEnd;
bool mAssignedIStart;
bool mAssignedIEnd;
};
LogicalRect
GetLogicalScrollableOverflowRectRelativeToBlock(nsIFrame* aFrame) const
{
return LogicalRect(mBlockWM,
aFrame->GetScrollableOverflowRect() +
aFrame->GetOffsetTo(mBlock),
mBlockSize);
}
/**
* Examines frames on the line to determine whether we should draw a left
* and/or right marker, and if so, which frames should be completely hidden
* and the bounds of what will be displayed between the markers.
* @param aLine the line we're processing
* @param aFramesToHide frames that should have their display items removed
* @param aAlignmentEdges the outermost edges of all text and atomic
* inline-level frames that are inside the area between the markers
*/
void ExamineLineFrames(nsLineBox* aLine,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges);
/**
* LineHasOverflowingText calls this to analyze edges, both the block's
* content edges and the hypothetical marker edges aligned at the block edges.
* @param aFrame the descendant frame of mBlock that we're analyzing
* @param aContentArea the block's content area
* @param aInsideMarkersArea the rectangle between the markers
* @param aFramesToHide frames that should have their display items removed
* @param aAlignmentEdges the outermost edges of all text and atomic
* inline-level frames that are inside the area between the markers
* @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
* inline-level frame is visible between the marker edges
* @param aClippedMarkerEdges the innermost edges of all text and atomic
* inline-level frames that are clipped by the current marker width
*/
void ExamineFrameSubtree(nsIFrame* aFrame,
const LogicalRect& aContentArea,
const LogicalRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges);
/**
* ExamineFrameSubtree calls this to analyze a frame against the hypothetical
* marker edges (aInsideMarkersArea) for text frames and atomic inline-level
* elements. A text frame adds its extent inside aInsideMarkersArea where
* grapheme clusters are fully visible. An atomic adds its border box if
* it's fully inside aInsideMarkersArea, otherwise the frame is added to
* aFramesToHide.
* @param aFrame the descendant frame of mBlock that we're analyzing
* @param aFrameType aFrame's frame type
* @param aInsideMarkersArea the rectangle between the markers
* @param aFramesToHide frames that should have their display items removed
* @param aAlignmentEdges the outermost edges of all text and atomic
* inline-level frames that are inside the area between the markers
* inside aInsideMarkersArea
* @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
* inline-level frame is visible between the marker edges
* @param aClippedMarkerEdges the innermost edges of all text and atomic
* inline-level frames that are clipped by the current marker width
*/
void AnalyzeMarkerEdges(nsIFrame* aFrame,
const nsIAtom* aFrameType,
const LogicalRect& aInsideMarkersArea,
FrameHashtable* aFramesToHide,
AlignmentEdges* aAlignmentEdges,
bool* aFoundVisibleTextOrAtomic,
InnerClipEdges* aClippedMarkerEdges);
/**
* Clip or remove items given the final marker edges. ("clip" here just means
* assigning mVisIStartEdge/mVisIEndEdge for any nsCharClipDisplayItem that
* needs it; see nsDisplayList.h for a description of that item).
* @param aFramesToHide remove display items for these frames
* @param aInsideMarkersArea is the area inside the markers
*/
void PruneDisplayListContents(nsDisplayList* aList,
const FrameHashtable& aFramesToHide,
const LogicalRect& aInsideMarkersArea);
/**
* ProcessLine calls this to create display items for the markers and insert
* them into mMarkerList.
* @param aLine the line we're processing
* @param aCreateIStart if true, create a marker on the inline start side
* @param aCreateIEnd if true, create a marker on the inline end side
* @param aInsideMarkersArea is the area inside the markers
*/
void CreateMarkers(const nsLineBox* aLine,
bool aCreateIStart, bool aCreateIEnd,
const LogicalRect& aInsideMarkersArea);
LogicalRect mContentArea;
nsDisplayListBuilder* mBuilder;
nsIFrame* mBlock;
nsIScrollableFrame* mScrollableFrame;
nsDisplayList mMarkerList;
nsSize mBlockSize;
WritingMode mBlockWM;
bool mCanHaveInlineAxisScrollbar;
bool mAdjustForPixelSnapping;
class Marker {
public:
void Init(const nsStyleTextOverflowSide& aStyle) {
mInitialized = false;
mISize = 0;
mStyle = &aStyle;
}
/**
* Setup the marker string and calculate its size, if not done already.
*/
void SetupString(nsIFrame* aFrame);
bool IsNeeded() const {
return mHasOverflow;
}
void Reset() {
mHasOverflow = false;
}
// The current width of the marker, the range is [0 .. mIntrinsicISize].
nscoord mISize;
// The intrinsic width of the marker.
nscoord mIntrinsicISize;
// The style for this side.
const nsStyleTextOverflowSide* mStyle;
// True if there is visible overflowing inline content on this side.
bool mHasOverflow;
// True if mMarkerString and mWidth have been setup from style.
bool mInitialized;
// True if the style is text-overflow:clip on this side and the marker
// won't cause the line to become empty.
bool mActive;
};
Marker mIStart; // the inline start marker
Marker mIEnd; // the inline end marker
};
} // namespace css
} // namespace mozilla
#endif /* !defined(TextOverflow_h_) */

View file

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* Declares visibility-related types. @Visibility is an enumeration of the
* possible visibility states of a frame. @OnNonvisible is an enumeration that
* allows callers to request a specific action when a frame transitions from
* visible to nonvisible.
*/
#ifndef mozilla_layout_generic_Visibility_h
#define mozilla_layout_generic_Visibility_h
namespace mozilla {
// Visibility states for frames.
enum class Visibility : uint8_t
{
// Indicates that we're not tracking visibility for this frame.
UNTRACKED,
// Indicates that the frame is probably nonvisible. Visible frames *may* be
// APPROXIMATELY_NONVISIBLE because approximate visibility is not updated
// synchronously. Some truly nonvisible frames may be marked
// APPROXIMATELY_VISIBLE instead if our heuristics lead us to think they may
// be visible soon.
APPROXIMATELY_NONVISIBLE,
// Indicates that the frame is either visible now or is likely to be visible
// soon according to our heuristics. As with APPROXIMATELY_NONVISIBLE, it's
// important to note that approximately visibility is not updated
// synchronously, so this information may be out of date.
APPROXIMATELY_VISIBLE
};
// Requested actions when frames transition to the nonvisible state.
enum class OnNonvisible : uint8_t
{
DISCARD_IMAGES // Discard images associated with the frame.
};
} // namespace mozilla
#endif // mozilla_layout_generic_Visibility_h

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
li::-moz-list-bullet {
direction: rtl;
margin-right: 1em;
}
</style>
</head>
<body>
Body
<ul>
<li>list item</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<style>
.r {
display: inline-block;
width: 40px;
height: 40px;
}
</style>
</head>
<body onload="document.getElementById('willFloatLeft').style.cssFloat = 'left';">
<div style="width: 100px; background: lightgray;">
<div class="r" style="background: red; width: 10px; float: left;"></div>
<div class="r" style="background: violet; width: 120px;" id="willFloatLeft" ></div>
<div style="clear: left;"></div>
<div class="r" style="background: blue;"></div>
<div class="r" style="background: yellow; float: right;"></div>
<div class="r" style="background: orange; position: relative;"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(boom,0,1)">
<splitter style="all: inherit;">
<box/>
<box/>
<box/>
<box/>
<iframe id="a" src="aaa"/>
</splitter>
<script id="script" xmlns="http://www.w3.org/1999/xhtml">
<![CDATA[//<![CDATA[
var doc = document;
function boom(i) {
if (i>6)
return;
var x=doc.getElementsByTagName('*');
if (x[i] && x[i+1]) {
var temp = x[i+1].getAttribute('style');
x[i+1].setAttribute('style', x[i].getAttribute('style'));
x[i].setAttribute('style', temp);
} else {
return;
}
i++;
setTimeout(boom,50,i);
}
//]]>
</script>
</window>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<script>
/*
user_pref("layout.css.grid.enabled", true);
*/
function boom()
{
document.getElementById("r").appendChild(document.createTextNode("B"));
}
</script>
</head>
<body onload="boom();">
<div id="r" style="display: grid;">A</div>
</body>
</html>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<html style="display: inline-flex;">
<body style="margin: -3642924795px; flex-grow: 1;"></body>
</html>

View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<div style="display: flex">
<div style="margin: -3642924795px; flex-grow: 1;"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,25 @@
<style>
.multicol {
width: 500px;
-moz-column-width: 100px;
height: 100px;
position: relative;
}
#clear {
position: absolute;
}
.float {
height: 300px;
}
</style>
<div class="multicol">
<div class="float">
<div class="multicol">
<div id="clear">
<div class="float">
<div class="multicol">
<div class="float">&nbsp;
</div>
<div class="multicol">
<div class="multicol">
<div class="float">a

View file

@ -0,0 +1,12 @@
<html>
<head>
</head>
<body style="display: -moz-inline-grid;">
<div style="display: table;position: relative; float: left;">
<button style="display: table-column-group;">
<iframe style="position: absolute;"></iframe>
</button>
</div>
</body></html>

View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" onload="tweak()">
<pattern>
<rect id="r" />
</pattern>
<script>
function tweak() {
document.getElementById("r").style.textDecoration = "underline";
}
</script>
</svg>

After

Width:  |  Height:  |  Size: 251 B

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<script>
function tweak() {
document.getElementById("c").style.textShadow = "3px 3px gray";
}
</script>
<body onload="tweak()">
<div id="c">hello
<svg height="0">
<clipPath>
<path d=""/>
</clipPath>
</svg>
</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html style="-moz-column-width: calc(15px);">
<body>
<video></video><audio style="box-decoration-break: clone; display: block; direction: rtl;"></audio>
</body>
</html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<body>
<div id="outer" style="width:200px; height:200px; background:blue; transform:translateY(10px)">
<div id="inner" style="width:200px; height:100px; background:yellow; transform:translateX(100px)">
</div>
</div>
<script>
console.log(outer.getBoundingClientRect());
outer.style.width = "400px";
console.log(outer.getBoundingClientRect());
outer.style.transform = "translateY(100px)";
</script>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html style="-moz-column-width: 1px;">
<body style="-moz-column-width: 1px; box-decoration-break: clone;">
<div>A B</div>
</body>
</html>

View file

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.flexRow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
.flexColumn {
display: flex;
flex-direction: column;
justify-content: center;
flex-basis: 100%;
}
.flexBlock {
flex: 0;
display: flex;
flex-direction: column;
padding: 5px;
border: 1px solid blue;
}
.flexColumn > .flexBlock:last-child {
flex: 1;
}
</style>
</head>
<body>
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 1
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 2
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 3
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 4
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 5
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 6
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 7
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 8
<div class="flexRow">
<div class="flexColumn">
<div class="flexBlock">
Nested layout 9
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<body>
<div style="display: -moz-grid-line;">
<div style="position: relative; direction: rtl;">
<div style="position: absolute;">
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<html>
<body onload="x.style.textTransform = 'capitalize';"><div id="x">b</div></body>
</html>

View file

@ -0,0 +1,2 @@
x
<ruby><x>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html class="reftest-print">
<head>
<meta charset="utf-8">
<title>Testcase bug 1137723</title>
<style>
.grid-comment{position:fixed;top:100%;}
a[href]:after{content:" (" attr(href) ")";}
</style>
<style>
@font-face { font-family: "slick"; src: url("non-existent-file.eot"); }
</style>
</head>
<body>
<div style="height:7in"></div>
<div class="grid-comment">Artikel teilen
<div>
<a href="https://www.facebook.com/sharer/sharer.php?u=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">shares</a>
<a href="https://twitter.com/share?text=Griechenland-Hilfe:Bundestag stimmt Verlängerung zu&amp;url=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">tweets</a>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html class="reftest-print">
<head>
<meta charset="utf-8">
<title>Testcase bug 1137723</title>
<style>
.grid-comment{position:fixed;top:100%;}
a[href]:after{content:" (" attr(href) ")";}
</style>
<style>
@font-face { font-family: "slick"; src: url("non-existent-file.eot"); }
</style>
</head>
<body>
<div style="height:3in"></div>
<div class="grid-comment">Artikel teilen
<div>
<a href="https://www.facebook.com/sharer/sharer.php?u=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">shares</a>
<a href="https://twitter.com/share?text=Griechenland-Hilfe:Bundestag stimmt Verlängerung zu&amp;url=http://www.stuttgarter-zeitung.de/inhalt.griechenland-hilfe-bundestag-stimmt-verlaengerung-zu.daa3baae-e17d-47e8-ad28-62eece0b4cfa.html">tweets</a>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<script>
function boom()
{
var e = document.getElementsByTagName("mo")[0];
e.setAttribute("style", "position: absolute; top: 0px;");
document.documentElement.offsetHeight;
e.setAttribute("style", "position: absolute; top: 100px;");
}
</script>
</head>
<body onload="boom();">
<math><mo>boom!</mo></math>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
function boom()
{
document.documentElement.offsetHeight;
var emptyTail = q.firstChild.splitText(2);
document.documentElement.offsetHeight;
emptyTail.splitText(0);
}
</script>
</head>
<body onload="boom();"><div style="width: 1px; height: 1px;"><div id="q" style="writing-mode: vertical-rl;">&#x07;R</div></div></body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div style="display: ruby-text; margin: -47891343%"></div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div style="display: ruby-base; margin: -47891343%"></div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div style="display: ruby-text; margin: 288230376151711740%"></div>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
function boom()
{
document.documentElement.offsetHeight;
document.body.appendChild(document.getElementById("x"));
document.documentElement.offsetHeight;
}
</script>
</head>
<body style="-moz-column-count: 4;" onload="boom();">
<div style="float: left; height: 10px; width: 10px;">
<div id="x">
<div style="height: 80px;"></div>
<div style="float: left; height: 10px;"></div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<div><span dir="rtl"></span><audio style="display: ruby-base;"></audio></div>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!--
user_pref("layout.css.grid.enabled", true);
-->
<script>
function boom()
{
document.documentElement.offsetHeight;
document.getElementById("g").firstChild.remove();
}
</script>
</head>
<body onload="boom();">
<div style="display: grid;" id="g">a b<br></div>
</body>
</html>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<html>
<body style="display: inline-flex;"><div><div style="display: ruby-base;">f<br style="display: ruby-base-container;"></div></div></body>
</html>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html style="writing-mode: vertical-lr;">
<body>
<div style="display: inline-flex;">
<div style="margin-inline-start: auto; margin-inline-end: 75px; direction: rtl;"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html style="writing-mode: vertical-lr;">
<body>
<div style="display: inline-flex;">
<div style="margin-bottom: auto; margin-top: 75px; direction: rtl;"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<!DOCTYPE>
<html>
<body>
<div style="writing-mode: vertical-lr;"><div style="position: fixed;"></div></div>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<style>
.flexContainer {
display: flex;
/* Just for easier visualization: */
width: 600px;
justify-content: space-around;
border: 1px solid black;
}
.flexContainer:before {
position:absolute;
content:'before';
}
.flexContainer:after {
position:absolute;
content:'after'
}
.ordered-item {
position:relative;
order:5;
}
</style>
</head>
<body>
<div class="flexContainer">
<div class="ordered-item">ItemWithOrderSet
<!-- It's important that this remain unclosed, for some reason. -->

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<style>
.flexContainer {
display: flex;
/* Just for easier visualization: */
width: 600px;
justify-content: space-around;
border: 1px solid black;
}
.flexContainer:before {
position:absolute;
content:'before';
}
.flexContainer:after {
position:absolute;
content:'after'
}
</style>
</head>
<body>
<div class="flexContainer">
<div class="ordered-item">NormalFlexItem
<!-- It's important that this remain unclosed, for some reason. -->

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
function boom()
{
document.documentElement.offsetHeight;
document.body.style.textOrientation = "sideways";
}
</script>
</head>
<body onload="boom();" style="writing-mode: tb-rl; display: table;">Hello</body>
</html>

View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<title>Test, bug 1222783</title>
<body>
<div id="container" style="width: 400px">
<div id="float1" style="float: left; height: 50px; width: 50px"></div>
<div id="float2" style="float: left; clear: left; height: 50px; width: 200px"></div>
<frameset cols="50%,50%">
<frame></frame>
<frame></frame>
</frameset>
</div>
</body>
</html>

View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<div style="display:flex; justify-content: stretch end">a

View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<span style="justify-content: stretch end true; display: inline-flex;"><span style="writing-mode: vertical-rl; display: -moz-inline-box;">f</span></span>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<meta charset=utf-8>
<script>
function boom() {
var div = document.querySelector("div");
div.offsetHeight;
div.style.fontSize = "120%";
}
</script>
<body onload="boom()">
<div style="float:left; position:relative;">
<div style="display:inline;">
<img src="foo.jpg" style="float:left;">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam tortor nulla,
eleifend eu eleifend eu, scelerisque sit amet sapien. Sed iaculis tellus ut quam
pharetra consequat. Donec vitae nulla eu mi porta vulputate. In vestibulum, erat
quis aliquam tempor, lectus augue viverra justo, vitae semper nibh neque tempor
orci. Etiam luctus aliquet magna id pellentesque. Interdum et malesuada fames ac
ante ipsum primis in faucibus. Suspendisse sit amet eros volutpat, convallis
purus non, porta sapien. Duis fermentum at tortor nec ultricies. Morbi et lacus
vitae risus elementum condimentum quis vitae justo. Cum sociis natoque penatibus
et magnis dis parturient montes, nascetur ridiculus mus.<br><br>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<html>
<body style="padding: 810520769306363pt; -moz-column-count: 10; transform: translate(50%);"><div style="position: absolute;"></div></body>
</html>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<html style="width: -moz-fit-content; display: grid;">
<body style="border: 1px solid green; padding-right: 56030668px;"></body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<body>
<div style="display: inline-grid;">
<div style="display: flex; align-self: right safe;"></div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<style>
rbc { display: ruby-base-container; }
</style>
<div style="-moz-column-width: 1px">
<ruby><rbc></rbc><rb><div style="float: right"></div></rb></ruby>X
</div>

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<div style="-moz-column-width: 1px">
<ruby><rb></rb><rb><div style="float: right"></div></rb></ruby>X
</div>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<body>
<div style="width: 1px;"><fieldset style="display: grid;">y<legend></legend>x</fieldset></div>
<div style="width: 1px;"><fieldset style="display: grid; overflow:hidden">y<legend></legend>x</fieldset></div>
<div style="width: 1px;"><fieldset style="display: flex;">y<legend></legend>x</fieldset></div>
<div style="width: 1px;"><fieldset style="display: flex; overflow:hidden">y<legend></legend>x</fieldset></div>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<style>
div.foo {
margin-bottom: 50%;
display: -moz-box;
direction: rtl;
}
</style>
</head>
<body>
<div class="foo"></div>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<style>
div.foo {
padding-bottom: 50%;
display: -moz-box;
direction: rtl;
}
</style>
</head>
<body>
<div class="foo"></div>
</body>
</html>

View file

@ -0,0 +1,3 @@
<div style="writing-mode: vertical-rl">
<span style="text-combine-upright: all">
<p>

View file

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html><head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
.grid {
display: grid;
}
.sfb { align-self:baseline; }
.slb { align-self:last baseline; }
.vl { writing-mode: vertical-lr; }
</style>
</head>
<body>
<div class="grid"><input class="slb"></div>
<div class="grid"><input class="sfb"></div>
<div class="grid"><input class="slb vl"></div>
<div class="grid"><input class="sfb vl"></div>
</body>
</html>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
<script>
function boom()
{
x.style.display = "contents";
}
</script>
</head>
<body onload="boom();">
<div style="-moz-column-width: 1px;">
<div style="display: grid;">
<div style="height: 200px;">v</div>
<div style="height: 200px;" id="x">x</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta http-equiv="content-type" content="text/html; charset=windows-1252"></head><body>
<style>
html{-moz-columns:50px auto}
body{display:grid}
div{height:200px}
</style>
<script>
window.onload=function(){
var b = document.body, v;
b.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
b.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
b.appendChild(v = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
setTimeout(function(){
v.style.overflow = "-moz-scrollbars-none";
document.documentElement.className = '';
},100);
};
</script>
<div></div><div></div><div style="overflow: hidden;"></div></body></html>

View file

@ -0,0 +1,35 @@
<!-- 128 LRI / RLI -->
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
<!-- 64 PDI -->
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;&#x2069;
<!-- 64 LRI / RLI -->
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;
&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;&#x2066;&#x2067;

View file

@ -0,0 +1,14 @@
<!doctype html>
<html>
<meta charset="utf-8" />
<style>
#box {
border-radius: 335px;
width: 600px;
height: 401px;
border-style: dotted;
border-width: 41px 1px;
}
</style>
<div id="box"></div>
</html>

View file

@ -0,0 +1,9 @@
<details>
<summary>
<li>
<style>
summary{
all:initial
}
:first-child::first-line
{}

View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<script>
addEventListener('DOMContentLoaded', function(){
obj1.style.writingMode = "rl";
obj2.style.writingMode = "rl";
//obj3.style.writingMode = "rl";
//obj4.style.writingMode = "rl";
obj5.style.writingMode = "rl";
sobj1.style.writingMode = "rl";
sobj2.style.writingMode = "rl";
//sobj3.style.writingMode = "rl";
//sobj4.style.writingMode = "rl";
sobj5.style.writingMode = "rl";
});
</script>
<body style="writing-mode:tb">
<button style="display:grid">
<div id=obj1></div>
</button>
<button style="display:inline-grid">
<div id=obj2></div>
</button>
<!--
<button style="display:flex">
<div id=obj3></div>
</button>
<button style="display:inline-flex">
<div id=obj4></div>
</button>
-->
<button style="columns:2">
<div id=obj5></div>
</button>
<button style="display:grid; overflow:hidden">
<div id=sobj1></div>
</button>
<button style="display:inline-grid; overflow:hidden">
<div id=sobj2></div>
</button>
<!--
<button style="display:flex; overflow:hidden">
<div id=sobj3></div>
</button>
<button style="display:inline-flex; overflow:hidden">
<div id=sobj4></div>
</button>
-->
<button style="columns:2; overflow:hidden">
<div id=sobj5></div>
</button>
</body>
</html>

View file

@ -0,0 +1,14 @@
<html>
<head>
<style type="text/css">
html { overflow: hidden; }
</style>
</head>
<body onload="setTimeout(function(){document.write('<body onload=&quot;parent.document.documentElement.removeAttribute(\'class\');&quot;>2</body>'); document.close();}, 0);">
1
</body>
</html>

View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head></head>
<body>
<iframe src="225868-1-inner.html"></iframe>
</body>
</html>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Iridescence</title>
<style type="text/css" title="Crashme" disabled="disabled">
ul.tree ul {
list-style-position: inside;
}
span.type:after {
content: ": ";
}
</style>
</head>
<body>
<h1>Crash Test</h1>
<table><tbody>
<tr><td><ul class="struct tree"><li><span class="type">A</span> X<ul><li><span class="type">L</span> <var>Size</var></li><li><var>data</var></li></ul></li></ul></td></tr>
</tbody></table>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html class="reftest-print">
<body>
<style type="text/css">
@page { size:5in 3in; margin:0.5in; }
</style>
<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
<br />
<div style="width: 60%; float: left;">3rd float</div>
<div style="width: 60%; float: left;">4th float</div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html class="reftest-print">
<body>
<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
Some text
<div style="width: 60%; float: left;">3rd float</div>
<div style="width: 60%; float: left;">4th float</div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html class="reftest-print">
<body>
<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
<div style="width: 60%; float: left; height: 1.6in; border:1px solid black;">overlaps page break</div>
Some text
<div style="width: 60%; float: left;">3rd float</div>
<div style="width: 60%; float: left;">4th float</div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html class="reftest-print">
<body>
<style type="text/css">
@page { size:5in 3in; margin:0.5in; }
</style>
<div style="width: 60%; float: left; height: 1.6in; ">a float</div>
<div style="width: 60%; float: left; height: 1.6in; border:1px solid black; ">overlaps page break</div>
This is enough text to trigger a line break.
<div style="width: 60%; float: left;">3rd float</div>
<div style="width: 60%; float: left;">4th float</div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<title>Hang while developing patch for bug 25888</title>
<div style="width:500px">
<div style="float:left;width:600px;height:30px"></div>
Hello
</div>

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<title>Testcase for hang while developing bug 25888 (hit on www.flightaware.com)</title>
<div style="border: solid 1em; width: 500px; height: 500px">
<div style="float:right; width: 300px; height: 3px;background:yellow;"></div>
<div style="float:left; width: 220px; height: 100px;background:aqua;"></div>
hi
</div>

View file

@ -0,0 +1,18 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Testcase for bug 264937</title>
<style type="text/css">
p:first-letter {
background-color: lime;
}
</style>
</head>
<body>
<p>"Test word</p>
</body>
</html>

View file

@ -0,0 +1,11 @@
<HTML>
<HEAD>
</HEAD>
<BODY>
<BODY STYLE="FLOAT:RIGHT;"></BODY>
<MARQUEE STYLE="MARGIN:99999999999px;"></MARQUEE>
<B STYLE="FLOAT:RIGHT; PADDING:99999999999px;"></B>
</BODY>
</HTML>

Some files were not shown because too many files have changed in this diff Show more