mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-03 13:57:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
133
layout/generic/AsyncScrollBase.cpp
Normal file
133
layout/generic/AsyncScrollBase.cpp
Normal 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));
|
||||
}
|
||||
105
layout/generic/AsyncScrollBase.h
Normal file
105
layout/generic/AsyncScrollBase.h
Normal 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_
|
||||
1140
layout/generic/BlockReflowInput.cpp
Normal file
1140
layout/generic/BlockReflowInput.cpp
Normal file
File diff suppressed because it is too large
Load diff
396
layout/generic/BlockReflowInput.h
Normal file
396
layout/generic/BlockReflowInput.h
Normal 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
|
||||
159
layout/generic/CSSAlignUtils.cpp
Normal file
159
layout/generic/CSSAlignUtils.cpp
Normal 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
|
||||
61
layout/generic/CSSAlignUtils.h
Normal file
61
layout/generic/CSSAlignUtils.h
Normal 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
|
||||
131
layout/generic/DetailsFrame.cpp
Normal file
131
layout/generic/DetailsFrame.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
62
layout/generic/DetailsFrame.h
Normal file
62
layout/generic/DetailsFrame.h
Normal 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
|
||||
59
layout/generic/FrameChildList.cpp
Normal file
59
layout/generic/FrameChildList.cpp
Normal 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
|
||||
126
layout/generic/FrameChildList.h
Normal file
126
layout/generic/FrameChildList.h
Normal 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_) */
|
||||
144
layout/generic/JustificationUtils.h
Normal file
144
layout/generic/JustificationUtils.h
Normal 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_) */
|
||||
801
layout/generic/MathMLTextRunFactory.cpp
Normal file
801
layout/generic/MathMLTextRunFactory.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
42
layout/generic/MathMLTextRunFactory.h
Normal file
42
layout/generic/MathMLTextRunFactory.h
Normal 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_*/
|
||||
3071
layout/generic/ReflowInput.cpp
Normal file
3071
layout/generic/ReflowInput.cpp
Normal file
File diff suppressed because it is too large
Load diff
1017
layout/generic/ReflowInput.h
Normal file
1017
layout/generic/ReflowInput.h
Normal file
File diff suppressed because it is too large
Load diff
67
layout/generic/ReflowOutput.cpp
Normal file
67
layout/generic/ReflowOutput.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
348
layout/generic/ReflowOutput.h
Normal file
348
layout/generic/ReflowOutput.h
Normal 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
|
||||
203
layout/generic/RubyUtils.cpp
Normal file
203
layout/generic/RubyUtils.cpp
Normal 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
234
layout/generic/RubyUtils.h
Normal 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_) */
|
||||
311
layout/generic/ScrollSnap.cpp
Normal file
311
layout/generic/ScrollSnap.cpp
Normal 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
|
||||
41
layout/generic/ScrollSnap.h
Normal file
41
layout/generic/ScrollSnap.h
Normal 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_
|
||||
97
layout/generic/ScrollVelocityQueue.cpp
Normal file
97
layout/generic/ScrollVelocityQueue.cpp
Normal 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
|
||||
|
||||
92
layout/generic/ScrollVelocityQueue.h
Normal file
92
layout/generic/ScrollVelocityQueue.h
Normal 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_) */
|
||||
469
layout/generic/ScrollbarActivity.cpp
Normal file
469
layout/generic/ScrollbarActivity.cpp
Normal 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
|
||||
160
layout/generic/ScrollbarActivity.h
Normal file
160
layout/generic/ScrollbarActivity.h
Normal 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
420
layout/generic/Selection.h
Normal 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__
|
||||
57
layout/generic/SelectionChangeListener.h
Normal file
57
layout/generic/SelectionChangeListener.h
Normal 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_
|
||||
391
layout/generic/StickyScrollContainer.cpp
Normal file
391
layout/generic/StickyScrollContainer.cpp
Normal 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
|
||||
110
layout/generic/StickyScrollContainer.h
Normal file
110
layout/generic/StickyScrollContainer.h
Normal 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 */
|
||||
837
layout/generic/TextOverflow.cpp
Normal file
837
layout/generic/TextOverflow.cpp
Normal 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
|
||||
259
layout/generic/TextOverflow.h
Normal file
259
layout/generic/TextOverflow.h
Normal 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_) */
|
||||
46
layout/generic/Visibility.h
Normal file
46
layout/generic/Visibility.h
Normal 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
|
||||
2075
layout/generic/WritingModes.h
Normal file
2075
layout/generic/WritingModes.h
Normal file
File diff suppressed because it is too large
Load diff
BIN
layout/generic/broken-image.png
Normal file
BIN
layout/generic/broken-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 B |
18
layout/generic/crashtests/1001233.html
Normal file
18
layout/generic/crashtests/1001233.html
Normal 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>
|
||||
26
layout/generic/crashtests/1001258-1.html
Normal file
26
layout/generic/crashtests/1001258-1.html
Normal 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>
|
||||
37
layout/generic/crashtests/1003441.xul
Normal file
37
layout/generic/crashtests/1003441.xul
Normal 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>
|
||||
20
layout/generic/crashtests/1015562.html
Normal file
20
layout/generic/crashtests/1015562.html
Normal 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>
|
||||
4
layout/generic/crashtests/1015563-1.html
Normal file
4
layout/generic/crashtests/1015563-1.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html style="display: inline-flex;">
|
||||
<body style="margin: -3642924795px; flex-grow: 1;"></body>
|
||||
</html>
|
||||
7
layout/generic/crashtests/1015563-2.html
Normal file
7
layout/generic/crashtests/1015563-2.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<div style="display: flex">
|
||||
<div style="margin: -3642924795px; flex-grow: 1;"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
25
layout/generic/crashtests/1015844.html
Normal file
25
layout/generic/crashtests/1015844.html
Normal 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">
|
||||
</div>
|
||||
<div class="multicol">
|
||||
<div class="multicol">
|
||||
<div class="float">a
|
||||
12
layout/generic/crashtests/1032450.html
Normal file
12
layout/generic/crashtests/1032450.html
Normal 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>
|
||||
10
layout/generic/crashtests/1032613-1.svg
Normal file
10
layout/generic/crashtests/1032613-1.svg
Normal 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 |
17
layout/generic/crashtests/1032613-2.html
Normal file
17
layout/generic/crashtests/1032613-2.html
Normal 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>
|
||||
6
layout/generic/crashtests/1037903.html
Normal file
6
layout/generic/crashtests/1037903.html
Normal 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>
|
||||
12
layout/generic/crashtests/1039454-1.html
Normal file
12
layout/generic/crashtests/1039454-1.html
Normal 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>
|
||||
6
layout/generic/crashtests/1042489.html
Normal file
6
layout/generic/crashtests/1042489.html
Normal 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>
|
||||
97
layout/generic/crashtests/1054010-1.html
Normal file
97
layout/generic/crashtests/1054010-1.html
Normal 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>
|
||||
13
layout/generic/crashtests/1058954-1.html
Normal file
13
layout/generic/crashtests/1058954-1.html
Normal 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>
|
||||
4
layout/generic/crashtests/1134531.html
Normal file
4
layout/generic/crashtests/1134531.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body onload="x.style.textTransform = 'capitalize';"><div id="x">b</div></body>
|
||||
</html>
|
||||
2
layout/generic/crashtests/1134667.html
Normal file
2
layout/generic/crashtests/1134667.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
x
|
||||
<ruby><x>
|
||||
29
layout/generic/crashtests/1137723-1.html
Normal file
29
layout/generic/crashtests/1137723-1.html
Normal 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&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>
|
||||
29
layout/generic/crashtests/1137723-2.html
Normal file
29
layout/generic/crashtests/1137723-2.html
Normal 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&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>
|
||||
18
layout/generic/crashtests/1140268-1.html
Normal file
18
layout/generic/crashtests/1140268-1.html
Normal 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>
|
||||
21
layout/generic/crashtests/1145768.html
Normal file
21
layout/generic/crashtests/1145768.html
Normal 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;">R</div></div></body>
|
||||
|
||||
</html>
|
||||
6
layout/generic/crashtests/1146103.html
Normal file
6
layout/generic/crashtests/1146103.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div style="display: ruby-text; margin: -47891343%"></div>
|
||||
</body>
|
||||
</html>
|
||||
6
layout/generic/crashtests/1146107.html
Normal file
6
layout/generic/crashtests/1146107.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div style="display: ruby-base; margin: -47891343%"></div>
|
||||
</body>
|
||||
</html>
|
||||
6
layout/generic/crashtests/1146114.html
Normal file
6
layout/generic/crashtests/1146114.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div style="display: ruby-text; margin: 288230376151711740%"></div>
|
||||
</body>
|
||||
</html>
|
||||
25
layout/generic/crashtests/1153695.html
Normal file
25
layout/generic/crashtests/1153695.html
Normal 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>
|
||||
6
layout/generic/crashtests/1156222.html
Normal file
6
layout/generic/crashtests/1156222.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div><span dir="rtl"></span><audio style="display: ruby-base;"></audio></div>
|
||||
</body>
|
||||
</html>
|
||||
19
layout/generic/crashtests/1156257.html
Normal file
19
layout/generic/crashtests/1156257.html
Normal 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>
|
||||
4
layout/generic/crashtests/1157011.html
Normal file
4
layout/generic/crashtests/1157011.html
Normal 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>
|
||||
8
layout/generic/crashtests/1169420-1.html
Normal file
8
layout/generic/crashtests/1169420-1.html
Normal 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>
|
||||
8
layout/generic/crashtests/1169420-2.html
Normal file
8
layout/generic/crashtests/1169420-2.html
Normal 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>
|
||||
6
layout/generic/crashtests/1183431.html
Normal file
6
layout/generic/crashtests/1183431.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE>
|
||||
<html>
|
||||
<body>
|
||||
<div style="writing-mode: vertical-lr;"><div style="position: fixed;"></div></div>
|
||||
</body>
|
||||
</html>
|
||||
32
layout/generic/crashtests/1221112-1.html
Normal file
32
layout/generic/crashtests/1221112-1.html
Normal 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. -->
|
||||
27
layout/generic/crashtests/1221112-2.html
Normal file
27
layout/generic/crashtests/1221112-2.html
Normal 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. -->
|
||||
16
layout/generic/crashtests/1221874-1.html
Normal file
16
layout/generic/crashtests/1221874-1.html
Normal 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>
|
||||
19
layout/generic/crashtests/1222783.xhtml
Normal file
19
layout/generic/crashtests/1222783.xhtml
Normal 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>
|
||||
2
layout/generic/crashtests/1223568-1.html
Normal file
2
layout/generic/crashtests/1223568-1.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<div style="display:flex; justify-content: stretch end">a
|
||||
6
layout/generic/crashtests/1223568-2.html
Normal file
6
layout/generic/crashtests/1223568-2.html
Normal 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>
|
||||
22
layout/generic/crashtests/1224230-1.html
Normal file
22
layout/generic/crashtests/1224230-1.html
Normal 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>
|
||||
4
layout/generic/crashtests/1225005.html
Normal file
4
layout/generic/crashtests/1225005.html
Normal 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>
|
||||
4
layout/generic/crashtests/1225118.html
Normal file
4
layout/generic/crashtests/1225118.html
Normal 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>
|
||||
10
layout/generic/crashtests/1225376.html
Normal file
10
layout/generic/crashtests/1225376.html
Normal 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>
|
||||
13
layout/generic/crashtests/1225592.html
Normal file
13
layout/generic/crashtests/1225592.html
Normal file
File diff suppressed because one or more lines are too long
8
layout/generic/crashtests/1229437-1.html
Normal file
8
layout/generic/crashtests/1229437-1.html
Normal 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>
|
||||
5
layout/generic/crashtests/1229437-2.html
Normal file
5
layout/generic/crashtests/1229437-2.html
Normal 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>
|
||||
9
layout/generic/crashtests/1233191.html
Normal file
9
layout/generic/crashtests/1233191.html
Normal 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>
|
||||
15
layout/generic/crashtests/1272983-1.html
Normal file
15
layout/generic/crashtests/1272983-1.html
Normal 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>
|
||||
15
layout/generic/crashtests/1272983-2.html
Normal file
15
layout/generic/crashtests/1272983-2.html
Normal 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>
|
||||
3
layout/generic/crashtests/1275059.html
Normal file
3
layout/generic/crashtests/1275059.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<div style="writing-mode: vertical-rl">
|
||||
<span style="text-combine-upright: all">
|
||||
<p>
|
||||
26
layout/generic/crashtests/1278007.html
Normal file
26
layout/generic/crashtests/1278007.html
Normal 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>
|
||||
23
layout/generic/crashtests/1278461-1.html
Normal file
23
layout/generic/crashtests/1278461-1.html
Normal 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>
|
||||
22
layout/generic/crashtests/1278461-2.html
Normal file
22
layout/generic/crashtests/1278461-2.html
Normal 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>
|
||||
35
layout/generic/crashtests/1279814.html
Normal file
35
layout/generic/crashtests/1279814.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<!-- 128 LRI / RLI -->
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
<!-- 64 PDI -->
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
⁩⁩⁩⁩⁩⁩⁩⁩
|
||||
<!-- 64 LRI / RLI -->
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
⁦⁧⁦⁧⁦⁧⁦⁧
|
||||
14
layout/generic/crashtests/1297427-non-equal-centers.html
Normal file
14
layout/generic/crashtests/1297427-non-equal-centers.html
Normal 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>
|
||||
9
layout/generic/crashtests/1304441.html
Normal file
9
layout/generic/crashtests/1304441.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<details>
|
||||
<summary>
|
||||
<li>
|
||||
<style>
|
||||
summary{
|
||||
all:initial
|
||||
}
|
||||
:first-child::first-line
|
||||
{}
|
||||
54
layout/generic/crashtests/1316649.html
Normal file
54
layout/generic/crashtests/1316649.html
Normal 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>
|
||||
14
layout/generic/crashtests/225868-1-inner.html
Normal file
14
layout/generic/crashtests/225868-1-inner.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
|
||||
html { overflow: hidden; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body onload="setTimeout(function(){document.write('<body onload="parent.document.documentElement.removeAttribute(\'class\');">2</body>'); document.close();}, 0);">
|
||||
|
||||
1
|
||||
|
||||
</body>
|
||||
</html>
|
||||
7
layout/generic/crashtests/225868-1.html
Normal file
7
layout/generic/crashtests/225868-1.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<head></head>
|
||||
<body>
|
||||
<iframe src="225868-1-inner.html"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
24
layout/generic/crashtests/255468.xhtml
Normal file
24
layout/generic/crashtests/255468.xhtml
Normal 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>
|
||||
13
layout/generic/crashtests/255982-1.html
Normal file
13
layout/generic/crashtests/255982-1.html
Normal 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>
|
||||
10
layout/generic/crashtests/255982-2.html
Normal file
10
layout/generic/crashtests/255982-2.html
Normal 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>
|
||||
10
layout/generic/crashtests/255982-3.html
Normal file
10
layout/generic/crashtests/255982-3.html
Normal 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>
|
||||
13
layout/generic/crashtests/255982-4.html
Normal file
13
layout/generic/crashtests/255982-4.html
Normal 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>
|
||||
6
layout/generic/crashtests/25888-1.html
Normal file
6
layout/generic/crashtests/25888-1.html
Normal 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>
|
||||
8
layout/generic/crashtests/25888-2.html
Normal file
8
layout/generic/crashtests/25888-2.html
Normal 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>
|
||||
18
layout/generic/crashtests/264937-1.html
Normal file
18
layout/generic/crashtests/264937-1.html
Normal 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>
|
||||
11
layout/generic/crashtests/265867-1.html
Normal file
11
layout/generic/crashtests/265867-1.html
Normal 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
Loading…
Add table
Add a link
Reference in a new issue