diff options
Diffstat (limited to 'layout/base')
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; } |