summaryrefslogtreecommitdiffstats
path: root/layout/base
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base')
-rw-r--r--layout/base/AccessibleCaretManager.cpp30
-rw-r--r--layout/base/MotionPathUtils.cpp35
-rw-r--r--layout/base/PresShell.cpp156
-rw-r--r--layout/base/PresShell.h18
-rw-r--r--layout/base/RestyleManager.cpp96
-rw-r--r--layout/base/RestyleManager.h17
-rw-r--r--layout/base/crashtests/crashtests.list4
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp18
-rw-r--r--layout/base/nsCSSFrameConstructor.h14
-rw-r--r--layout/base/nsCaret.cpp275
-rw-r--r--layout/base/nsCaret.h143
-rw-r--r--layout/base/nsCounterManager.cpp6
-rw-r--r--layout/base/nsDocumentViewer.cpp42
-rw-r--r--layout/base/nsLayoutUtils.cpp59
-rw-r--r--layout/base/nsLayoutUtils.h2
-rw-r--r--layout/base/nsPresContext.cpp9
-rw-r--r--layout/base/nsRefreshDriver.cpp23
-rw-r--r--layout/base/nsRefreshDriver.h5
-rw-r--r--layout/base/tests/chrome/chrome.toml6
-rw-r--r--layout/base/tests/chrome/printpreview_helper.xhtml243
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_001.html21
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_001_ref.html20
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_002.html21
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_002_ref.html26
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_003.html21
-rw-r--r--layout/base/tests/chrome/printpreview_scale_test_003_ref.html21
-rw-r--r--layout/base/tests/chrome/test_bug420499.xhtml2
27 files changed, 929 insertions, 404 deletions
diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp
index 17597d287d..99155133cd 100644
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -222,27 +222,17 @@ bool AccessibleCaretManager::IsCaretDisplayableInCursorMode(
if (!caret || !caret->IsVisible()) {
return false;
}
-
- int32_t offset = 0;
- nsIFrame* frame =
- nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
-
- if (!frame) {
- return false;
- }
-
- if (!GetEditingHostForFrame(frame)) {
+ auto frameData =
+ nsCaret::GetFrameAndOffset(nsCaret::CaretPositionFor(GetSelection()));
+ if (!GetEditingHostForFrame(frameData.mFrame)) {
return false;
}
-
if (aOutFrame) {
- *aOutFrame = frame;
+ *aOutFrame = frameData.mFrame;
}
-
if (aOutOffset) {
- *aOutOffset = offset;
+ *aOutOffset = frameData.mOffsetInFrameContent;
}
-
return true;
}
@@ -1333,10 +1323,9 @@ nsPoint AccessibleCaretManager::AdjustDragBoundary(
const nsPoint& aPoint) const {
nsPoint adjustedPoint = aPoint;
- int32_t focusOffset = 0;
- nsIFrame* focusFrame =
- nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &focusOffset);
- Element* editingHost = GetEditingHostForFrame(focusFrame);
+ auto frameData =
+ nsCaret::GetFrameAndOffset(nsCaret::CaretPositionFor(GetSelection()));
+ Element* editingHost = GetEditingHostForFrame(frameData.mFrame);
if (editingHost) {
nsIFrame* editingHostFrame = editingHost->GetPrimaryFrame();
@@ -1471,8 +1460,7 @@ void AccessibleCaretManager::DispatchCaretStateChangedEvent(
// Send isEditable info w/ event detail. This info can help determine
// whether to show cut command on selection dialog or not.
- init.mSelectionEditable =
- commonAncestorFrame && GetEditingHostForFrame(commonAncestorFrame);
+ init.mSelectionEditable = GetEditingHostForFrame(commonAncestorFrame);
init.mBoundingClientRect = domRect;
init.mReason = aReason;
diff --git a/layout/base/MotionPathUtils.cpp b/layout/base/MotionPathUtils.cpp
index 4045f2304a..d620064b73 100644
--- a/layout/base/MotionPathUtils.cpp
+++ b/layout/base/MotionPathUtils.cpp
@@ -434,7 +434,7 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
static inline bool IsClosedLoop(const StyleSVGPathData& aPathData) {
return !aPathData._0.AsSpan().empty() &&
- aPathData._0.AsSpan().rbegin()->IsClosePath();
+ aPathData._0.AsSpan().rbegin()->IsClose();
}
// Create a path for "inset(0 round X)", where X is the value of border-radius
@@ -466,8 +466,8 @@ static already_AddRefed<gfx::Path> BuildDefaultPathForURL(
return nullptr;
}
- Array<const StylePathCommand, 1> array(StylePathCommand::MoveTo(
- StyleCoordPair(gfx::Point{0.0, 0.0}), StyleIsAbsolute::No));
+ Array<const StylePathCommand, 1> array(StylePathCommand::Move(
+ StyleByTo::By, StyleCoordinatePair<StyleCSSFloat>{0.0, 0.0}));
return SVGPathData::BuildPath(array, aBuilder, StyleStrokeLinecap::Butt, 0.0);
}
@@ -695,6 +695,21 @@ already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
0.0);
}
+static already_AddRefed<gfx::Path> BuildShape(
+ const Span<const StyleShapeCommand>& aShape, gfx::PathBuilder* aPathBuilder,
+ const nsRect& aCoordBox) {
+ if (!aPathBuilder) {
+ return nullptr;
+ }
+
+ // For motion path, we always use CSSPixel unit to compute the offset
+ // transform (i.e. motion path transform).
+ const auto rect = CSSRect::FromAppUnits(aCoordBox);
+ return SVGPathData::BuildPath(aShape, aPathBuilder, StyleStrokeLinecap::Butt,
+ 0.0, rect.Size(),
+ rect.TopLeft().ToUnknownPoint());
+}
+
/* static */
already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
const StyleBasicShape& aBasicShape,
@@ -725,12 +740,22 @@ already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
case StyleBasicShape::Tag::Polygon:
return ShapeUtils::BuildPolygonPath(aBasicShape, aCoordBox,
AppUnitsPerCSSPixel(), aPathBuilder);
- case StyleBasicShape::Tag::Path:
+ case StyleBasicShape::Tag::PathOrShape: {
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
// to also check its containing block as well. For now, we are still
// building its gfx::Path directly by its SVGPathData without other
// reference. https://github.com/w3c/fxtf-drafts/issues/504
- return BuildSVGPath(aBasicShape.AsPath().path, aPathBuilder);
+ const auto& pathOrShape = aBasicShape.AsPathOrShape();
+ if (pathOrShape.IsPath()) {
+ return BuildSVGPath(pathOrShape.AsPath().path, aPathBuilder);
+ }
+
+ // Note that shape() always defines the initial position, i.e. "from x y",
+ // by its first move command, so |aOffsetPosition|, i.e. offset-position
+ // property, is ignored.
+ return BuildShape(pathOrShape.AsShape().commands.AsSpan(), aPathBuilder,
+ aCoordBox);
+ }
}
return nullptr;
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 31c21c3377..7674abb3d0 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -14,6 +14,7 @@
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/FragmentDirective.h"
#include "mozilla/dom/LargestContentfulPaint.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/PerformanceMainThread.h"
@@ -1281,10 +1282,13 @@ void PresShell::Destroy() {
ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
- if (mCaret) {
+ if (mOriginalCaret) {
+ mOriginalCaret->Terminate();
+ }
+ if (mCaret && mCaret != mOriginalCaret) {
mCaret->Terminate();
- mCaret = nullptr;
}
+ mCaret = mOriginalCaret = nullptr;
mFocusedFrameSelection = nullptr;
@@ -2238,9 +2242,20 @@ PresShell::GetAccessibleCaretEventHub() const {
return eventHub.forget();
}
-void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
+void PresShell::SetCaret(nsCaret* aNewCaret) {
+ if (mCaret == aNewCaret) {
+ return;
+ }
+ if (mCaret) {
+ mCaret->SchedulePaint();
+ }
+ mCaret = aNewCaret;
+ if (aNewCaret) {
+ aNewCaret->SchedulePaint();
+ }
+}
-void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
+void PresShell::RestoreCaret() { SetCaret(mOriginalCaret); }
NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
bool oldEnabled = mCaretEnabled;
@@ -3277,6 +3292,46 @@ nsresult PresShell::ScrollToAnchor() {
ScrollAxis(), ScrollFlags::AnchorScrollFlags);
}
+bool PresShell::HighlightAndGoToTextFragment(bool aScrollToTextFragment) {
+ MOZ_ASSERT(mDocument);
+ if (!StaticPrefs::dom_text_fragments_enabled()) {
+ return false;
+ }
+ const RefPtr<FragmentDirective> fragmentDirective =
+ mDocument->FragmentDirective();
+
+ nsTArray<RefPtr<nsRange>> textDirectiveRanges =
+ fragmentDirective->FindTextFragmentsInDocument();
+ if (textDirectiveRanges.IsEmpty()) {
+ return false;
+ }
+
+ const RefPtr<Selection> targetTextSelection =
+ GetCurrentSelection(SelectionType::eTargetText);
+ if (!targetTextSelection) {
+ return false;
+ }
+ for (RefPtr<nsRange> range : textDirectiveRanges) {
+ targetTextSelection->AddRangeAndSelectFramesAndNotifyListeners(
+ *range, IgnoreErrors());
+ }
+ if (!aScrollToTextFragment) {
+ return false;
+ }
+
+ // Scroll the last text directive into view.
+ nsRange* lastRange = textDirectiveRanges.LastElement();
+ MOZ_ASSERT(lastRange);
+ if (RefPtr<nsIContent> lastRangeStartContent =
+ nsIContent::FromNode(lastRange->GetStartContainer())) {
+ return ScrollContentIntoView(
+ lastRangeStartContent,
+ ScrollAxis(WhereToScroll::Center, WhenToScroll::Always),
+ ScrollAxis(), ScrollFlags::AnchorScrollFlags) == NS_OK;
+ }
+ return false;
+}
+
/*
* Helper (per-continuation) for ScrollContentIntoView.
*
@@ -4473,6 +4528,26 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged(
}
}
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CustomStatesWillChange(
+ Element& aElement) {
+ if (MOZ_UNLIKELY(!mDidInitialize)) {
+ return;
+ }
+
+ mPresContext->RestyleManager()->CustomStatesWillChange(aElement);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CustomStateChanged(
+ Element& aElement, nsAtom* aState) {
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected CustomStateChanged");
+ MOZ_ASSERT(aState, "Unexpected empty state");
+
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->CustomStateChanged(aElement, aState);
+ }
+}
+
void PresShell::DocumentStatesChanged(DocumentState aStateMask) {
MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
MOZ_ASSERT(mDocument);
@@ -5582,7 +5657,7 @@ nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
// GetResolution handles mResolution being nothing by returning 1 so this
// is checking that the resolution is actually changing.
- bool resolutionUpdated = (aResolution != GetResolution());
+ bool resolutionUpdated = aResolution != GetResolution();
mLastResolutionChangeOrigin = aOrigin;
@@ -5647,7 +5722,9 @@ void PresShell::SetRenderingState(const RenderingState& aState) {
}
void PresShell::SynthesizeMouseMove(bool aFromScroll) {
- if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
+ if (!StaticPrefs::layout_reflow_synthMouseMove()) {
+ return;
+ }
if (mPaintingSuppressed || !mIsActive || !mPresContext) {
return;
@@ -5660,8 +5737,9 @@ void PresShell::SynthesizeMouseMove(bool aFromScroll) {
return;
}
- if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
+ if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
return;
+ }
if (!mSynthMouseMoveEvent.IsPending()) {
RefPtr<nsSynthMouseMoveEvent> ev =
@@ -11220,8 +11298,7 @@ Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) {
return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
}
- if (StaticPrefs::apz_mvm_force_enabled() ||
- nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
+ if (nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
}
return Nothing();
@@ -11244,6 +11321,11 @@ void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
return;
}
+ if (!mPresContext->IsRootContentDocumentCrossProcess()) {
+ MOZ_ASSERT(!mMobileViewportManager, "We never create MVMs for subframes");
+ return;
+ }
+
if (mMobileViewportManager) {
// We have one, but we need to either destroy it completely to replace it
// with another one of the correct type. So either way, let's destroy the
@@ -11253,16 +11335,6 @@ void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
mMVMContext = nullptr;
ResetVisualViewportSize();
-
- // After we clear out the MVM and the MVMContext, also reset the
- // resolution to its pre-MVM value.
- SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(),
- ResolutionChangeOrigin::MainThreadRestore);
-
- if (aAfterInitialization) {
- // Force a reflow to our correct view manager size.
- ForceResizeReflowWithCurrentDimensions();
- }
}
if (mvmType) {
@@ -11270,32 +11342,33 @@ void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
// have one.
MOZ_ASSERT(!mMobileViewportManager);
- if (mPresContext->IsRootContentDocumentCrossProcess()) {
- // Store the resolution so we can restore to this resolution when
- // the MVM is destroyed.
- mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f));
-
- mMVMContext = new GeckoMVMContext(mDocument, this);
- mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
- if (MOZ_UNLIKELY(
- MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
- nsIURI* uri = mDocument->GetDocumentURI();
- MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug,
- ("Created MVM %p (type %d) for URI %s",
- mMobileViewportManager.get(), (int)*mvmType,
- uri ? uri->GetSpecOrDefault().get() : "(null)"));
- }
-
- if (aAfterInitialization) {
- // Setting the initial viewport will trigger a reflow.
- mMobileViewportManager->SetInitialViewport();
- }
+ mMVMContext = new GeckoMVMContext(mDocument, this);
+ mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
+ if (MOZ_UNLIKELY(
+ MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ MOZ_LOG(
+ MobileViewportManager::gLog, LogLevel::Debug,
+ ("Created MVM %p (type %d) for URI %s", mMobileViewportManager.get(),
+ (int)*mvmType, uri ? uri->GetSpecOrDefault().get() : "(null)"));
}
}
+ if (aAfterInitialization) {
+ // Setting the initial viewport will trigger a reflow.
+ if (mMobileViewportManager) {
+ mMobileViewportManager->SetInitialViewport();
+ } else {
+ // Force a reflow to our correct view manager size.
+ ForceResizeReflowWithCurrentDimensions();
+ }
+ // After we clear out the MVM and the MVMContext, also reset the
+ // resolution to 1.
+ SetResolutionAndScaleTo(1.0f, ResolutionChangeOrigin::MainThreadRestore);
+ }
}
bool PresShell::UsesMobileViewportSizing() const {
- return mMobileViewportManager != nullptr &&
+ return mMobileViewportManager &&
nsLayoutUtils::ShouldHandleMetaViewport(mDocument);
}
@@ -11708,8 +11781,7 @@ static bool IsTopLevelWidget(nsIWidget* aWidget) {
auto windowType = aWidget->GetWindowType();
return windowType == WindowType::TopLevel ||
- windowType == WindowType::Dialog || windowType == WindowType::Popup ||
- windowType == WindowType::Sheet;
+ windowType == WindowType::Dialog || windowType == WindowType::Popup;
}
PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
index 482ace1421..2b43b3c492 100644
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -531,6 +531,12 @@ class PresShell final : public nsStubDocumentObserver,
dom::HTMLSlotElement* aOldSlot,
dom::HTMLSlotElement* aNewSlot);
+ /**
+ * Handles when a CustomStateSet state is about to be removed or added.
+ */
+ void CustomStatesWillChange(Element& aElement);
+ void CustomStateChanged(Element& aElement, nsAtom* aState);
+
void PostRecreateFramesFor(Element*);
void RestyleForAnimation(Element*, RestyleHint);
@@ -1604,6 +1610,18 @@ class PresShell final : public nsStubDocumentObserver,
MOZ_CAN_RUN_SCRIPT nsresult ScrollToAnchor();
/**
+ * Finds text fragments ranes in the document, highlights the ranges and
+ * scrolls to the last text fragment range on the page if
+ * `aScrollToTextFragment` is true.
+ *
+ * @param aScrollToTextFragment If true, scrolls the view to the last text
+ * fragment.
+ * @return True if scrolling happened.
+ */
+ MOZ_CAN_RUN_SCRIPT bool HighlightAndGoToTextFragment(
+ bool aScrollToTextFragment);
+
+ /**
* When scroll anchoring adjusts positions in the root frame during page load,
* it may move our scroll position in the root frame.
*
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
index 8c07512093..81ffebf89a 100644
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2087,6 +2087,28 @@ void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
}
}
+// When using handled hints by an ancestor, we need to make sure that our
+// ancestor in the DOM tree is actually our ancestor in the flat tree.
+// Otherwise, we can't guarantee that e.g. a repaint from an ancestor in the DOM
+// will really end up repainting us.
+static bool CanUseHandledHintsFromAncestors(const nsIFrame* aFrame) {
+ if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // An out of flow can be parented in other part of the tree.
+ return false;
+ }
+ if (aFrame->IsColumnSpanInMulticolSubtree()) {
+ // Any column-spanner's parent frame is not its DOM parent's primary frame.
+ return false;
+ }
+ if (aFrame->IsTableCaption()) {
+ // This one is more subtle. captions are in-flow children of the table
+ // frame. But they are parented to the table wrapper. So hints handled for
+ // the inner table might not be applicable to us.
+ return false;
+ }
+ return true;
+}
+
#ifdef DEBUG
static bool IsAnonBox(const nsIFrame* aFrame) {
return aFrame->Style()->IsAnonBox();
@@ -2185,8 +2207,7 @@ static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
MOZ_ASSERT(mOwner);
- MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
- MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree());
+ MOZ_ASSERT(CanUseHandledHintsFromAncestors(mOwner));
// We allow aParent.mOwner to be null, for cases when we're not starting at
// the root of the tree. We also allow aParent.mOwner to be somewhere up our
// expected owner chain not our immediate owner, which allows us creating long
@@ -2309,7 +2330,7 @@ size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
if (parentForRestyle != aParent) {
parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
- Type::InFlow);
+ CanUseHandledHints::Yes);
}
ServoRestyleState& curRestyleState =
parentRestyleState ? *parentRestyleState : *this;
@@ -2332,7 +2353,7 @@ size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
// the other hand, presumably our mChangesHandled already has the bits
// we really want here so in practice it doesn't matter.
ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
- Type::InFlow,
+ CanUseHandledHints::Yes,
/* aAssertWrapperRestyleLength = */ false);
numProcessed +=
childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
@@ -2652,8 +2673,7 @@ static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
uint32_t equalStructs; // Not used, actually.
nsChangeHint childHint =
aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
- if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
- !aFrame->IsColumnSpanInMulticolSubtree()) {
+ if (CanUseHandledHintsFromAncestors(aFrame)) {
childHint = NS_RemoveSubsumedHints(childHint,
aRestyleState.ChangesHandledFor(aFrame));
}
@@ -2812,11 +2832,8 @@ bool RestyleManager::ProcessPostTraversal(Element* aElement,
const bool isOutOfFlow =
primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
- // We need this because any column-spanner's parent frame is not its DOM
- // parent's primary frame. We need some special check similar to out-of-flow
- // frames.
- const bool isColumnSpan =
- primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree();
+ const bool canUseHandledHints =
+ primaryFrame && CanUseHandledHintsFromAncestors(primaryFrame);
// Grab the change hint from Servo.
bool wasRestyled = false;
@@ -2848,11 +2865,12 @@ bool RestyleManager::ProcessPostTraversal(Element* aElement,
maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
} else {
maybeAnonBoxChild = primaryFrame;
- // Do not subsume change hints for the column-spanner.
- if (!isColumnSpan) {
- changeHint = NS_RemoveSubsumedHints(
- changeHint, aRestyleState.ChangesHandledFor(styleFrame));
- }
+ }
+
+ // Do not subsume change hints for the column-spanner.
+ if (canUseHandledHints) {
+ changeHint = NS_RemoveSubsumedHints(
+ changeHint, aRestyleState.ChangesHandledFor(styleFrame));
}
// If the parent wasn't restyled, the styles of our anon box parents won't
@@ -2944,10 +2962,9 @@ bool RestyleManager::ProcessPostTraversal(Element* aElement,
Maybe<ServoRestyleState> thisFrameRestyleState;
if (styleFrame) {
- auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow
- : ServoRestyleState::Type::InFlow;
-
- thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type);
+ thisFrameRestyleState.emplace(
+ *styleFrame, aRestyleState, changeHint,
+ ServoRestyleState::CanUseHandledHints(canUseHandledHints));
}
// We can't really assume as used changes from display: contents elements (or
@@ -3435,6 +3452,45 @@ void RestyleManager::ElementStateChanged(Element* aElement,
MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
}
+void RestyleManager::CustomStatesWillChange(Element& aElement) {
+ MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
+
+ IncrementUndisplayedRestyleGeneration();
+
+ // Relative selector invalidation travels ancestor and earlier sibling
+ // direction, so it's very possible that it invalidates a styled element.
+ if (!aElement.HasServoData() &&
+ !(aElement.GetSelectorFlags() &
+ NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
+ return;
+ }
+
+ ServoElementSnapshot& snapshot = SnapshotFor(aElement);
+ snapshot.AddCustomStates(aElement);
+}
+
+void RestyleManager::CustomStateChanged(Element& aElement, nsAtom* aState) {
+ ServoStyleSet& styleSet = *StyleSet();
+ MaybeRestyleForNthOfCustomState(styleSet, aElement, aState);
+ styleSet.MaybeInvalidateRelativeSelectorCustomStateDependency(
+ aElement, aState, Snapshots());
+}
+
+void RestyleManager::MaybeRestyleForNthOfCustomState(ServoStyleSet& aStyleSet,
+ Element& aChild,
+ nsAtom* aState) {
+ const auto* parentNode = aChild.GetParentNode();
+ MOZ_ASSERT(parentNode);
+ const auto parentFlags = parentNode->GetSelectorFlags();
+ if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
+ return;
+ }
+
+ if (aStyleSet.HasNthOfCustomStateDependency(aChild, aState)) {
+ RestyleSiblingsForNthOf(&aChild, parentFlags);
+ }
+}
+
void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
Element* aChild,
ElementState aChangedBits) {
diff --git a/layout/base/RestyleManager.h b/layout/base/RestyleManager.h
index 62fef15a16..8485fe18a0 100644
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -66,13 +66,11 @@ class ServoRestyleState {
// our children too if we're out of flow since they aren't necessarily
// parented in DOM order, and thus a change handled by a DOM ancestor doesn't
// necessarily mean that it's handled for an ancestor frame.
- enum class Type {
- InFlow,
- OutOfFlow,
- };
+ enum class CanUseHandledHints : bool { No = false, Yes };
ServoRestyleState(const nsIFrame& aOwner, ServoRestyleState& aParentState,
- nsChangeHint aHintForThisFrame, Type aType,
+ nsChangeHint aHintForThisFrame,
+ CanUseHandledHints aCanUseHandledHints,
bool aAssertWrapperRestyleLength = true)
: mStyleSet(aParentState.mStyleSet),
mChangeList(aParentState.mChangeList),
@@ -81,7 +79,7 @@ class ServoRestyleState {
aParentState.mPendingScrollAnchorSuppressions),
mPendingWrapperRestyleOffset(
aParentState.mPendingWrapperRestyles.Length()),
- mChangesHandled(aType == Type::InFlow
+ mChangesHandled(bool(aCanUseHandledHints)
? aParentState.mChangesHandled | aHintForThisFrame
: aHintForThisFrame)
#ifdef DEBUG
@@ -90,7 +88,7 @@ class ServoRestyleState {
mAssertWrapperRestyleLength(aAssertWrapperRestyleLength)
#endif
{
- if (aType == Type::InFlow) {
+ if (bool(aCanUseHandledHints)) {
AssertOwner(aParentState);
}
}
@@ -361,6 +359,11 @@ class RestyleManager {
void ElementStateChanged(Element*, dom::ElementState);
+ void CustomStatesWillChange(Element&);
+ void CustomStateChanged(Element&, nsAtom* aState);
+ void MaybeRestyleForNthOfCustomState(ServoStyleSet&, Element&,
+ nsAtom* aState);
+
/**
* Posts restyle hints for siblings of an element and their descendants if the
* element's parent has NODE_HAS_SLOW_SELECTOR_NTH_OF and the element has a
diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list
index d9cf87f4d2..3b1ed1e3d4 100644
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -494,7 +494,7 @@ load 1463940.html
HTTP load 1464641.html
load 1464737.html
load 1466638.html
-pref(layout.css.motion-path-ray.enabled,true) asserts(1-1) load 1467519.html # bogus size
+asserts(1-1) load 1467519.html # bogus size
load 1467688.html
load 1467964.html
load 1469354.html
@@ -547,7 +547,7 @@ load 1599532.html
pref(layout.accessiblecaret.enabled,true) load 1606492.html
load 1654315.html
load 1676301-1.html
-pref(apz.mvm.force-enabled,false) pref(dom.meta-viewport.enabled,false) pref(apz.allow_zooming,false) pref(layout.dynamic-toolbar-max-height,100) load 1689371.html
+pref(dom.meta-viewport.enabled,false) pref(apz.allow_zooming,false) pref(layout.dynamic-toolbar-max-height,100) load 1689371.html
load 1685146.html
load 1689912.html
load 1690163.html
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index 149f2f24bf..c0f35c278d 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -4985,9 +4985,8 @@ nsCSSFrameConstructor::FindSVGData(const Element& aElement,
return data;
}
-void nsCSSFrameConstructor::InsertPageBreakItem(
- nsIContent* aContent, FrameConstructionItemList& aItems,
- InsertPageBreakLocation location) {
+void nsCSSFrameConstructor::AppendPageBreakItem(
+ nsIContent* aContent, FrameConstructionItemList& aItems) {
RefPtr<ComputedStyle> pseudoStyle =
mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
PseudoStyleType::pageBreak);
@@ -4997,13 +4996,8 @@ void nsCSSFrameConstructor::InsertPageBreakItem(
static constexpr FrameConstructionData sPageBreakData(NS_NewPageBreakFrame,
FCDATA_SKIP_FRAMESET);
- if (location == InsertPageBreakLocation::eBefore) {
- aItems.PrependItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
- true);
- } else {
- aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
- true);
- }
+ aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
+ true);
}
bool nsCSSFrameConstructor::ShouldCreateItemsForChild(
@@ -6012,7 +6006,7 @@ nsIFrame* nsCSSFrameConstructor::GetInsertionPrevSibling(
// Find the frame that precedes the insertion point.
FlattenedChildIterator iter(aInsertion->mContainer);
- if (iter.ShadowDOMInvolved() || !aChild->IsRootOfNativeAnonymousSubtree()) {
+ if (!aChild->IsRootOfNativeAnonymousSubtree()) {
// The check for IsRootOfNativeAnonymousSubtree() is because editor is
// severely broken and calls us directly for native anonymous
// nodes that it creates.
@@ -8390,7 +8384,7 @@ void nsCSSFrameConstructor::RecreateFramesForContent(
}
// TODO(emilio): We technically can find the right insertion point nowadays
- // using StyleChildrenIterator rather than FlattenedTreeIterator. But we'd
+ // using StyleChildrenIterator rather than FlattenedChildIterator. But we'd
// need to tweak the setup to insert into replaced elements to filter which
// anonymous roots can be allowed, and which can't.
//
diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h
index 283d1385ce..51d5fe32d0 100644
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1423,18 +1423,8 @@ class nsCSSFrameConstructor final : public nsFrameManager {
// for it.
void ReframeTextIfNeeded(nsIContent* aContent);
- enum InsertPageBreakLocation { eBefore, eAfter };
- inline void AppendPageBreakItem(nsIContent* aContent,
- FrameConstructionItemList& aItems) {
- InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eAfter);
- }
- inline void PrependPageBreakItem(nsIContent* aContent,
- FrameConstructionItemList& aItems) {
- InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eBefore);
- }
- void InsertPageBreakItem(nsIContent* aContent,
- FrameConstructionItemList& aItems,
- InsertPageBreakLocation location);
+ void AppendPageBreakItem(nsIContent* aContent,
+ FrameConstructionItemList& aItems);
// Function to find FrameConstructionData for aElement. Will return
// null if aElement is not HTML.
diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp
index d66d55d6bb..8707dcba5f 100644
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -50,16 +50,7 @@ using BidiEmbeddingLevel = mozilla::intl::BidiEmbeddingLevel;
// like an insignificant dot
static const int32_t kMinBidiIndicatorPixels = 2;
-nsCaret::nsCaret()
- : mOverrideOffset(0),
- mBlinkCount(-1),
- mBlinkRate(0),
- mHideCount(0),
- mIsBlinkOn(false),
- mVisible(false),
- mReadOnly(false),
- mShowDuringSelection(false),
- mIgnoreUserModify(true) {}
+nsCaret::nsCaret() = default;
nsCaret::~nsCaret() { StopBlinking(); }
@@ -70,10 +61,6 @@ nsresult nsCaret::Init(PresShell* aPresShell) {
do_GetWeakReference(aPresShell); // the presshell owns us, so no addref
NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
- mShowDuringSelection =
- LookAndFeel::GetInt(LookAndFeel::IntID::ShowCaretDuringSelection,
- mShowDuringSelection ? 1 : 0) != 0;
-
RefPtr<Selection> selection =
aPresShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
if (!selection) {
@@ -82,6 +69,7 @@ nsresult nsCaret::Init(PresShell* aPresShell) {
selection->AddSelectionListener(this);
mDomSelectionWeak = selection;
+ UpdateCaretPositionFromSelectionIfNeeded();
return NS_OK;
}
@@ -137,8 +125,7 @@ void nsCaret::Terminate() {
}
mDomSelectionWeak = nullptr;
mPresShell = nullptr;
-
- mOverrideContent = nullptr;
+ mCaretPosition = {};
}
NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
@@ -148,15 +135,30 @@ Selection* nsCaret::GetSelection() { return mDomSelectionWeak; }
void nsCaret::SetSelection(Selection* aDOMSel) {
MOZ_ASSERT(aDOMSel);
mDomSelectionWeak = aDOMSel;
+ UpdateCaretPositionFromSelectionIfNeeded();
ResetBlinking();
- SchedulePaint(aDOMSel);
+ SchedulePaint();
}
-void nsCaret::SetVisible(bool inMakeVisible) {
- mVisible = inMakeVisible;
- mIgnoreUserModify = mVisible;
+void nsCaret::SetVisible(bool aVisible) {
+ const bool wasVisible = mVisible;
+ mVisible = aVisible;
+ mIgnoreUserModify = aVisible;
+ if (mVisible != wasVisible) {
+ CaretVisibilityMaybeChanged();
+ }
+}
+
+bool nsCaret::IsVisible() const { return mVisible && !mHideCount; }
+
+void nsCaret::CaretVisibilityMaybeChanged() {
ResetBlinking();
SchedulePaint();
+ if (IsVisible()) {
+ // We ignore caret position updates when the caret is not visible, so we
+ // update the caret position here if needed.
+ UpdateCaretPositionFromSelectionIfNeeded();
+ }
}
void nsCaret::AddForceHide() {
@@ -164,20 +166,18 @@ void nsCaret::AddForceHide() {
if (++mHideCount > 1) {
return;
}
- ResetBlinking();
- SchedulePaint();
+ CaretVisibilityMaybeChanged();
}
void nsCaret::RemoveForceHide() {
if (!mHideCount || --mHideCount) {
return;
}
- ResetBlinking();
- SchedulePaint();
+ CaretVisibilityMaybeChanged();
}
-void nsCaret::SetCaretReadOnly(bool inMakeReadonly) {
- mReadOnly = inMakeReadonly;
+void nsCaret::SetCaretReadOnly(bool aReadOnly) {
+ mReadOnly = aReadOnly;
ResetBlinking();
SchedulePaint();
}
@@ -333,61 +333,48 @@ nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
return rect;
}
-nsIFrame* nsCaret::GetFrameAndOffset(const Selection* aSelection,
- nsINode* aOverrideNode,
- int32_t aOverrideOffset,
- int32_t* aFrameOffset,
- nsIFrame** aUnadjustedFrame) {
- if (aUnadjustedFrame) {
- *aUnadjustedFrame = nullptr;
+auto nsCaret::CaretPositionFor(const Selection* aSelection) -> CaretPosition {
+ if (!aSelection) {
+ return {};
}
-
- nsINode* focusNode;
- int32_t focusOffset;
-
- if (aOverrideNode) {
- focusNode = aOverrideNode;
- focusOffset = aOverrideOffset;
- } else if (aSelection) {
- focusNode = aSelection->GetFocusNode();
- focusOffset = aSelection->FocusOffset();
- } else {
- return nullptr;
+ const nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
+ if (!frameSelection) {
+ return {};
}
+ nsINode* node = aSelection->GetFocusNode();
+ if (!node) {
+ return {};
+ }
+ return {
+ node,
+ int32_t(aSelection->FocusOffset()),
+ frameSelection->GetHint(),
+ frameSelection->GetCaretBidiLevel(),
+ };
+}
- if (!focusNode || !focusNode->IsContent() || !aSelection) {
- return nullptr;
+CaretFrameData nsCaret::GetFrameAndOffset(const CaretPosition& aPosition) {
+ nsINode* focusNode = aPosition.mContent;
+ int32_t focusOffset = aPosition.mOffset;
+
+ if (!focusNode || !focusNode->IsContent()) {
+ return {};
}
nsIContent* contentNode = focusNode->AsContent();
- nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
- BidiEmbeddingLevel bidiLevel = frameSelection->GetCaretBidiLevel();
- const CaretFrameData result =
- SelectionMovementUtils::GetCaretFrameForNodeOffset(
- frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
- bidiLevel, ForceEditableRegion::No);
- // FIXME: It's odd to update nsFrameSelection within this method which is
- // named as a getter.
- if (result.mFrame) {
- frameSelection->SetHint(result.mHint);
- }
- if (aUnadjustedFrame) {
- *aUnadjustedFrame = result.mUnadjustedFrame;
- }
- if (aFrameOffset) {
- *aFrameOffset = result.mOffsetInFrameContent;
- }
- return result.mFrame;
+ return SelectionMovementUtils::GetCaretFrameForNodeOffset(
+ nullptr, contentNode, focusOffset, aPosition.mHint, aPosition.mBidiLevel,
+ ForceEditableRegion::No);
}
/* static */
nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
- int32_t frameOffset;
- nsIFrame* frame = GetFrameAndOffset(aSelection, nullptr, 0, &frameOffset);
- if (frame) {
- *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
+ auto data = GetFrameAndOffset(CaretPositionFor(aSelection));
+ if (data.mFrame) {
+ *aRect =
+ GetGeometryForFrame(data.mFrame, data.mOffsetInFrameContent, nullptr);
}
- return frame;
+ return data.mFrame;
}
[[nodiscard]] static nsIFrame* GetContainingBlockIfNeeded(nsIFrame* aFrame) {
@@ -397,39 +384,56 @@ nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
return aFrame->GetContainingBlock();
}
-void nsCaret::SchedulePaint(Selection* aSelection) {
- Selection* selection;
- if (aSelection) {
- selection = aSelection;
- } else {
- selection = GetSelection();
+void nsCaret::SchedulePaint() {
+ if (mLastPaintedFrame) {
+ mLastPaintedFrame->SchedulePaint();
+ mLastPaintedFrame = nullptr;
}
-
- int32_t frameOffset;
- nsIFrame* frame = GetFrameAndOffset(selection, mOverrideContent,
- mOverrideOffset, &frameOffset);
- if (!frame) {
+ auto data = GetFrameAndOffset(mCaretPosition);
+ if (!data.mFrame) {
return;
}
-
+ nsIFrame* frame = data.mFrame;
if (nsIFrame* cb = GetContainingBlockIfNeeded(frame)) {
- cb->SchedulePaint();
- } else {
- frame->SchedulePaint();
+ frame = cb;
}
+ frame->SchedulePaint();
}
void nsCaret::SetVisibilityDuringSelection(bool aVisibility) {
+ if (mShowDuringSelection == aVisibility) {
+ return;
+ }
mShowDuringSelection = aVisibility;
+ if (mHiddenDuringSelection && aVisibility) {
+ RemoveForceHide();
+ mHiddenDuringSelection = false;
+ }
SchedulePaint();
}
-void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
- mOverrideContent = aNode;
- mOverrideOffset = aOffset;
+void nsCaret::UpdateCaretPositionFromSelectionIfNeeded() {
+ if (mFixedCaretPosition) {
+ return;
+ }
+ CaretPosition newPos = CaretPositionFor(GetSelection());
+ if (newPos == mCaretPosition) {
+ return;
+ }
+ mCaretPosition = newPos;
+ SchedulePaint();
+}
+void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
+ // Schedule a paint with the old position to invalidate.
+ mFixedCaretPosition = !!aNode;
+ if (mFixedCaretPosition) {
+ mCaretPosition = {aNode, aOffset};
+ SchedulePaint();
+ } else {
+ UpdateCaretPositionFromSelectionIfNeeded();
+ }
ResetBlinking();
- SchedulePaint();
}
void nsCaret::CheckSelectionLanguageChange() {
@@ -478,16 +482,15 @@ nsIFrame* nsCaret::GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
// taken into account when computing the caret position below.
CheckSelectionLanguageChange();
- int32_t frameOffset;
- nsIFrame* unadjustedFrame = nullptr;
- nsIFrame* frame =
- GetFrameAndOffset(GetSelection(), mOverrideContent, mOverrideOffset,
- &frameOffset, &unadjustedFrame);
- MOZ_ASSERT(!!frame == !!unadjustedFrame);
- if (!frame) {
+ auto data = GetFrameAndOffset(mCaretPosition);
+ MOZ_ASSERT(!!data.mFrame == !!data.mUnadjustedFrame);
+ if (!data.mFrame) {
return nullptr;
}
+ nsIFrame* frame = data.mFrame;
+ nsIFrame* unadjustedFrame = data.mUnadjustedFrame;
+ int32_t frameOffset(data.mOffsetInFrameContent);
// Now we have a frame, check whether it's appropriate to show the caret here.
// Note we need to check the unadjusted frame, otherwise consider the
// following case:
@@ -555,26 +558,35 @@ void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
NS_IMETHODIMP
nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason,
int32_t aAmount) {
- // Note that aDomSel, per the comment below may not be the same as our
- // selection, but that's OK since if that is the case, it wouldn't have
- // mattered what IsVisible() returns here, so we just opt for checking
- // the selection later down below.
- if ((aReason & nsISelectionListener::MOUSEUP_REASON) ||
- !IsVisible(aDomSel)) // this wont do
- return NS_OK;
-
// The same caret is shared amongst the document and any text widgets it
// may contain. This means that the caret could get notifications from
// multiple selections.
//
// If this notification is for a selection that is not the one the
- // the caret is currently interested in (mDomSelectionWeak), then there
- // is nothing to do!
+ // the caret is currently interested in (mDomSelectionWeak), or the caret
+ // position is fixed, then there is nothing to do!
+ if (mDomSelectionWeak != aDomSel) {
+ return NS_OK;
+ }
- if (mDomSelectionWeak != aDomSel) return NS_OK;
+ // Check if we need to hide / un-hide the caret due to the selection being
+ // collapsed.
+ if (!mShowDuringSelection &&
+ !aDomSel->IsCollapsed() != mHiddenDuringSelection) {
+ if (mHiddenDuringSelection) {
+ RemoveForceHide();
+ } else {
+ AddForceHide();
+ }
+ mHiddenDuringSelection = !mHiddenDuringSelection;
+ }
- ResetBlinking();
- SchedulePaint(aDomSel);
+ // We don't bother computing the caret position when invisible. We'll do it if
+ // we become visible in CaretVisibilityMaybeChanged().
+ if (IsVisible()) {
+ UpdateCaretPositionFromSelectionIfNeeded();
+ ResetBlinking();
+ }
return NS_OK;
}
@@ -589,7 +601,7 @@ void nsCaret::ResetBlinking() {
mIsBlinkOn = true;
- if (mReadOnly || !mVisible || mHideCount) {
+ if (mReadOnly || !IsVisible()) {
StopBlinking();
return;
}
@@ -647,49 +659,6 @@ size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
return total;
}
-bool nsCaret::IsMenuPopupHidingCaret() {
- // Check if there are open popups.
- nsXULPopupManager* popMgr = nsXULPopupManager::GetInstance();
- nsTArray<nsIFrame*> popups;
- popMgr->GetVisiblePopups(popups);
-
- if (popups.Length() == 0)
- return false; // No popups, so caret can't be hidden by them.
-
- // Get the selection focus content, that's where the caret would
- // go if it was drawn.
- if (!mDomSelectionWeak) {
- return true; // No selection/caret to draw.
- }
- nsCOMPtr<nsIContent> caretContent =
- nsIContent::FromNodeOrNull(mDomSelectionWeak->GetFocusNode());
- if (!caretContent) return true; // No selection/caret to draw.
-
- // If there's a menu popup open before the popup with
- // the caret, don't show the caret.
- for (uint32_t i = 0; i < popups.Length(); i++) {
- nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
- nsIContent* popupContent = popupFrame->GetContent();
-
- if (caretContent->IsInclusiveDescendantOf(popupContent)) {
- // The caret is in this popup. There were no menu popups before this
- // popup, so don't hide the caret.
- return false;
- }
-
- if (popupFrame->GetPopupType() == widget::PopupType::Menu &&
- !popupFrame->IsContextMenu()) {
- // This is an open menu popup. It does not contain the caret (else we'd
- // have returned above). Even if the caret is in a subsequent popup,
- // or another document/frame, it should be hidden.
- return true;
- }
- }
-
- // There are no open menu popups, no need to hide the caret.
- return false;
-}
-
void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect) {
NS_ASSERTION(aFrame, "Should have a frame here");
diff --git a/layout/base/nsCaret.h b/layout/base/nsCaret.h
index 43329c827f..cba719962c 100644
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -10,10 +10,10 @@
#define nsCaret_h__
#include "mozilla/MemoryReporting.h"
-#include "mozilla/dom/Selection.h"
+#include "mozilla/SelectionMovementUtils.h"
#include "nsCoord.h"
+#include "nsIFrame.h"
#include "nsISelectionListener.h"
-#include "nsIWeakReferenceUtils.h"
#include "nsPoint.h"
#include "nsRect.h"
@@ -46,10 +46,10 @@ class nsCaret final : public nsISelectionListener {
using CaretAssociationHint = mozilla::CaretAssociationHint;
- nsresult Init(mozilla::PresShell* aPresShell);
+ nsresult Init(mozilla::PresShell*);
void Terminate();
- void SetSelection(mozilla::dom::Selection* aDOMSel);
+ void SetSelection(mozilla::dom::Selection*);
mozilla::dom::Selection* GetSelection();
/**
@@ -61,40 +61,18 @@ class nsCaret final : public nsISelectionListener {
* those with user-modify: read-only
*/
void SetIgnoreUserModify(bool aIgnoreUserModify);
- /** SetVisible will set the visibility of the caret
- * @param inMakeVisible true to show the caret, false to hide it
+ /**
+ * SetVisible will set the visibility of the caret
+ * @param aVisible true to show the caret, false to hide it
*/
- void SetVisible(bool intMakeVisible);
- /** IsVisible will get the visibility of the caret.
- * This returns false if the caret is hidden because it was set
- * to not be visible, or because the selection is not collapsed, or
- * because an open popup is hiding the caret.
- * It does not take account of blinking or the caret being hidden
- * because we're in non-editable/disabled content.
+ void SetVisible(bool aVisible);
+ /**
+ * IsVisible will get the visibility of the caret.
+ * It does not take account of blinking or the caret being hidden because
+ * we're in non-editable/disabled content.
*/
- bool IsVisible(mozilla::dom::Selection* aSelection = nullptr) {
- if (!mVisible || mHideCount) {
- return false;
- }
-
- if (!mShowDuringSelection) {
- mozilla::dom::Selection* selection;
- if (aSelection) {
- selection = aSelection;
- } else {
- selection = GetSelection();
- }
- if (!selection || !selection->IsCollapsed()) {
- return false;
- }
- }
-
- if (IsMenuPopupHidingCaret()) {
- return false;
- }
+ bool IsVisible() const;
- return true;
- }
/**
* AddForceHide() increases mHideCount and hide the caret even if
* SetVisible(true) has been or will be called. This is useful when the
@@ -114,7 +92,7 @@ class nsCaret final : public nsISelectionListener {
* @param inMakeReadonly true to show the caret in a 'read only' state,
* false to show the caret in normal, editing state
*/
- void SetCaretReadOnly(bool inMakeReadonly);
+ void SetCaretReadOnly(bool aReadOnly);
/**
* @param aVisibility true if the caret should be visible even when the
* selection is not collapsed.
@@ -132,7 +110,10 @@ class nsCaret final : public nsISelectionListener {
* Schedule a repaint for the frame where the caret would appear.
* Does not check visibility etc.
*/
- void SchedulePaint(mozilla::dom::Selection* aSelection = nullptr);
+ void SchedulePaint();
+
+ nsIFrame* GetLastPaintedFrame() { return mLastPaintedFrame; }
+ void SetLastPaintedFrame(nsIFrame* aFrame) { mLastPaintedFrame = aFrame; }
/**
* Returns a frame to paint in, and the bounds of the painted caret
@@ -142,6 +123,7 @@ class nsCaret final : public nsISelectionListener {
* off).
*/
nsIFrame* GetPaintGeometry(nsRect* aRect);
+
/**
* Same as the overload above, but returns the caret and hook rects
* separately, and also computes the color if requested.
@@ -165,6 +147,22 @@ class nsCaret final : public nsISelectionListener {
// nsISelectionListener interface
NS_DECL_NSISELECTIONLISTENER
+ /** The current caret position. */
+ struct CaretPosition {
+ nsCOMPtr<nsINode> mContent;
+ int32_t mOffset = 0;
+ CaretAssociationHint mHint{0};
+ mozilla::intl::BidiEmbeddingLevel mBidiLevel;
+
+ bool operator==(const CaretPosition& aOther) const {
+ return mContent == aOther.mContent && mOffset == aOther.mOffset &&
+ mHint == aOther.mHint && mBidiLevel == aOther.mBidiLevel;
+ }
+ explicit operator bool() const { return !!mContent; }
+ };
+
+ static CaretPosition CaretPositionFor(const mozilla::dom::Selection*);
+
/**
* Gets the position and size of the caret that would be drawn for
* the focus node/offset of aSelection (assuming it would be drawn,
@@ -181,18 +179,9 @@ class nsCaret final : public nsISelectionListener {
static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
nscoord* aBidiIndicatorSize);
- // Get the frame and frame offset based on the focus node and focus offset
- // of aSelection. If aOverrideNode and aOverride are provided, use them
- // instead.
- // @param aFrameOffset return the frame offset if non-null.
- // @param aUnadjustedFrame return the original frame that the selection is
- // targeting, without any adjustment for painting.
- // @return the frame of the focus node.
- static nsIFrame* GetFrameAndOffset(const mozilla::dom::Selection* aSelection,
- nsINode* aOverrideNode,
- int32_t aOverrideOffset,
- int32_t* aFrameOffset,
- nsIFrame** aUnadjustedFrame = nullptr);
+ // Get the frame and frame offset based on aPosition.
+ static mozilla::CaretFrameData GetFrameAndOffset(
+ const CaretPosition& aPosition);
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
@@ -200,6 +189,7 @@ class nsCaret final : public nsISelectionListener {
static void CaretBlinkCallback(nsITimer* aTimer, void* aClosure);
void CheckSelectionLanguageChange();
+ void CaretVisibilityMaybeChanged();
void ResetBlinking();
void StopBlinking();
@@ -213,72 +203,73 @@ class nsCaret final : public nsISelectionListener {
void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect);
- // Returns true if we should not draw the caret because of XUL menu popups.
- // The caret should be hidden if:
- // 1. An open popup contains the caret, but a menu popup exists before the
- // caret-owning popup in the popup list (i.e. a menu is in front of the
- // popup with the caret). If the menu itself contains the caret we don't
- // hide it.
- // 2. A menu popup is open, but there is no caret present in any popup.
- // 3. The caret selection is empty.
- bool IsMenuPopupHidingCaret();
+ // If we're tracking the selection, this updates the caret position and
+ // invalidates paint as needed.
+ void UpdateCaretPositionFromSelectionIfNeeded();
nsWeakPtr mPresShell;
mozilla::WeakPtr<mozilla::dom::Selection> mDomSelectionWeak;
nsCOMPtr<nsITimer> mBlinkTimer;
- /**
- * The content to draw the caret at. If null, we use mDomSelectionWeak's
- * focus node instead.
- */
- nsCOMPtr<nsINode> mOverrideContent;
- /**
- * The character offset to draw the caret at.
- * Ignored if mOverrideContent is null.
- */
- int32_t mOverrideOffset;
+ CaretPosition mCaretPosition;
+
+ // The last frame we painted the caret in.
+ WeakFrame mLastPaintedFrame;
+
/**
* mBlinkCount is used to control the number of times to blink the caret
* before stopping the blink. This is reset each time we reset the
* blinking.
*/
- int32_t mBlinkCount;
+ int32_t mBlinkCount = -1;
/**
* mBlinkRate is the rate of the caret blinking the last time we read it.
* It is used as a way to optimize whether we need to reset the blinking
* timer. 0 or a negative value means no blinking.
*/
- int32_t mBlinkRate;
+ int32_t mBlinkRate = 0;
/**
* mHideCount is not 0, it means that somebody doesn't want the caret
* to be visible. See AddForceHide() and RemoveForceHide().
*/
- uint32_t mHideCount;
+ uint32_t mHideCount = 0;
/**
* mIsBlinkOn is true when we're in a blink cycle where the caret is on.
*/
- bool mIsBlinkOn;
+ bool mIsBlinkOn = false;
/**
* mIsVisible is true when SetVisible was last called with 'true'.
*/
- bool mVisible;
+ bool mVisible = false;
/**
* mReadOnly is true when the caret is set to "read only" mode (i.e.,
* it doesn't blink).
*/
- bool mReadOnly;
+ bool mReadOnly = false;
/**
* mShowDuringSelection is true when the caret should be shown even when
* the selection is not collapsed.
*/
- bool mShowDuringSelection;
+ bool mShowDuringSelection = false;
/**
* mIgnoreUserModify is true when the caret should be shown even when
* it's in non-user-modifiable content.
*/
- bool mIgnoreUserModify;
+ bool mIgnoreUserModify = true;
+
+ /**
+ * If the caret position is fixed, it's been overridden externally and it
+ * will not track the selection.
+ */
+ bool mFixedCaretPosition = false;
+
+ /**
+ * If we're currently hiding the caret due to the selection not being
+ * collapsed. Can only be true if mShowDuringSelection is false.
+ */
+ bool mHiddenDuringSelection = false;
};
#endif // nsCaret_h__
diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp
index 7c2e9bb776..f2bbb5e50e 100644
--- a/layout/base/nsCounterManager.cpp
+++ b/layout/base/nsCounterManager.cpp
@@ -203,7 +203,11 @@ void nsCounterList::SetScope(nsCounterNode* aNode) {
// the element itself, then we use that scope.
// Otherwise, fall through to consider scopes created by siblings (and
// their descendants) in reverse document order.
- if (aNode->mType != nsCounterNode::USE &&
+ // Do this only for the list-item counter, while the CSSWG discusses what the
+ // right thing to do here is, see bug 1548753 and
+ // https://github.com/w3c/csswg-drafts/issues/5477.
+ if (mCounterName == nsGkAtoms::list_item &&
+ aNode->mType != nsCounterNode::USE &&
StaticPrefs::layout_css_counter_ancestor_scope_enabled()) {
for (auto* p = aNode->mPseudoFrame; p; p = p->GetParent()) {
// This relies on the fact that a RESET node is always the first
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
index d1cf9bf237..8ae0e001b8 100644
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -449,6 +449,7 @@ class nsDocumentViewer final : public nsIDocumentViewer,
#ifdef NS_PRINTING
unsigned mClosingWhilePrinting : 1;
+ unsigned mCloseWindowAfterPrint : 1;
# if NS_PRINT_PREVIEW
RefPtr<nsPrintJob> mPrintJob;
@@ -520,6 +521,7 @@ nsDocumentViewer::nsDocumentViewer()
mInPermitUnloadPrompt(false),
#ifdef NS_PRINTING
mClosingWhilePrinting(false),
+ mCloseWindowAfterPrint(false),
#endif // NS_PRINTING
mReloadEncodingSource(kCharsetUninitialized),
mReloadEncoding(nullptr),
@@ -3140,6 +3142,20 @@ nsDocumentViewer::GetDoingPrintPreview(bool* aDoingPrintPreview) {
}
NS_IMETHODIMP
+nsDocumentViewer::GetCloseWindowAfterPrint(bool* aCloseWindowAfterPrint) {
+ NS_ENSURE_ARG_POINTER(aCloseWindowAfterPrint);
+
+ *aCloseWindowAfterPrint = mCloseWindowAfterPrint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetCloseWindowAfterPrint(bool aCloseWindowAfterPrint) {
+ mCloseWindowAfterPrint = aCloseWindowAfterPrint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsDocumentViewer::ExitPrintPreview() {
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
@@ -3301,15 +3317,23 @@ void nsDocumentViewer::OnDonePrinting() {
printJob->Destroy();
}
- // We are done printing, now clean up.
- //
- // For non-print-preview jobs, we are actually responsible for cleaning up
- // our whole <browser> or window (see the OPEN_PRINT_BROWSER code), so gotta
- // run window.close(), which will take care of this.
- //
- // For print preview jobs the front-end code is responsible for cleaning the
- // UI.
- if (!printJob->CreatedForPrintPreview()) {
+// We are done printing, now clean up.
+//
+// If the original document to print was not a static clone, we opened a new
+// window and are responsible for cleaning up the whole <browser> or window
+// (see the OPEN_PRINT_BROWSER code, specifically
+// handleStaticCloneCreatedForPrint()), so gotta run window.close(), which
+// will take care of this.
+//
+// Otherwise the front-end code is responsible for cleaning the UI.
+# ifdef ANDROID
+ // Android doesn't support Content Analysis and prints in a different way,
+ // so use different logic to clean up.
+ bool closeWindowAfterPrint = !printJob->CreatedForPrintPreview();
+# else
+ bool closeWindowAfterPrint = GetCloseWindowAfterPrint();
+# endif
+ if (closeWindowAfterPrint) {
if (mContainer) {
if (nsCOMPtr<nsPIDOMWindowOuter> win = mContainer->GetWindow()) {
win->Close();
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
index db766f6603..429ad335cc 100644
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -714,12 +714,9 @@ bool nsLayoutUtils::AllowZoomingForDocument(
return false;
}
// True if we allow zooming for all documents on this platform, or if we are
- // in RDM and handling meta viewports, which force zoom under some
- // circumstances.
- BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
- return StaticPrefs::apz_allow_zooming() ||
- (bc && bc->InRDMPane() &&
- nsLayoutUtils::ShouldHandleMetaViewport(aDocument));
+ // in RDM.
+ BrowsingContext* bc = aDocument->GetBrowsingContext();
+ return StaticPrefs::apz_allow_zooming() || (bc && bc->InRDMPane());
}
static bool HasVisibleAnonymousContents(Document* aDoc) {
@@ -4676,7 +4673,7 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
PhysicalAxis ourInlineAxis =
- aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ aFrame->GetWritingMode().PhysicalAxis(LogicalAxis::Inline);
const bool isInlineAxis = aAxis == ourInlineAxis;
auto resetIfKeywords = [](StyleSize& aSize, StyleSize& aMinSize,
@@ -4897,8 +4894,8 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
// We are computing the size of |aFrame|, so we use the inline & block
// dimensions of |aFrame|.
result = ratio.ComputeRatioDependentSize(
- isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
- *contentBoxSizeToBoxSizingAdjust);
+ isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
+ h, *contentBoxSizeToBoxSizingAdjust);
// We have get the inlineSizeForAspectRatio value, so we don't have to
// recompute this again below.
inlineSizeFromAspectRatio.emplace(result);
@@ -4910,8 +4907,8 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) {
h = std::max(0, h - bSizeTakenByBoxSizing);
nscoord maxISize = ratio.ComputeRatioDependentSize(
- isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
- *contentBoxSizeToBoxSizingAdjust);
+ isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
+ h, *contentBoxSizeToBoxSizingAdjust);
if (maxISize < result) {
result = maxISize;
}
@@ -4926,8 +4923,8 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) {
h = std::max(0, h - bSizeTakenByBoxSizing);
nscoord minISize = ratio.ComputeRatioDependentSize(
- isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
- *contentBoxSizeToBoxSizingAdjust);
+ isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
+ h, *contentBoxSizeToBoxSizingAdjust);
if (minISize > result) {
result = minISize;
}
@@ -4990,9 +4987,9 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
GetDefiniteSizeTakenByBoxSizing(boxSizingForAR, aFrame, !isInlineAxis,
ignorePadding, aPercentageBasis);
bSize -= bSizeTakenByBoxSizing;
- inlineSizeFromAspectRatio.emplace(ar.ComputeRatioDependentSize(
- LogicalAxis::eLogicalAxisInline, childWM, bSize,
- *contentBoxSizeToBoxSizingAdjust));
+ inlineSizeFromAspectRatio.emplace(
+ ar.ComputeRatioDependentSize(LogicalAxis::Inline, childWM, bSize,
+ *contentBoxSizeToBoxSizingAdjust));
}
}
@@ -5027,7 +5024,7 @@ nscoord nsLayoutUtils::IntrinsicForContainer(gfxContext* aRenderingContext,
MOZ_ASSERT(aFrame && aFrame->GetParent());
// We want the size aFrame will contribute to its parent's inline-size.
PhysicalAxis axis =
- aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ aFrame->GetParent()->GetWritingMode().PhysicalAxis(LogicalAxis::Inline);
return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, Nothing(),
aFlags);
}
@@ -5056,7 +5053,7 @@ nscoord nsLayoutUtils::MinSizeContributionForAxis(
StyleMaxSize maxSize =
aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
auto childWM = aFrame->GetWritingMode();
- PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(eLogicalAxisInline);
+ PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(LogicalAxis::Inline);
// According to the spec, max-content and min-content should behave as the
// property's initial values in block axis.
// It also make senses to use the initial values for -moz-fit-content and
@@ -5323,7 +5320,6 @@ nscoord nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame,
"should not be container for font size inflation");
nsIFrame::InlineMinISizeData data;
- DISPLAY_MIN_INLINE_SIZE(aFrame, data.mPrevLines);
aFrame->AddInlineMinISize(aRenderingContext, &data);
data.ForceBreak();
return data.mPrevLines;
@@ -5336,7 +5332,6 @@ nscoord nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame,
"should not be container for font size inflation");
nsIFrame::InlinePrefISizeData data;
- DISPLAY_PREF_INLINE_SIZE(aFrame, data.mPrevLines);
aFrame->AddInlinePrefISize(aRenderingContext, &data);
data.ForceBreak();
return data.mPrevLines;
@@ -7449,7 +7444,7 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
- RefPtr<DrawTarget>& aTarget) {
+ RefPtr<DrawTarget>& aTarget, bool aOptimizeSourceSurface) {
SurfaceFromElementResult result;
result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
@@ -7484,7 +7479,7 @@ SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
- if (aTarget) {
+ if (aTarget && aOptimizeSourceSurface) {
// They gave us a DrawTarget to optimize for, so even though we have a
// layers::Image, we should unconditionally try to grab a SourceSurface and
// try to optimize it.
@@ -9769,24 +9764,8 @@ void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
/* static */
bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
- auto metaViewportOverride = nsIDocShell::META_VIEWPORT_OVERRIDE_NONE;
- if (aDocument) {
- if (nsIDocShell* docShell = aDocument->GetDocShell()) {
- metaViewportOverride = docShell->GetMetaViewportOverride();
- }
- }
- switch (metaViewportOverride) {
- case nsIDocShell::META_VIEWPORT_OVERRIDE_ENABLED:
- return true;
- case nsIDocShell::META_VIEWPORT_OVERRIDE_DISABLED:
- return false;
- default:
- MOZ_ASSERT(metaViewportOverride ==
- nsIDocShell::META_VIEWPORT_OVERRIDE_NONE);
- // The META_VIEWPORT_OVERRIDE_NONE case means that there is no override
- // and we rely solely on the StaticPrefs.
- return StaticPrefs::dom_meta_viewport_enabled();
- }
+ BrowsingContext* bc = aDocument->GetBrowsingContext();
+ return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane());
}
/* static */
diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h
index 697b139ed9..135a9ad9ab 100644
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2230,7 +2230,7 @@ class nsLayoutUtils {
}
static mozilla::SurfaceFromElementResult SurfaceFromElement(
mozilla::dom::HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
- RefPtr<DrawTarget>& aTarget);
+ RefPtr<DrawTarget>& aTarget, bool aOptimizeSourceSurface = true);
/**
* When the document is editable by contenteditable attribute of its root
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
index 7d9515c495..0786ec25d9 100644
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -336,6 +336,7 @@ static const char* gExactCallbackPrefs[] = {
"intl.accept_languages",
"layout.css.devPixelsPerPx",
"layout.css.dpi",
+ "layout.css.letter-spacing.model",
"layout.css.text-transform.uppercase-eszett.enabled",
"privacy.trackingprotection.enabled",
"ui.use_standins_for_native_colors",
@@ -608,7 +609,8 @@ void nsPresContext::PreferenceChanged(const char* aPrefName) {
}
if (prefName.EqualsLiteral(
- "layout.css.text-transform.uppercase-eszett.enabled")) {
+ "layout.css.text-transform.uppercase-eszett.enabled") ||
+ prefName.EqualsLiteral("layout.css.letter-spacing.model")) {
changeHint |= NS_STYLE_HINT_REFLOW;
}
@@ -1410,6 +1412,11 @@ void nsPresContext::SetInRDMPane(bool aInRDMPane) {
}
mInRDMPane = aInRDMPane;
RecomputeTheme();
+ if (mPresShell) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod<bool>(
+ "PresShell::MaybeRecreateMobileViewportManager", mPresShell,
+ &PresShell::MaybeRecreateMobileViewportManager, true));
+ }
}
float nsPresContext::GetDeviceFullZoom() {
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
index a5c2b1ded9..7885d6b8db 100644
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1528,6 +1528,16 @@ void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent,
}
}
+void nsRefreshDriver::PostScrollEndEvent(mozilla::Runnable* aScrollEndEvent,
+ bool aDelayed) {
+ if (aDelayed) {
+ mDelayedScrollEndEvents.AppendElement(aScrollEndEvent);
+ } else {
+ mScrollEndEvents.AppendElement(aScrollEndEvent);
+ EnsureTimerStarted();
+ }
+}
+
void nsRefreshDriver::DispatchScrollEvents() {
// Scroll events are one-shot, so after running them we can drop them.
// However, dispatching a scroll event can potentially cause more scroll
@@ -1539,6 +1549,13 @@ void nsRefreshDriver::DispatchScrollEvents() {
}
}
+void nsRefreshDriver::DispatchScrollEndEvents() {
+ ScrollEventArray events = std::move(mScrollEndEvents);
+ for (auto& event : events) {
+ event->Run();
+ }
+}
+
void nsRefreshDriver::PostVisualViewportScrollEvent(
VVPScrollEvent* aScrollEvent) {
mVisualViewportScrollEvents.AppendElement(aScrollEvent);
@@ -1673,6 +1690,9 @@ void nsRefreshDriver::RunDelayedEventsSoon() {
mScrollEvents.AppendElements(mDelayedScrollEvents);
mDelayedScrollEvents.Clear();
+ mScrollEndEvents.AppendElements(mDelayedScrollEvents);
+ mDelayedScrollEndEvents.Clear();
+
mResizeEventFlushObservers.AppendElements(mDelayedResizeEventFlushObservers);
mDelayedResizeEventFlushObservers.Clear();
@@ -2008,7 +2028,7 @@ auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
if (!mVisualViewportResizeEvents.IsEmpty()) {
reasons |= TickReasons::eHasVisualViewportResizeEvents;
}
- if (!mScrollEvents.IsEmpty()) {
+ if (!mScrollEvents.IsEmpty() || !mScrollEndEvents.IsEmpty()) {
reasons |= TickReasons::eHasScrollEvents;
}
if (!mVisualViewportScrollEvents.IsEmpty()) {
@@ -2476,6 +2496,7 @@ bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
FlushAutoFocusDocuments();
DispatchScrollEvents();
DispatchVisualViewportScrollEvents();
+ DispatchScrollEndEvents();
EvaluateMediaQueriesAndReportChanges();
DispatchAnimationEvents();
RunFullscreenSteps();
diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h
index cd050f7431..7bd0f883f4 100644
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -114,7 +114,10 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
void DispatchVisualViewportResizeEvents();
void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
+ void PostScrollEndEvent(mozilla::Runnable* aScrollEndEvent,
+ bool aDelayed = false);
void DispatchScrollEvents();
+ void DispatchScrollEndEvents();
void PostVisualViewportScrollEvent(VVPScrollEvent* aScrollEvent);
void DispatchVisualViewportScrollEvents();
@@ -694,10 +697,12 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
VisualViewportResizeEventArray mVisualViewportResizeEvents;
ScrollEventArray mScrollEvents;
+ ScrollEventArray mScrollEndEvents;
VisualViewportScrollEventArray mVisualViewportScrollEvents;
// Scroll events on documents that might have events suppressed.
ScrollEventArray mDelayedScrollEvents;
+ ScrollEventArray mDelayedScrollEndEvents;
AutoTArray<mozilla::PresShell*, 16> mResizeEventFlushObservers;
AutoTArray<mozilla::PresShell*, 16> mDelayedResizeEventFlushObservers;
diff --git a/layout/base/tests/chrome/chrome.toml b/layout/base/tests/chrome/chrome.toml
index 6636b224e6..63e3da9609 100644
--- a/layout/base/tests/chrome/chrome.toml
+++ b/layout/base/tests/chrome/chrome.toml
@@ -66,6 +66,12 @@ support-files = [
"printpreview_pps16_ref.html",
"printpreview_prettyprint.xml",
"printpreview_prettyprint_ref.xhtml",
+ "printpreview_scale_test_001.html",
+ "printpreview_scale_test_001_ref.html",
+ "printpreview_scale_test_002.html",
+ "printpreview_scale_test_002_ref.html",
+ "printpreview_scale_test_003.html",
+ "printpreview_scale_test_003_ref.html",
"printpreview_mask.html",
"print_page_size1.html",
"print_page_size1_ref.html",
diff --git a/layout/base/tests/chrome/printpreview_helper.xhtml b/layout/base/tests/chrome/printpreview_helper.xhtml
index e40d2e4d5b..119c55d777 100644
--- a/layout/base/tests/chrome/printpreview_helper.xhtml
+++ b/layout/base/tests/chrome/printpreview_helper.xhtml
@@ -1708,9 +1708,252 @@ async function runTest58() {
let test = "printpreview_mixed_page_size_002.html";
// The params are just to give the file unique URLs.
await compareFiles(test + "?test", test + "?ref");
+ requestAnimationFrame(() => setTimeout(runTest59));
+}
+
+// Creates a data URL that has a single div of |divSize| em square, on a page
+// of size |pageSize| inches square.
+// |center| determines if the div should be centered horizontally.
+function createScalingTestSource(pageSize, center, name) {
+ // Styling always used.
+ let baseStyle = 'background: blue;';
+ if (center) {
+ baseStyle += 'margin: auto;';
+ }
+ const div = '<div style="width: 100px; height: 100px;' + baseStyle + '"></div>';
+ const style = '<style>@page{size:' + pageSize + 'in}body{margin:0}</style>';
+ // Add the name as a comment, to ensure every test has a unique source even
+ // if the parameters are identical.
+ const comment = '<!-- ' + name + ' -->';
+ return 'data:text/html,' + style + div + comment;
+}
+
+async function runScalingCenteredTest(refPageSize, testPageSize, paperSize,
+ name, center = true, fuzz = null) {
+ const printSettings = {
+ settings: {
+ paperWidth: paperSize,
+ paperHeight: paperSize,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ }
+ };
+ let settings = Object.create(fuzz, {
+ ref: {value: printSettings},
+ test: {value: printSettings}
+ });
+ const testSrc = createScalingTestSource(testPageSize, center, name);
+ const refSrc = createScalingTestSource(refPageSize, center, name);
+ return compareFiles(testSrc, refSrc, settings);
+}
+
+// Tests that auto-detection and use of page centering.
+// Has a smaller page on a larger sheet, where the difference is within the
+// tolerance for auto-detection.
+async function runTest59() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 2]]
+ });
+ // See bug 1680838
+ const fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 180, maxDifference: 255 } :
+ null;
+ await runScalingCenteredTest(10, 9.5, 10, "runTest59", true, fuzz);
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest60));
+}
+
+// Tests that centering won't occur when the pref disables it, using the same
+// values as runTest59.
+async function runTest60() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 0]]
+ });
+ // See bug 1680838
+ const fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 180, maxDifference: 255 } :
+ null;
+ await runScalingCenteredTest(10, 9.5, 10, "runTest60", false, fuzz);
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest61));
+}
+
+// Tests that auto-detection will reject too big a difference for page
+// centering. Has a much smaller page on a larger sheet, where the difference
+// is outside the threshold for auto-detection.
+async function runTest61() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 2]]
+ });
+ // See bug 1680838
+ const fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 450, maxDifference: 255 } :
+ null;
+ await runScalingCenteredTest(10, 8.9, 10, "runTest61", false, fuzz);
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest62));
+}
+
+// Tests that we can force page centering with the pref, using the same values
+// as runTest61.
+async function runTest62() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 1]]
+ });
+ // See bug 1680838
+ const fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 450, maxDifference: 255 } :
+ null;
+ await runScalingCenteredTest(10, 8.9, 10, "runTest62", true, fuzz);
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest63));
+}
+
+// Tests that centering will always happen if the pref forces it.
+// The sizes used in these files are very large and the scale factor very high
+// here to ensure that any errors in the calculation for centering will be
+// magnified.
+async function runTest63() {
+ let test = "printpreview_scale_test_001.html";
+ let ref = "printpreview_scale_test_001_ref.html";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 1]]
+ });
+ await compareFiles(test, ref, {
+ test: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest64));
+}
+
+// Tests that printing A4 pages on US Letter will be a close enough fit
+// that we will automatically center the page.
+async function runTest64() {
+ let test = "printpreview_scale_test_002.html";
+ let ref = "printpreview_scale_test_002_ref.html";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 2]]
+ });
+ await compareFiles(test, ref, {
+ test: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+ await SpecialPowers.popPrefEnv();
+ requestAnimationFrame(() => setTimeout(runTest65));
+}
+
+// Tests that auto-detection will reject a large enough difference in width
+// when downscaling is used to make the page fit on the paper.
+async function runTest65() {
+ let test = "printpreview_scale_test_003.html";
+ let ref = "printpreview_scale_test_003_ref.html";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.center_page_on_sheet", 2]]
+ });
+ await compareFiles(test, ref, {
+ test: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8.5,
+ paperHeight: 11,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+ await SpecialPowers.popPrefEnv();
finish();
}
+
]]></script>
<table style="border: 1px solid black;" xmlns="http://www.w3.org/1999/xhtml">
<tr><th>Print preview canvas 1</th><th>Print preview canvas 2</th></tr>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_001.html b/layout/base/tests/chrome/printpreview_scale_test_001.html
new file mode 100644
index 0000000000..e9d3122b6b
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_001.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: 10in 22in;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ width: 2in;
+ height: 2in;
+ background: blue;
+ margin-left: 4in;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_001_ref.html b/layout/base/tests/chrome/printpreview_scale_test_001_ref.html
new file mode 100644
index 0000000000..2ed4571ef1
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_001_ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: 4in 44in;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ height: 4in;
+ width: 4in;
+ background: blue;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_002.html b/layout/base/tests/chrome/printpreview_scale_test_002.html
new file mode 100644
index 0000000000..94c35ab3c3
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_002.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: A4;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ height: 200px;
+ width: 200px;
+ background: blue;
+ margin: auto;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_002_ref.html b/layout/base/tests/chrome/printpreview_scale_test_002_ref.html
new file mode 100644
index 0000000000..d73de86fe5
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_002_ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: letter;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ /* A4-on-letter requires a 0.9407 downscale. 11in = 279.4mm, and
+ * 279.4mm / 297mm = 0.9407407407..
+ * The unscaled reference case has a 200px square div, so reverse the scale
+ * to match that (rounding to 0.940741)
+ */
+ height: calc(0.940741 * 200px);
+ width: calc(0.940741 * 200px);
+ background: blue;
+ margin: auto;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_003.html b/layout/base/tests/chrome/printpreview_scale_test_003.html
new file mode 100644
index 0000000000..2b1d68ff60
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_003.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: 8.5in 13in;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ /* 13in / 11in = 1.1818181... */
+ height: calc(1.1818182 * 200px);
+ width: calc(1.1818182 * 200px);
+ background: blue;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_scale_test_003_ref.html b/layout/base/tests/chrome/printpreview_scale_test_003_ref.html
new file mode 100644
index 0000000000..41a3f87889
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_scale_test_003_ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+ <style>
+@page {
+ size: 8.5in 11in;
+ margin: 0;
+}
+body{
+ margin:0;
+}
+div{
+ /* 11in / 13in = 0.8461538 */
+ height: 200px;
+ width: 200px;
+ background: blue;
+}
+ </style>
+</head>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/test_bug420499.xhtml b/layout/base/tests/chrome/test_bug420499.xhtml
index 22fefd7987..8db69ff6c1 100644
--- a/layout/base/tests/chrome/test_bug420499.xhtml
+++ b/layout/base/tests/chrome/test_bug420499.xhtml
@@ -84,7 +84,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=420499
function popupMenuShownHandler() {
window.removeEventListener("popupshown", popupMenuShownHandler);
- ok(!isCaretVisible(), "Caret shouldn't be visible when menu open");
+ ok(isCaretVisible(), "Caret shouldn't be visible when menu open");
window.addEventListener("popuphidden", ensureParagraphFocused);
$("menu").open = false;
}