diff options
Diffstat (limited to 'layout/base')
30 files changed, 656 insertions, 736 deletions
diff --git a/layout/base/LayoutTelemetryTools.h b/layout/base/LayoutTelemetryTools.h index 25c80dcc2c..43eee79923 100644 --- a/layout/base/LayoutTelemetryTools.h +++ b/layout/base/LayoutTelemetryTools.h @@ -38,9 +38,9 @@ enum class LayoutSubsystem : uint8_t { }; using LayoutSubsystemDurations = - EnumeratedArray<LayoutSubsystem, LayoutSubsystem::Count, double>; + EnumeratedArray<LayoutSubsystem, double, size_t(LayoutSubsystem::Count)>; using LayoutFlushCount = - EnumeratedArray<FlushKind, FlushKind::Count, SaturateUint8>; + EnumeratedArray<FlushKind, SaturateUint8, size_t(FlushKind::Count)>; struct Data { Data(); diff --git a/layout/base/MotionPathUtils.cpp b/layout/base/MotionPathUtils.cpp index c81020645d..4045f2304a 100644 --- a/layout/base/MotionPathUtils.cpp +++ b/layout/base/MotionPathUtils.cpp @@ -111,9 +111,11 @@ CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) { const auto size = CSSSize::FromAppUnits( (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ? nsLayoutUtils::ComputeSVGReferenceRect( - aFrame, aFrame->StyleSVGReset()->HasNonScalingStroke() - ? StyleGeometryBox::FillBox - : StyleGeometryBox::StrokeBox) + aFrame, + aFrame->StyleSVGReset()->HasNonScalingStroke() + ? StyleGeometryBox::FillBox + : StyleGeometryBox::StrokeBox, + nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes) : nsLayoutUtils::ComputeHTMLReferenceRect( aFrame, StyleGeometryBox::BorderBox)) .Size()); diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp index e159239f16..a975372dea 100644 --- a/layout/base/PositionedEventTargeting.cpp +++ b/layout/base/PositionedEventTargeting.cpp @@ -249,18 +249,6 @@ static nsIContent* GetClickableAncestor( return content; } - // Bug 921928: we don't have access to the content of remote iframe. - // So fluffing won't go there. We do an optimistic assumption here: - // that the content of the remote iframe needs to be a target. - if (content->IsHTMLElement(nsGkAtoms::iframe) && - content->AsElement()->AttrValueIs(kNameSpaceID_None, - nsGkAtoms::mozbrowser, - nsGkAtoms::_true, eIgnoreCase) && - content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote, - nsGkAtoms::_true, eIgnoreCase)) { - return content; - } - // See nsCSSFrameConstructor::FindXULTagData. This code is not // really intended to be used with XUL, though. if (content->IsAnyOfXULElements( diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 90a9eee411..31c21c3377 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -9,11 +9,13 @@ #include "mozilla/PresShell.h" #include "Units.h" +#include "mozilla/EventForwards.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/LargestContentfulPaint.h" +#include "mozilla/dom/MouseEventBinding.h" #include "mozilla/dom/PerformanceMainThread.h" #include "mozilla/dom/HTMLAreaElement.h" #include "mozilla/ArrayUtils.h" @@ -45,6 +47,7 @@ #include "mozilla/StaticPrefs_font.h" #include "mozilla/StaticPrefs_image.h" #include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_test.h" #include "mozilla/StaticPrefs_toolkit.h" #include "mozilla/Try.h" #include "mozilla/TextEvents.h" @@ -562,35 +565,52 @@ class nsBeforeFirstPaintDispatcher : public Runnable { class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final { public: AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent, - nsIFrame* aFrame, nsIContent** aTargetContent) { + nsIFrame* aFrame, nsIContent* aTargetContent, + nsIContent** aOutTargetContent) { MOZ_ASSERT(aEvent); - if (!aTargetContent || aEvent->mClass != ePointerEventClass) { + if (!aOutTargetContent || aEvent->mClass != ePointerEventClass) { // Make the destructor happy. - mTargetContent = nullptr; + mOutTargetContent = nullptr; return; } MOZ_ASSERT(aShell); - MOZ_ASSERT(aFrame); - MOZ_ASSERT(!aFrame->GetContent() || - aShell->GetDocument() == aFrame->GetContent()->OwnerDoc()); + MOZ_ASSERT_IF(aFrame && aFrame->GetContent(), + aShell->GetDocument() == aFrame->GetContent()->OwnerDoc()); mShell = aShell; mWeakFrame = aFrame; - mTargetContent = aTargetContent; - aShell->mPointerEventTarget = aFrame->GetContent(); + mOutTargetContent = aOutTargetContent; + mFromTouch = aEvent->AsPointerEvent()->mFromTouchEvent; + // Touch event target may have no frame, e.g., removed from the DOM + MOZ_ASSERT_IF(!mFromTouch, aFrame); + mOriginalPointerEventTarget = aShell->mPointerEventTarget = + aFrame ? aFrame->GetContent() : aTargetContent; } ~AutoPointerEventTargetUpdater() { - if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) { + if (!mOutTargetContent || !mShell || mWeakFrame.IsAlive()) { return; } - mShell->mPointerEventTarget.swap(*mTargetContent); + if (mFromTouch) { + // If the source event is a touch event, the touch event target should + // always be same target as preceding ePointerDown. Therefore, we should + // always set it back to the original event target. + mOriginalPointerEventTarget.swap(*mOutTargetContent); + } else { + // If the source event is not a touch event (must be a mouse event in + // this case), the event should be fired on the closest inclusive ancestor + // of the pointer event target which is still connected. The mutations + // are tracked by PresShell::ContentRemoved. Therefore, we should set it. + mShell->mPointerEventTarget.swap(*mOutTargetContent); + } } private: RefPtr<PresShell> mShell; + nsCOMPtr<nsIContent> mOriginalPointerEventTarget; AutoWeakFrame mWeakFrame; - nsIContent** mTargetContent; + nsIContent** mOutTargetContent; + bool mFromTouch = false; }; bool PresShell::sDisableNonTestMouseEvents = false; @@ -5421,7 +5441,6 @@ bool PresShell::IsTransparentContainerElement() const { case dom::PrefersColorSchemeOverride::Dark: return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark; case dom::PrefersColorSchemeOverride::None: - case dom::PrefersColorSchemeOverride::EndGuard_: break; } } @@ -6888,18 +6907,13 @@ PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) { return nullptr; } - nsCOMPtr<nsIContent> content = do_QueryInterface(oldTouch->GetTarget()); + nsIContent* const content = + nsIContent::FromEventTargetOrNull(oldTouch->GetTarget()); if (!content) { return nullptr; } - nsIFrame* contentFrame = content->GetPrimaryFrame(); - if (!contentFrame) { - return nullptr; - } - - PresShell* presShell = contentFrame->PresContext()->GetPresShell(); - if (presShell) { + if (PresShell* const presShell = content->OwnerDoc()->GetPresShell()) { return presShell; } } @@ -7249,12 +7263,6 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates( return NS_OK; } - // frame could be null after dispatching pointer events. - // XXX Despite of this comment, we update the event target data outside - // DispatchPrecedingPointerEvent(). Can we make it call - // UpdateTouchEventTarget()? - eventTargetData.UpdateTouchEventTarget(aGUIEvent); - // Handle the event in the correct shell. // We pass the subshell's root frame as the frame to start from. This is // the only correct alternative; if the event was captured then it @@ -7267,7 +7275,17 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates( nsresult rv = eventHandler.HandleEventWithCurrentEventInfo( aGUIEvent, aEventStatus, true, MOZ_KnownLive(eventTargetData.mOverrideClickTarget)); - return rv; + if (NS_FAILED(rv) || + MOZ_UNLIKELY(eventTargetData.mPresShell->IsDestroying())) { + return rv; + } + + if (aGUIEvent->mMessage == eTouchEnd) { + MaybeSynthesizeCompatMouseEventsForTouchEnd(aGUIEvent->AsTouchEvent(), + aEventStatus); + } + + return NS_OK; } bool PresShell::EventHandler::MaybeFlushPendingNotifications( @@ -7436,41 +7454,58 @@ bool PresShell::EventHandler::DispatchPrecedingPointerEvent( AutoWeakFrame weakTargetFrame(targetFrame); AutoWeakFrame weakFrame(aEventTargetData->GetFrame()); - nsCOMPtr<nsIContent> content(aEventTargetData->GetContent()); + nsCOMPtr<nsIContent> pointerEventTargetContent( + aEventTargetData->GetContent()); RefPtr<PresShell> presShell(aEventTargetData->mPresShell); - nsCOMPtr<nsIContent> targetContent; + nsCOMPtr<nsIContent> mouseOrTouchEventTargetContent; PointerEventHandler::DispatchPointerFromMouseOrTouch( - presShell, aEventTargetData->GetFrame(), content, aGUIEvent, - aDontRetargetEvents, aEventStatus, getter_AddRefs(targetContent)); + presShell, aEventTargetData->GetFrame(), pointerEventTargetContent, + aGUIEvent, aDontRetargetEvents, aEventStatus, + getter_AddRefs(mouseOrTouchEventTargetContent)); // If the target frame is alive, the caller should keep handling the event // unless event target frame is destroyed. - if (weakTargetFrame.IsAlive()) { - return weakFrame.IsAlive(); + if (weakTargetFrame.IsAlive() && weakFrame.IsAlive()) { + aEventTargetData->UpdateTouchEventTarget(aGUIEvent); + return true; } - // If the event is not a mouse event, the caller should keep handling the - // event unless event target frame is destroyed. Note that this case is - // not defined by the spec. - if (aGUIEvent->mClass != eMouseEventClass) { - return weakFrame.IsAlive(); + presShell->FlushPendingNotifications(FlushType::Layout); + if (MOZ_UNLIKELY(mPresShell->IsDestroying())) { + return false; } - // Spec defines that mouse events must be dispatched to the same target as - // the pointer event. If the target is no longer participating in its - // ownerDocument's tree, fire the event at the original target's nearest - // ancestor node - if (!targetContent) { + // The spec defines that mouse events must be dispatched to the same target as + // the pointer event. + // The Touch Events spec defines that touch events must be dispatched to the + // same target as touch start and the other browsers dispatch touch events + // even if the touch event target is not connected to the document. + // Retargetting the event is handled by AutoPointerEventTargetUpdater and + // mouseOrTouchEventTargetContent stores the result. + + // If the target is no longer participating in its ownerDocument's tree, + // fire the event at the original target's nearest ancestor node. + if (!mouseOrTouchEventTargetContent) { + MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass); return false; } - aEventTargetData->SetFrameAndContent(targetContent->GetPrimaryFrame(), - targetContent); - aEventTargetData->mPresShell = PresShell::GetShellForEventTarget( - aEventTargetData->GetFrame(), aEventTargetData->GetContent()); + aEventTargetData->SetFrameAndContent( + mouseOrTouchEventTargetContent->GetPrimaryFrame(), + mouseOrTouchEventTargetContent); + aEventTargetData->mPresShell = + mouseOrTouchEventTargetContent->IsInComposedDoc() + ? PresShell::GetShellForEventTarget(aEventTargetData->GetFrame(), + aEventTargetData->GetContent()) + : mouseOrTouchEventTargetContent->OwnerDoc()->GetPresShell(); // If new target PresShel is not found, we cannot keep handling the event. - return !!aEventTargetData->mPresShell; + if (!aEventTargetData->mPresShell) { + return false; + } + + aEventTargetData->UpdateTouchEventTarget(aGUIEvent); + return true; } /** @@ -7586,6 +7621,60 @@ bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret( return true; } +void PresShell::EventHandler::MaybeSynthesizeCompatMouseEventsForTouchEnd( + const WidgetTouchEvent* aTouchEndEvent, + const nsEventStatus* aStatus) const { + MOZ_ASSERT(aTouchEndEvent->mMessage == eTouchEnd); + + // If the eTouchEnd event is dispatched via APZ, APZCCallbackHelper dispatches + // a set of mouse events with better handling. Therefore, we don't need to + // handle that here. + if (!aTouchEndEvent->mFlags.mIsSynthesizedForTests || + StaticPrefs::test_events_async_enabled()) { + return; + } + + // If the tap was consumed or 2 or more touches occurred, we don't need the + // compatibility mouse events. + if (*aStatus == nsEventStatus_eConsumeNoDefault || + !TouchManager::IsSingleTapEndToDoDefault(aTouchEndEvent)) { + return; + } + + if (NS_WARN_IF(!aTouchEndEvent->mWidget)) { + return; + } + + nsCOMPtr<nsIWidget> widget = aTouchEndEvent->mWidget; + + // NOTE: I think that we don't need to implement a double click here becase + // WebDriver does not support a way to synthesize a double click and Chrome + // does not fire "dblclick" even if doing `pointerDown().pointerUp()` twice. + // FIXME: Currently we don't support long tap. + RefPtr<PresShell> presShell = mPresShell; + for (const EventMessage message : {eMouseMove, eMouseDown, eMouseUp}) { + if (MOZ_UNLIKELY(presShell->IsDestroying())) { + break; + } + nsIFrame* frameForPresShell = GetNearestFrameContainingPresShell(presShell); + if (!frameForPresShell) { + break; + } + WidgetMouseEvent event(true, message, widget, WidgetMouseEvent::eReal, + WidgetMouseEvent::eNormal); + event.mRefPoint = aTouchEndEvent->mTouches[0]->mRefPoint; + event.mButton = MouseButton::ePrimary; + event.mButtons = message == eMouseDown ? MouseButtonsFlag::ePrimaryFlag + : MouseButtonsFlag::eNoButtons; + event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; + event.mClickCount = message == eMouseMove ? 0 : 1; + event.mModifiers = aTouchEndEvent->mModifiers; + event.convertToPointer = false; + nsEventStatus mouseEventStatus = nsEventStatus_eIgnore; + presShell->HandleEvent(frameForPresShell, &event, false, &mouseEventStatus); + } +} + bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) { MOZ_ASSERT(aGUIEvent); @@ -8252,7 +8341,7 @@ nsresult PresShell::EventHandler::HandleEventWithTarget( mPresShell->RecordPointerLocation(aEvent->AsMouseEvent()); } AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame, - aTargetContent); + aNewEventContent, aTargetContent); AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame, aNewEventContent); nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false, @@ -8360,7 +8449,7 @@ nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo( manager->TryToFlushPendingNotificationsToIME(); } - FinalizeHandlingEvent(aEvent); + FinalizeHandlingEvent(aEvent, aEventStatus); RecordEventHandlingResponsePerformance(aEvent); @@ -8512,7 +8601,8 @@ bool PresShell::EventHandler::PrepareToDispatchEvent( } } -void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) { +void PresShell::EventHandler::FinalizeHandlingEvent( + WidgetEvent* aEvent, const nsEventStatus* aStatus) { switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: @@ -8562,6 +8652,16 @@ void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) { } return; } + case eTouchStart: + case eTouchMove: + case eTouchEnd: + case eTouchCancel: + case eTouchPointerCancel: + case eMouseLongTap: + case eContextMenu: { + mPresShell->mTouchManager.PostHandleEvent(aEvent, aStatus); + break; + } default: return; } @@ -11955,7 +12055,7 @@ void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget( nsIFrame* newFrame = TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent); if (!newFrame) { - return; // XXX Why don't we stop handling the event in this case? + return; } SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent); return; diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index c6a965e81e..482ace1421 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -2440,6 +2440,14 @@ class PresShell final : public nsStubDocumentObserver, nsEventStatus* aEventStatus); /** + * Maybe dispatch mouse events for aTouchEnd. This should be called after + * aTouchEndEvent is dispatched into the DOM. + */ + MOZ_CAN_RUN_SCRIPT void MaybeSynthesizeCompatMouseEventsForTouchEnd( + const WidgetTouchEvent* aTouchEndEvent, + const nsEventStatus* aStatus) const; + + /** * MaybeDiscardOrDelayKeyboardEvent() may discared or put aGUIEvent into * the delayed event queue if it's a keyboard event and if we should do so. * If aGUIEvent is not a keyboard event, this does nothing. @@ -2821,8 +2829,10 @@ class PresShell final : public nsStubDocumentObserver, * and then, this cleans up the state of mPresShell and aEvent. * * @param aEvent The handled event. + * @param aStatus The status of aEvent. Must not be nullptr. */ - MOZ_CAN_RUN_SCRIPT void FinalizeHandlingEvent(WidgetEvent* aEvent); + MOZ_CAN_RUN_SCRIPT void FinalizeHandlingEvent(WidgetEvent* aEvent, + const nsEventStatus* aStatus); /** * AutoCurrentEventInfoSetter() pushes and pops current event info of diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 9c313a254c..8c07512093 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -2709,6 +2709,7 @@ enum class ServoPostTraversalFlags : uint32_t { MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags) +#ifdef ACCESSIBILITY static bool IsVisibleForA11y(const ComputedStyle& aStyle) { return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert(); } @@ -2717,6 +2718,7 @@ static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) { return aStyle.StyleDisplay()->mContentVisibility != StyleContentVisibility::Hidden; } +#endif // Send proper accessibility notifications and return post traversal // flags for kids. @@ -3450,11 +3452,8 @@ void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet, static inline bool AttributeInfluencesOtherPseudoClassState( const Element& aElement, const nsAtom* aAttribute) { - // We must record some state for :-moz-browser-frame, - // :-moz-table-border-nonzero, and :-moz-select-list-box. - if (aAttribute == nsGkAtoms::mozbrowser) { - return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame); - } + // We must record some state for :-moz-table-border-nonzero and + // :-moz-select-list-box. if (aAttribute == nsGkAtoms::border) { return aElement.IsHTMLElement(nsGkAtoms::table); diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp index 0c34567b65..42ea78eb2e 100644 --- a/layout/base/TouchManager.cpp +++ b/layout/base/TouchManager.cpp @@ -7,9 +7,13 @@ #include "TouchManager.h" +#include "Units.h" +#include "mozilla/EventForwards.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_test.h" +#include "mozilla/TimeStamp.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/EventTarget.h" -#include "mozilla/PresShell.h" #include "mozilla/layers/InputAPZContext.h" #include "nsIContent.h" #include "nsIFrame.h" @@ -24,6 +28,8 @@ namespace mozilla { StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>> TouchManager::sCaptureTouchList; layers::LayersId TouchManager::sCaptureTouchLayersId; +TimeStamp TouchManager::sSingleTouchStartTimeStamp; +LayoutDeviceIntPoint TouchManager::sSingleTouchStartPoint; /*static*/ void TouchManager::InitializeStatics() { @@ -168,7 +174,7 @@ nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame( } nsIFrame* frame = nullptr; - for (int32_t i = aEvent->mTouches.Length(); i;) { + for (uint32_t i = aEvent->mTouches.Length(); i;) { --i; dom::Touch* touch = aEvent->mTouches[i]; if (TouchManager::HasCapturedTouch(touch->Identifier())) { @@ -176,9 +182,35 @@ nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame( } MOZ_ASSERT(touch->mOriginalTarget); - nsCOMPtr<nsIContent> targetContent = do_QueryInterface(touch->GetTarget()); - nsIFrame* targetFrame = - targetContent ? targetContent->GetPrimaryFrame() : nullptr; + nsIContent* const targetContent = + nsIContent::FromEventTargetOrNull(touch->GetTarget()); + if (MOZ_UNLIKELY(!targetContent)) { + touch->mIsTouchEventSuppressed = true; + continue; + } + + // Even if the target content is not connected, we should dispatch the touch + // start event except when the target content is owned by different + // document. + if (MOZ_UNLIKELY(!targetContent->IsInComposedDoc())) { + if (anyTarget && anyTarget->OwnerDoc() != targetContent->OwnerDoc()) { + touch->mIsTouchEventSuppressed = true; + continue; + } + if (!anyTarget) { + anyTarget = targetContent; + } + touch->SetTouchTarget(targetContent->GetAsElementOrParentElement()); + if (PresShell* const presShell = + targetContent->OwnerDoc()->GetPresShell()) { + if (nsIFrame* rootFrame = presShell->GetRootFrame()) { + frame = rootFrame; + } + } + continue; + } + + nsIFrame* targetFrame = targetContent->GetPrimaryFrame(); if (targetFrame && !anyTarget) { anyTarget = targetContent; } else { @@ -202,10 +234,12 @@ nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame( touch->mIsTouchEventSuppressed = true; } else { targetFrame = newTargetFrame; - targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent)); - touch->SetTouchTarget(targetContent - ? targetContent->GetAsElementOrParentElement() - : nullptr); + nsCOMPtr<nsIContent> newTargetContent; + targetFrame->GetContentForEvent(aEvent, + getter_AddRefs(newTargetContent)); + touch->SetTouchTarget( + newTargetContent ? newTargetContent->GetAsElementOrParentElement() + : nullptr); } } if (targetFrame) { @@ -236,8 +270,11 @@ bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, // touch event associated to. We cache layers id of the first touchstart // event, all subsequent touch events will use the same layers id. sCaptureTouchLayersId = aEvent->mLayersId; + sSingleTouchStartTimeStamp = aEvent->mTimeStamp; + sSingleTouchStartPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint; } else { touchEvent->mLayersId = sCaptureTouchLayersId; + sSingleTouchStartTimeStamp = TimeStamp(); } // Add any new touches to the queue WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches; @@ -404,6 +441,60 @@ bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus, return true; } +void TouchManager::PostHandleEvent(const WidgetEvent* aEvent, + const nsEventStatus* aStatus) { + switch (aEvent->mMessage) { + case eTouchMove: { + if (sSingleTouchStartTimeStamp.IsNull()) { + break; + } + if (*aStatus == nsEventStatus_eConsumeNoDefault) { + sSingleTouchStartTimeStamp = TimeStamp(); + break; + } + const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent(); + if (touchEvent->mTouches.Length() > 1) { + sSingleTouchStartTimeStamp = TimeStamp(); + break; + } + if (touchEvent->mTouches.Length() == 1) { + // If the touch moved too far from the start point, don't treat the + // touch as a tap. + const float distance = + static_cast<float>((sSingleTouchStartPoint - + aEvent->AsTouchEvent()->mTouches[0]->mRefPoint) + .Length()); + const float maxDistance = + StaticPrefs::apz_touch_start_tolerance() * + (MOZ_LIKELY(touchEvent->mWidget) ? touchEvent->mWidget->GetDPI() + : 96.0f); + if (distance > maxDistance) { + sSingleTouchStartTimeStamp = TimeStamp(); + } + } + break; + } + case eTouchStart: + case eTouchEnd: + if (*aStatus == nsEventStatus_eConsumeNoDefault && + !sSingleTouchStartTimeStamp.IsNull()) { + sSingleTouchStartTimeStamp = TimeStamp(); + } + break; + case eTouchCancel: + case eTouchPointerCancel: + case eMouseLongTap: + case eContextMenu: { + if (!sSingleTouchStartTimeStamp.IsNull()) { + sSingleTouchStartTimeStamp = TimeStamp(); + } + break; + } + default: + break; + } +} + /*static*/ already_AddRefed<nsIContent> TouchManager::GetAnyCapturedTouchTarget() { nsCOMPtr<nsIContent> result = nullptr; @@ -473,4 +564,25 @@ bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch, return true; } +/* static */ +bool TouchManager::IsSingleTapEndToDoDefault( + const WidgetTouchEvent* aTouchEndEvent) { + MOZ_ASSERT(aTouchEndEvent); + MOZ_ASSERT(aTouchEndEvent->mFlags.mIsSynthesizedForTests); + MOZ_ASSERT(!StaticPrefs::test_events_async_enabled()); + if (sSingleTouchStartTimeStamp.IsNull() || + aTouchEndEvent->mTouches.Length() != 1) { + return false; + } + // If it's pressed long time, we should not treat it as a single tap because + // a long press should cause opening context menu by default. + if ((aTouchEndEvent->mTimeStamp - sSingleTouchStartTimeStamp) + .ToMilliseconds() > StaticPrefs::apz_max_tap_time()) { + return false; + } + NS_WARNING_ASSERTION(aTouchEndEvent->mTouches[0]->mChanged, + "The single tap end should be changed"); + return true; +} + } // namespace mozilla diff --git a/layout/base/TouchManager.h b/layout/base/TouchManager.h index 0e12c25c7d..40b9083bdb 100644 --- a/layout/base/TouchManager.h +++ b/layout/base/TouchManager.h @@ -12,6 +12,7 @@ #ifndef TouchManager_h_ #define TouchManager_h_ +#include "Units.h" #include "mozilla/BasicEvents.h" #include "mozilla/dom/Touch.h" #include "mozilla/StaticPtr.h" @@ -20,6 +21,7 @@ namespace mozilla { class PresShell; +class TimeStamp; class TouchManager { public: @@ -52,6 +54,8 @@ class TouchManager { bool PreHandleEvent(mozilla::WidgetEvent* aEvent, nsEventStatus* aStatus, bool& aTouchIsNew, nsCOMPtr<nsIContent>& aCurrentEventContent); + void PostHandleEvent(const mozilla::WidgetEvent* aEvent, + const nsEventStatus* aStatus); static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget(); static bool HasCapturedTouch(int32_t aId); @@ -59,6 +63,12 @@ class TouchManager { static bool ShouldConvertTouchToPointer(const dom::Touch* aTouch, const WidgetTouchEvent* aEvent); + // This should be called after PostHandleEvent() is called. Note that this + // cannot check touches outside this process. So, this should not be used for + // actual user input handling. This is designed for a fallback path to + // dispatch mouse events for touch events synthesized without APZ. + static bool IsSingleTapEndToDoDefault(const WidgetTouchEvent* aTouchEndEvent); + private: void EvictTouches(dom::Document* aLimitToDocument = nullptr); static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch, @@ -77,6 +87,12 @@ class TouchManager { static StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchInfo>> sCaptureTouchList; static layers::LayersId sCaptureTouchLayersId; + // The last start of a single tap. This will be set to "Null" if the tap is + // consumed or becomes not a single tap. + static TimeStamp sSingleTouchStartTimeStamp; + // The last start point of the single tap tracked with + // sSingleTouchStartTimeStamp. + static LayoutDeviceIntPoint sSingleTouchStartPoint; }; } // namespace mozilla diff --git a/layout/base/crashtests/1435015.html b/layout/base/crashtests/1435015.html deleted file mode 100644 index 329aaca22f..0000000000 --- a/layout/base/crashtests/1435015.html +++ /dev/null @@ -1,9 +0,0 @@ -<!doctype html> -<style> - div { display: contents; } -</style> -<math></math> -<script> - let div = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'div'); - document.querySelector('math').appendChild(div); -</script> diff --git a/layout/base/crashtests/243159-2.xhtml b/layout/base/crashtests/243159-2.xhtml deleted file mode 100644 index 79d9bcd90a..0000000000 --- a/layout/base/crashtests/243159-2.xhtml +++ /dev/null @@ -1,26 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml" - xmlns:mathml="http://www.w3.org/1998/Math/MathML"> - <body onload="run()"> - <mathml:math id="test" style="display: block"> - </mathml:math> -<script> - function run() { - var t1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", - "mtable"); - var t2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", - "mtable"); - var r1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", - "mtr"); - var r2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", - "mtr"); - var test = - document.getElementsByTagNameNS("http://www.w3.org/1998/Math/MathML", "math")[0]; - t1.appendChild(r1); - test.appendChild(t1); - test.appendChild(t2); - t2.appendChild(r2); - - } -</script> -</body> -</html> diff --git a/layout/base/crashtests/355993-1.xhtml b/layout/base/crashtests/355993-1.xhtml deleted file mode 100644 index e902ee550e..0000000000 --- a/layout/base/crashtests/355993-1.xhtml +++ /dev/null @@ -1,26 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml"> - -<head> -<style> -body, body * { position: fixed; } -</style> -</head> - -<body> - - -<div> - <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> - - <mtable> - <mtr> - <mtd> - <mn>1</mn> - </mtd> - </mtr> - </mtable> - </math> -</div> - -</body> -</html>
\ No newline at end of file diff --git a/layout/base/crashtests/384649-1.xhtml b/layout/base/crashtests/384649-1.xhtml deleted file mode 100644 index e2ba50cdee..0000000000 --- a/layout/base/crashtests/384649-1.xhtml +++ /dev/null @@ -1,31 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<style> - -/* use attribute selector instead of the .class shorthand to work around bug 379178 */ - -*[class="fixed"] { position: fixed; } - -math, mtable, mtr { position: inherit; } - -</style> -</head> - -<body> - -<div class="fixed"> - <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> - <mtable> - <mtr class="fixed"> - <mtd><mi>x</mi></mtd> - </mtr> - <mtr> - <mtd><mi>y</mi></mtd> - </mtr> - </mtable> - </math> -</div> - -</body> - -</html> diff --git a/layout/base/crashtests/394150-1.xhtml b/layout/base/crashtests/394150-1.xhtml deleted file mode 100644 index b2349c9f8b..0000000000 --- a/layout/base/crashtests/394150-1.xhtml +++ /dev/null @@ -1,27 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML"> -<head> -<script> - -function boom() -{ - var ms = document.createElementNS("http://www.w3.org/1998/Math/MathML", "ms"); - var textNode = document.getElementById("emptyset").firstChild; - var mrow = document.getElementById("mrow"); - - ms.appendChild(textNode); // *move* the text node from one place to another! - mrow.appendChild(ms); -} - -</script> -</head> - -<body onload="boom();"> - -<math xmlns="http://www.w3.org/1998/Math/MathML"> -<merror><emptyset id="emptyset"> - <mrow id="mrow"></mrow></emptyset></merror> -</math> - -</body> - -</html> diff --git a/layout/base/crashtests/399676-1.xhtml b/layout/base/crashtests/399676-1.xhtml deleted file mode 100644 index 82b547e5ea..0000000000 --- a/layout/base/crashtests/399676-1.xhtml +++ /dev/null @@ -1,7 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML"> -<head> -</head> -<body> -<math:mtd><span style="float: right;" /></math:mtd> -</body> -</html> diff --git a/layout/base/crashtests/400445-1.xhtml b/layout/base/crashtests/400445-1.xhtml deleted file mode 100644 index 9cb71dbbd6..0000000000 --- a/layout/base/crashtests/400445-1.xhtml +++ /dev/null @@ -1,22 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML"> -<head> -<script> - -function boom() -{ - var mtd1 = document.getElementById("mtd1"); - var mtd2 = document.getElementById("mtd2"); - var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span"); - - mtd1.appendChild(newSpan); - mtd1.removeAttribute("columnspan"); - mtd2.setAttribute("columnspan", 0); -} - -</script> -</head> - -<body onload="boom();"> -<math:mtd id="mtd1" columnspan="5" /><math:mtd id="mtd2" /> -</body> -</html> diff --git a/layout/base/crashtests/400904-1.xhtml b/layout/base/crashtests/400904-1.xhtml deleted file mode 100644 index a00f42fd02..0000000000 --- a/layout/base/crashtests/400904-1.xhtml +++ /dev/null @@ -1,20 +0,0 @@ -<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML"> -<head> -<script type="text/javascript"> - -function boom() -{ - var MATHML_NS = "http://www.w3.org/1998/Math/MathML"; - var mtd = document.getElementById("mtd"); - var n = document.createElementNS(MATHML_NS, 'mrow'); - mtd.appendChild(n); - mtd.setAttribute('rowspan', 7); -} - -</script> -</head> - -<body onload="boom();"> -<math:mtd id="mtd"></math:mtd><math:mtr><math:mrow></math:mrow></math:mtr> -</body> -</html> diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list index 44572dad53..d9cf87f4d2 100644 --- a/layout/base/crashtests/crashtests.list +++ b/layout/base/crashtests/crashtests.list @@ -7,7 +7,7 @@ load 56746-1.html load 89101-1.html load 89358-1.html load 90205-1.html -skip-if(cocoaWidget&&browserIsRemote) load 99776-1.html # Bug 849747 +skip-if(cocoaWidget) load 99776-1.html # Bug 849747 load 118931-1.html load 121533-1.html load 123049-1.html @@ -31,7 +31,6 @@ load 234851-1.html load 234851-2.html load 241300-1.html load 243159-1.html -load 243159-2.xhtml load 243519-1.html load 244490-1.html load 254367-1.html @@ -116,7 +115,6 @@ load 350267-1.html load 354133-1.html load 354766-1.xhtml load 355989-1.xhtml -load 355993-1.xhtml load chrome://reftest/content/crashtests/layout/base/crashtests/356325-1.xhtml load 358729-1.xhtml skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/360339-1.xhtml @@ -151,7 +149,6 @@ load 383129-1.html load 384344-1.html load 384392-1.xhtml load 384392-2.svg -load 384649-1.xhtml load 385354.html load 385866-1.xhtml load 385880-1.xhtml @@ -163,7 +160,6 @@ load 388715-1.html load 390976-1.html load 393661-1.html load 393801-1.html -load 394150-1.xhtml load 397011-1.xhtml load 398510-1.xhtml load 398733-1.html @@ -171,13 +167,10 @@ load 398733-2.html load 399132-1.xhtml load 399219-1.xhtml load 399365-1.html -load 399676-1.xhtml load 399687-1.html load 399940-1.xhtml load 399951-1.html load 399994-1.html -load 400445-1.xhtml -load 400904-1.xhtml load 401734-1.html load 401734-2.html needs-focus pref(accessibility.browsewithcaret,true) load 403048.html @@ -258,7 +251,7 @@ load 468645-3.xhtml load 469861-1.xhtml load 469861-2.xhtml load 470851-1.xhtml -asserts-if(Android&&!asyncPan,1-2) load 473042.xhtml # bug 1034369 (may also cause a few assertions to be registered on the next test) +load 473042.xhtml # bug 1034369 (may also cause a few assertions to be registered on the next test) asserts(1) load 474075.html # bug 1775003 load 477333-1.xhtml load 477731-1.html @@ -483,7 +476,6 @@ load 1428892.html load 1429088.html load 1429961.html load 1429962.html -load 1435015.html load 1437155.html load 1439016.html load 1442018-1.html diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp index b1215972c2..0331a7e5a0 100644 --- a/layout/base/nsBidiPresUtils.cpp +++ b/layout/base/nsBidiPresUtils.cpp @@ -47,9 +47,10 @@ using BidiClass = intl::BidiClass; using BidiDirection = intl::BidiDirection; using BidiEmbeddingLevel = intl::BidiEmbeddingLevel; -static const char16_t kSpace = 0x0020; +static const char16_t kNextLine = 0x0085; static const char16_t kZWSP = 0x200B; static const char16_t kLineSeparator = 0x2028; +static const char16_t kParagraphSeparator = 0x2029; static const char16_t kObjectSubstitute = 0xFFFC; static const char16_t kLRE = 0x202A; static const char16_t kRLE = 0x202B; @@ -60,11 +61,12 @@ static const char16_t kLRI = 0x2066; static const char16_t kRLI = 0x2067; static const char16_t kFSI = 0x2068; static const char16_t kPDI = 0x2069; -// All characters with Bidi type Segment Separator or Block Separator +// All characters with Bidi type Segment Separator or Block Separator. +// This should be kept in sync with the table in ReplaceSeparators. static const char16_t kSeparators[] = { - char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb), - char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f), - char16_t(0x85), char16_t(0x2029), char16_t(0)}; + char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb), + char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f), + kNextLine, kParagraphSeparator, char16_t(0)}; #define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1) @@ -478,115 +480,99 @@ struct MOZ_STACK_CLASS BidiParagraphData { } }; -struct MOZ_STACK_CLASS BidiLineData { - AutoTArray<nsIFrame*, 16> mLogicalFrames; - AutoTArray<nsIFrame*, 16> mVisualFrames; - AutoTArray<int32_t, 16> mIndexMap; - AutoTArray<BidiEmbeddingLevel, 16> mLevels; - bool mIsReordered; - +class MOZ_STACK_CLASS BidiLineData { + public: BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) { - /** - * Initialize the logically-ordered array of frames using the top-level - * frames of a single line - */ - bool isReordered = false; - bool hasRTLFrames = false; - bool hasVirtualControls = false; - + // Initialize the logically-ordered array of frames using the top-level + // frames of a single line auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) { mLogicalFrames.AppendElement(frame); mLevels.AppendElement(level); mIndexMap.AppendElement(0); - if (level.IsRTL()) { - hasRTLFrames = true; - } }; - bool firstFrame = true; for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--; frame = frame->GetNextSibling()) { FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame); - // Ignore virtual control before the first frame. Doing so should - // not affect the visual result, but could avoid running into the - // stripping code below for many cases. - if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) { + if (bidiData.precedingControl != kBidiLevelNone) { appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl); - hasVirtualControls = true; } appendFrame(frame, bidiData.embeddingLevel); - firstFrame = false; } // Reorder the line - BidiEngine::ReorderVisual(mLevels.Elements(), FrameCount(), + BidiEngine::ReorderVisual(mLevels.Elements(), mLevels.Length(), mIndexMap.Elements()); - // Strip virtual frames - if (hasVirtualControls) { - auto originalCount = mLogicalFrames.Length(); - AutoTArray<int32_t, 16> realFrameMap; - realFrameMap.SetCapacity(originalCount); - size_t count = 0; - for (auto i : IntegerRange(originalCount)) { - if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) { - realFrameMap.AppendElement(-1); - } else { - mLogicalFrames[count] = mLogicalFrames[i]; - mLevels[count] = mLevels[i]; - realFrameMap.AppendElement(count); - count++; - } + // Collect the frames in visual order, omitting virtual controls + // and noting whether frames are reordered. + for (uint32_t i = 0; i < mIndexMap.Length(); i++) { + nsIFrame* frame = mLogicalFrames[mIndexMap[i]]; + if (frame == NS_BIDI_CONTROL_FRAME) { + continue; } - // Only keep index map for real frames. - for (size_t i = 0, j = 0; i < originalCount; ++i) { - auto newIndex = realFrameMap[mIndexMap[i]]; - if (newIndex != -1) { - mIndexMap[j] = newIndex; - j++; - } + mVisualFrameIndex.AppendElement(mIndexMap[i]); + if (int32_t(i) != mIndexMap[i]) { + mIsReordered = true; } - mLogicalFrames.TruncateLength(count); - mLevels.TruncateLength(count); - mIndexMap.TruncateLength(count); } + } - for (int32_t i = 0; i < FrameCount(); i++) { - mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i])); - if (i != mIndexMap[i]) { - isReordered = true; - } - } + uint32_t LogicalFrameCount() const { return mLogicalFrames.Length(); } + uint32_t VisualFrameCount() const { return mVisualFrameIndex.Length(); } - // If there's an RTL frame, assume the line is reordered - mIsReordered = isReordered || hasRTLFrames; + nsIFrame* LogicalFrameAt(uint32_t aIndex) const { + return mLogicalFrames[aIndex]; } - int32_t FrameCount() const { return mLogicalFrames.Length(); } + nsIFrame* VisualFrameAt(uint32_t aIndex) const { + return mLogicalFrames[mVisualFrameIndex[aIndex]]; + } - nsIFrame* LogicalFrameAt(int32_t aIndex) const { - return mLogicalFrames[aIndex]; + std::pair<nsIFrame*, BidiEmbeddingLevel> VisualFrameAndLevelAt( + uint32_t aIndex) const { + int32_t index = mVisualFrameIndex[aIndex]; + return std::pair(mLogicalFrames[index], mLevels[index]); } - nsIFrame* VisualFrameAt(int32_t aIndex) const { - return mVisualFrames[aIndex]; + bool IsReordered() const { return mIsReordered; } + + void InitContinuationStates(nsContinuationStates* aContinuationStates) const { + for (auto* frame : mLogicalFrames) { + if (frame != NS_BIDI_CONTROL_FRAME) { + nsBidiPresUtils::InitContinuationStates(frame, aContinuationStates); + } + } } + + private: + AutoTArray<nsIFrame*, 16> mLogicalFrames; + AutoTArray<int32_t, 16> mVisualFrameIndex; + AutoTArray<int32_t, 16> mIndexMap; + AutoTArray<BidiEmbeddingLevel, 16> mLevels; + bool mIsReordered = false; }; #ifdef DEBUG extern "C" { -void MOZ_EXPORT DumpFrameArray(const nsTArray<nsIFrame*>& aFrames) { - for (nsIFrame* frame : aFrames) { +void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) { + auto dump = [](nsIFrame* frame) { if (frame == NS_BIDI_CONTROL_FRAME) { fprintf_stderr(stderr, "(Bidi control frame)\n"); } else { frame->List(); } - } -} + }; -void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) { - DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames); + if (aVisualOrder) { + for (uint32_t i = 0; i < aData->VisualFrameCount(); i++) { + dump(aData->VisualFrameAt(i)); + } + } else { + for (uint32_t i = 0; i < aData->LogicalFrameCount(); i++) { + dump(aData->LogicalFrameAt(i)); + } + } } } #endif @@ -870,11 +856,32 @@ nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) { return ResolveParagraph(&bpd); } +// In ResolveParagraph, we previously used ReplaceChar(kSeparators, kSpace) +// to convert separators to spaces, but this hard-coded implementation is +// substantially faster than the general-purpose ReplaceChar function. +// This must be kept in sync with the definition of kSeparators. +static inline void ReplaceSeparators(nsString& aText, size_t aStartIndex = 0) { + for (char16_t* cp = aText.BeginWriting() + aStartIndex; + cp < aText.EndWriting(); cp++) { + if (MOZ_UNLIKELY(*cp < char16_t(' '))) { + static constexpr char16_t SeparatorToSpace[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ' ', ' ', + ' ', 0x0c, ' ', 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, ' ', ' ', ' ', ' ', + }; + *cp = SeparatorToSpace[*cp]; + } else if (MOZ_UNLIKELY(*cp == kNextLine || *cp == kParagraphSeparator)) { + *cp = ' '; + } + } +} + nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) { if (aBpd->BufferLength() < 1) { return NS_OK; } - aBpd->mBuffer.ReplaceChar(kSeparators, kSpace); + + ReplaceSeparators(aBpd->mBuffer); int32_t runCount; @@ -1506,8 +1513,19 @@ nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine, aStart = 0; } + // No need to bidi-reorder the line if there's only a single frame. + if (aNumFramesOnLine == 1) { + auto bidiData = nsBidiPresUtils::GetFrameBidiData(aFirstFrameOnLine); + nsContinuationStates continuationStates; + InitContinuationStates(aFirstFrameOnLine, &continuationStates); + return aStart + RepositionFrame(aFirstFrameOnLine, + bidiData.embeddingLevel.IsLTR(), aStart, + &continuationStates, aLineWM, false, + containerSize); + } + BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); - return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart); + return RepositionInlineFrames(bld, aLineWM, containerSize, aStart); } nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) { @@ -1857,40 +1875,30 @@ void nsBidiPresUtils::InitContinuationStates( } /* static */ -nscoord nsBidiPresUtils::RepositionInlineFrames(BidiLineData* aBld, +nscoord nsBidiPresUtils::RepositionInlineFrames(const BidiLineData& aBld, WritingMode aLineWM, const nsSize& aContainerSize, nscoord aStart) { - nscoord start = aStart; - nsIFrame* frame; - int32_t count = aBld->mVisualFrames.Length(); - int32_t index; nsContinuationStates continuationStates; + aBld.InitContinuationStates(&continuationStates); - // Initialize continuation states to (nullptr, 0) for - // each frame on the line. - for (index = 0; index < count; index++) { - InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates); - } - - // Reposition frames in visual order - int32_t step, limit; if (aLineWM.IsBidiLTR()) { - index = 0; - step = 1; - limit = count; + for (auto index : IntegerRange(aBld.VisualFrameCount())) { + auto [frame, level] = aBld.VisualFrameAndLevelAt(index); + aStart += + RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates, + aLineWM, false, aContainerSize); + } } else { - index = count - 1; - step = -1; - limit = -1; - } - for (; index != limit; index += step) { - frame = aBld->VisualFrameAt(index); - start += RepositionFrame( - frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start, - &continuationStates, aLineWM, false, aContainerSize); + for (auto index : Reversed(IntegerRange(aBld.VisualFrameCount()))) { + auto [frame, level] = aBld.VisualFrameAndLevelAt(index); + aStart += + RepositionFrame(frame, level.IsLTR(), aStart, &continuationStates, + aLineWM, false, aContainerSize); + } } - return start; + + return aStart; } bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, @@ -1898,16 +1906,15 @@ bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine, nsIFrame** aFirstVisual, nsIFrame** aLastVisual) { BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); - int32_t count = bld.FrameCount(); if (aFirstVisual) { *aFirstVisual = bld.VisualFrameAt(0); } if (aLastVisual) { - *aLastVisual = bld.VisualFrameAt(count - 1); + *aLastVisual = bld.VisualFrameAt(bld.VisualFrameCount() - 1); } - return bld.mIsReordered; + return bld.IsReordered(); } nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame, @@ -1915,9 +1922,11 @@ nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame, int32_t aNumFramesOnLine) { BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); - int32_t count = bld.mVisualFrames.Length(); + int32_t count = bld.VisualFrameCount(); - if (aFrame == nullptr && count) return bld.VisualFrameAt(0); + if (!aFrame && count) { + return bld.VisualFrameAt(0); + } for (int32_t i = 0; i < count - 1; i++) { if (bld.VisualFrameAt(i) == aFrame) { @@ -1933,9 +1942,11 @@ nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame, int32_t aNumFramesOnLine) { BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine); - int32_t count = bld.mVisualFrames.Length(); + int32_t count = bld.VisualFrameCount(); - if (aFrame == nullptr && count) return bld.VisualFrameAt(count - 1); + if (!aFrame && count) { + return bld.VisualFrameAt(count - 1); + } for (int32_t i = 1; i < count; i++) { if (bld.VisualFrameAt(i) == aFrame) { @@ -2476,9 +2487,19 @@ nsresult nsBidiPresUtils::ProcessTextForRenderingContext( nsIRenderingContextBidiProcessor processor(&aRenderingContext, aTextRunConstructionDrawTarget, &aFontMetrics, nsPoint(aX, aY)); - nsAutoString text(aText, aLength); - text.ReplaceChar(kSeparators, ' '); - return ProcessText(text.BeginReading(), text.Length(), aBaseLevel, + nsDependentSubstring text(aText, aLength); + auto separatorIndex = text.FindCharInSet(kSeparators); + if (separatorIndex == kNotFound) { + return ProcessText(text.BeginReading(), text.Length(), aBaseLevel, + aPresContext, processor, aMode, aPosResolve, + aPosResolveCount, aWidth, aPresContext->BidiEngine()); + } + + // We need to replace any block or segment separators with space for bidi + // processing, so make a local copy. + nsAutoString localText(text); + ReplaceSeparators(localText, separatorIndex); + return ProcessText(localText.BeginReading(), localText.Length(), aBaseLevel, aPresContext, processor, aMode, aPosResolve, aPosResolveCount, aWidth, aPresContext->BidiEngine()); } diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h index c369a06f2d..80cf8fbc41 100644 --- a/layout/base/nsBidiPresUtils.h +++ b/layout/base/nsBidiPresUtils.h @@ -21,7 +21,7 @@ #endif struct BidiParagraphData; -struct BidiLineData; +class BidiLineData; class gfxContext; class nsFontMetrics; class nsIFrame; @@ -398,6 +398,8 @@ class nsBidiPresUtils { mozilla::ComputedStyle* aComputedStyle); private: + friend class BidiLineData; + static nsresult ProcessTextForRenderingContext( const char16_t* aText, int32_t aLength, mozilla::intl::BidiEmbeddingLevel aBaseLevel, nsPresContext* aPresContext, @@ -536,7 +538,7 @@ class nsBidiPresUtils { * * @lina 04/11/2000 */ - static nscoord RepositionInlineFrames(BidiLineData* aBld, + static nscoord RepositionInlineFrames(const BidiLineData& aBld, mozilla::WritingMode aLineWM, const nsSize& aContainerSize, nscoord aStart); diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 3c1896c162..149f2f24bf 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -170,6 +170,7 @@ nsIFrame* NS_NewSVGFEImageFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewSVGFEUnstyledLeafFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewFileControlLabelFrame(PresShell*, ComputedStyle*); +nsIFrame* NS_NewComboboxLabelFrame(PresShell*, ComputedStyle*); nsIFrame* NS_NewMiddleCroppingLabelFrame(PresShell*, ComputedStyle*); #include "mozilla/dom/NodeInfo.h" @@ -320,7 +321,7 @@ static bool ShouldSuppressColumnSpanDescendants(nsIFrame* aFrame) { } if (!aFrame->IsBlockFrameOrSubclass() || - aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS | NS_FRAME_OUT_OF_FLOW) || + aFrame->HasAnyStateBits(NS_BLOCK_BFC | NS_FRAME_OUT_OF_FLOW) || aFrame->IsFixedPosContainingBlock()) { // Need to suppress column-span if we: // - Are a different block formatting context, @@ -2286,7 +2287,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructTableCell( aState, content, innerPseudoStyle, cellFrame, PseudoStyleType::scrolledContent, false, scrollFrame); } - cellInnerFrame = NS_NewBlockFormattingContext(mPresShell, innerPseudoStyle); + cellInnerFrame = NS_NewBlockFrame(mPresShell, innerPseudoStyle); } auto* parent = scrollFrame ? scrollFrame : cellFrame; InitAndRestoreFrame(aState, content, parent, cellInnerFrame); @@ -2566,7 +2567,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructDocElementFrame( MOZ_ASSERT(display->mDisplay == StyleDisplay::Block || display->mDisplay == StyleDisplay::FlowRoot, "Unhandled display type for root element"); - contentFrame = NS_NewBlockFormattingContext(mPresShell, computedStyle); + contentFrame = NS_NewBlockFrame(mPresShell, computedStyle); ConstructBlock( state, aDocElement, state.GetGeometricParent(*display, mDocElementContainingBlock), @@ -3012,91 +3013,33 @@ static inline void ClearLazyBits(nsIContent* aStartContent, } } -nsIFrame* nsCSSFrameConstructor::ConstructSelectFrame( +/* static */ +const nsCSSFrameConstructor::FrameConstructionData* +nsCSSFrameConstructor::FindSelectData(const Element& aElement, + ComputedStyle& aStyle) { + // Construct a frame-based listbox or combobox + const auto* sel = dom::HTMLSelectElement::FromNode(aElement); + MOZ_ASSERT(sel); + if (sel->IsCombobox()) { + static constexpr FrameConstructionData sComboboxData{ + ToCreationFunc(NS_NewComboboxControlFrame), 0, + PseudoStyleType::buttonContent}; + return &sComboboxData; + } + // FIXME: Can we simplify this to avoid needing ConstructListboxSelectFrame, + // and reuse ConstructScrollableBlock or so? + static constexpr FrameConstructionData sListBoxData{ + &nsCSSFrameConstructor::ConstructListBoxSelectFrame}; + return &sListBoxData; +} + +nsIFrame* nsCSSFrameConstructor::ConstructListBoxSelectFrame( nsFrameConstructorState& aState, FrameConstructionItem& aItem, nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay, nsFrameList& aFrameList) { nsIContent* const content = aItem.mContent; ComputedStyle* const computedStyle = aItem.mComputedStyle; - // Construct a frame-based listbox or combobox - dom::HTMLSelectElement* sel = dom::HTMLSelectElement::FromNode(content); - MOZ_ASSERT(sel); - if (sel->IsCombobox()) { - // Construct a frame-based combo box. - // The frame-based combo box is built out of three parts. A display area, a - // button and a dropdown list. The display area and button are created - // through anonymous content. The drop-down list's frame is created - // explicitly. The combobox frame shares its content with the drop-down - // list. - nsComboboxControlFrame* comboboxFrame = - NS_NewComboboxControlFrame(mPresShell, computedStyle); - - // Save the history state so we don't restore during construction - // since the complete tree is required before we restore. - nsILayoutHistoryState* historyState = aState.mFrameState; - aState.mFrameState = nullptr; - // Initialize the combobox frame - InitAndRestoreFrame(aState, content, - aState.GetGeometricParent(*aStyleDisplay, aParentFrame), - comboboxFrame); - - comboboxFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES); - - aState.AddChild(comboboxFrame, aFrameList, content, aParentFrame); - - // Resolve pseudo element style for the dropdown list - RefPtr<ComputedStyle> listStyle = - mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle( - PseudoStyleType::dropDownList, computedStyle); - - // child frames of combobox frame - nsFrameList childList; - - // Create display and button frames from the combobox's anonymous content. - // The anonymous content is appended to existing anonymous content for this - // element (the scrollbars). - // - // nsComboboxControlFrame needs special frame creation behavior for its - // first piece of anonymous content, which means that we can't take the - // normal ProcessChildren path. - AutoTArray<nsIAnonymousContentCreator::ContentInfo, 2> newAnonymousItems; - DebugOnly<nsresult> rv = - GetAnonymousContent(content, comboboxFrame, newAnonymousItems); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - MOZ_ASSERT(!newAnonymousItems.IsEmpty()); - - // Manually create a frame for the special NAC. - MOZ_ASSERT(newAnonymousItems[0].mContent == - comboboxFrame->GetDisplayNode()); - newAnonymousItems.RemoveElementAt(0); - nsIFrame* customFrame = comboboxFrame->CreateFrameForDisplayNode(); - MOZ_ASSERT(customFrame); - childList.AppendFrame(nullptr, customFrame); - - nsFrameConstructorSaveState floatSaveState; - aState.MaybePushFloatContainingBlock(comboboxFrame, floatSaveState); - - // The other piece of NAC can take the normal path. - AutoFrameConstructionItemList fcItems(this); - AutoFrameConstructionPageName pageNameTracker(aState, comboboxFrame); - AddFCItemsForAnonymousContent(aState, comboboxFrame, newAnonymousItems, - fcItems, pageNameTracker); - ConstructFramesFromItemList(aState, fcItems, comboboxFrame, - /* aParentIsWrapperAnonBox = */ false, - childList); - - comboboxFrame->SetInitialChildList(FrameChildListID::Principal, - std::move(childList)); - - aState.mFrameState = historyState; - if (aState.mFrameState) { - // Restore frame state for the entire subtree of |comboboxFrame|. - RestoreFrameState(comboboxFrame, aState.mFrameState); - } - return comboboxFrame; - } - // Listbox, not combobox nsContainerFrame* listFrame = NS_NewListControlFrame(mPresShell, computedStyle); @@ -3175,7 +3118,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame( const nsStyleDisplay* fieldsetContentDisplay = fieldsetContentStyle->StyleDisplay(); - bool isScrollable = fieldsetContentDisplay->IsScrollableOverflow(); + const bool isScrollable = fieldsetContentDisplay->IsScrollableOverflow(); nsContainerFrame* scrollFrame = nullptr; if (isScrollable) { fieldsetContentStyle = BeginBuildingScrollFrame( @@ -3204,8 +3147,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame( MOZ_ASSERT(fieldsetContentDisplay->mDisplay == StyleDisplay::Block, "bug in StyleAdjuster::adjust_for_fieldset_content?"); - contentFrame = - NS_NewBlockFormattingContext(mPresShell, fieldsetContentStyle); + contentFrame = NS_NewBlockFrame(mPresShell, fieldsetContentStyle); if (fieldsetContentStyle->StyleColumn()->IsColumnContainerStyle()) { contentFrameTop = BeginBuildingColumns( aState, content, parent, contentFrame, fieldsetContentStyle); @@ -3499,11 +3441,18 @@ nsCSSFrameConstructor::FindHTMLData(const Element& aElement, "Unexpected parent for fieldset content anon box"); if (aElement.IsInNativeAnonymousSubtree() && - aElement.NodeInfo()->NameAtom() == nsGkAtoms::label && - static_cast<nsFileControlFrame*>(do_QueryFrame(aParentFrame))) { - static constexpr FrameConstructionData sFileLabelData( - NS_NewFileControlLabelFrame); - return &sFileLabelData; + aElement.NodeInfo()->NameAtom() == nsGkAtoms::label && aParentFrame) { + if (static_cast<nsFileControlFrame*>(do_QueryFrame(aParentFrame))) { + static constexpr FrameConstructionData sFileLabelData( + NS_NewFileControlLabelFrame); + return &sFileLabelData; + } + if (aParentFrame->GetParent() && + aParentFrame->GetParent()->IsComboboxControlFrame()) { + static constexpr FrameConstructionData sComboboxLabelData( + NS_NewComboboxLabelFrame); + return &sComboboxLabelData; + } } static constexpr FrameConstructionDataByTag sHTMLData[] = { @@ -3515,7 +3464,7 @@ nsCSSFrameConstructor::FindHTMLData(const Element& aElement, SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame), SIMPLE_TAG_CHAIN(input, nsCSSFrameConstructor::FindInputData), SIMPLE_TAG_CREATE(textarea, NS_NewTextControlFrame), - COMPLEX_TAG_CREATE(select, &nsCSSFrameConstructor::ConstructSelectFrame), + SIMPLE_TAG_CHAIN(select, nsCSSFrameConstructor::FindSelectData), SIMPLE_TAG_CHAIN(object, nsCSSFrameConstructor::FindObjectData), SIMPLE_TAG_CHAIN(embed, nsCSSFrameConstructor::FindObjectData), COMPLEX_TAG_CREATE(fieldset, @@ -3741,11 +3690,6 @@ void nsCSSFrameConstructor::ConstructFrameFromItemInternal( CHECK_ONLY_ONE_BIT(FCDATA_WRAP_KIDS_IN_BLOCKS, FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS); #undef CHECK_ONLY_ONE_BIT - NS_ASSERTION(!(bits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) || - ((bits & FCDATA_FUNC_IS_FULL_CTOR) && - data->mFunc.mFullConstructor == - &nsCSSFrameConstructor::ConstructNonScrollableBlock), - "Unexpected FCDATA_FORCED_NON_SCROLLABLE_BLOCK flag"); MOZ_ASSERT( !(bits & FCDATA_IS_WRAPPER_ANON_BOX) || (bits & FCDATA_USE_CHILD_ITEMS), "Wrapper anon boxes should always have FCDATA_USE_CHILD_ITEMS"); @@ -3823,7 +3767,7 @@ void nsCSSFrameConstructor::ConstructFrameFromItemInternal( innerFrame = outerFrame; break; default: { - innerFrame = NS_NewBlockFormattingContext(mPresShell, outerStyle); + innerFrame = NS_NewBlockFrame(mPresShell, outerStyle); if (outerStyle->StyleColumn()->IsColumnContainerStyle()) { outerFrame = BeginBuildingColumns(aState, content, container, innerFrame, outerStyle); @@ -3836,7 +3780,7 @@ void nsCSSFrameConstructor::ConstructFrameFromItemInternal( } } } else { - innerFrame = NS_NewBlockFormattingContext(mPresShell, outerStyle); + innerFrame = NS_NewBlockFrame(mPresShell, outerStyle); InitAndRestoreFrame(aState, content, container, innerFrame); outerFrame = innerFrame; } @@ -4386,14 +4330,14 @@ nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay& aDisplay, // XXX Ignore tables for the time being (except caption) const uint32_t kCaptionCtorFlags = FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable); - bool caption = aDisplay.mDisplay == StyleDisplay::TableCaption; - bool suppressScrollFrame = false; - bool needScrollFrame = + const bool caption = aDisplay.mDisplay == StyleDisplay::TableCaption; + const bool needScrollFrame = aDisplay.IsScrollableOverflow() && !propagatedScrollToViewport; if (needScrollFrame) { - suppressScrollFrame = mPresShell->GetPresContext()->IsPaginated() && - aDisplay.IsBlockOutsideStyle() && - !aElement.IsInNativeAnonymousSubtree(); + const bool suppressScrollFrame = + mPresShell->GetPresContext()->IsPaginated() && + aDisplay.IsBlockOutsideStyle() && + !aElement.IsInNativeAnonymousSubtree(); if (!suppressScrollFrame) { static constexpr FrameConstructionData sScrollableBlockData[2] = { {&nsCSSFrameConstructor::ConstructScrollableBlock}, @@ -4401,27 +4345,14 @@ nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay& aDisplay, kCaptionCtorFlags}}; return &sScrollableBlockData[caption]; } - - // If the scrollable frame would have propagated its scrolling to the - // viewport, we still want to construct a regular block rather than a - // scrollframe so that it paginates correctly, but we don't want to set - // the bit on the block that tells it to clip at paint time. - if (mPresShell->GetPresContext()->ElementWouldPropagateScrollStyles( - aElement)) { - suppressScrollFrame = false; - } } // Handle various non-scrollable blocks. - static constexpr FrameConstructionData sNonScrollableBlockData[2][2] = { - {{&nsCSSFrameConstructor::ConstructNonScrollableBlock}, - {&nsCSSFrameConstructor::ConstructNonScrollableBlock, - kCaptionCtorFlags}}, - {{&nsCSSFrameConstructor::ConstructNonScrollableBlock, - FCDATA_FORCED_NON_SCROLLABLE_BLOCK}, - {&nsCSSFrameConstructor::ConstructNonScrollableBlock, - FCDATA_FORCED_NON_SCROLLABLE_BLOCK | kCaptionCtorFlags}}}; - return &sNonScrollableBlockData[suppressScrollFrame][caption]; + static constexpr FrameConstructionData sNonScrollableBlockData[2] = { + {&nsCSSFrameConstructor::ConstructNonScrollableBlock}, + {&nsCSSFrameConstructor::ConstructNonScrollableBlock, + kCaptionCtorFlags}}; + return &sNonScrollableBlockData[caption]; } case StyleDisplayInside::Table: { static constexpr FrameConstructionData data( @@ -4555,8 +4486,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlock( // Create our block frame // pass a temporary stylecontext, the correct one will be set later - nsContainerFrame* scrolledFrame = - NS_NewBlockFormattingContext(mPresShell, computedStyle); + nsContainerFrame* scrolledFrame = NS_NewBlockFrame(mPresShell, computedStyle); // Make sure to AddChild before we call ConstructBlock so that we // end up before our descendants in fixed-pos lists as needed. @@ -4579,26 +4509,7 @@ nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlock( nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay, nsFrameList& aFrameList) { ComputedStyle* const computedStyle = aItem.mComputedStyle; - - // We want a block formatting context root in paginated contexts for - // every block that would be scrollable in a non-paginated context. - // We mark our blocks with a bit here if this condition is true, so - // we can check it later in nsIFrame::ApplyPaginatedOverflowClipping. - bool clipPaginatedOverflow = - (aItem.mFCData->mBits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) != 0; - nsFrameState flags = nsFrameState(0); - if ((aDisplay->IsAbsolutelyPositionedStyle() || aDisplay->IsFloatingStyle() || - aDisplay->DisplayInside() == StyleDisplayInside::FlowRoot || - clipPaginatedOverflow) && - !aParentFrame->IsInSVGTextSubtree()) { - flags = NS_BLOCK_STATIC_BFC; - if (clipPaginatedOverflow) { - flags |= NS_BLOCK_CLIP_PAGINATED_OVERFLOW; - } - } - nsContainerFrame* newFrame = NS_NewBlockFrame(mPresShell, computedStyle); - newFrame->AddStateBits(flags); ConstructBlock(aState, aItem.mContent, aState.GetGeometricParent(*aDisplay, aParentFrame), aParentFrame, computedStyle, &newFrame, aFrameList, @@ -5165,10 +5076,14 @@ static bool ShouldSuppressFrameInSelect(const nsIContent* aParent, return false; } + // Allow native anonymous content no matter what. + if (aChild.IsRootOfNativeAnonymousSubtree()) { + return false; + } + // Options with labels have their label text added in ::before by forms.css. // Suppress frames for their child text. - if (aParent->IsHTMLElement(nsGkAtoms::option) && - !aChild.IsRootOfNativeAnonymousSubtree()) { + if (aParent->IsHTMLElement(nsGkAtoms::option)) { return aParent->AsElement()->HasNonEmptyAttr(nsGkAtoms::label); } @@ -5191,11 +5106,7 @@ static bool ShouldSuppressFrameInSelect(const nsIContent* aParent, return false; } - // Allow native anonymous content no matter what. - if (aChild.IsRootOfNativeAnonymousSubtree()) { - return false; - } - + // Anything else is not ok. return true; } @@ -8042,6 +7953,20 @@ nsIFrame* nsCSSFrameConstructor::CreateContinuingFrame( return newFrame; } +void nsCSSFrameConstructor::MaybeSetNextPageContentFramePageName( + const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "Frame should not be null"); + // No parent means the root frame, which isn't what this funciton is for. + MOZ_ASSERT(aFrame->GetParent(), + "Frame should be the first child placed on a new page, not the " + "root frame."); + if (mNextPageContentFramePageName) { + return; + } + const nsAtom* const autoValue = aFrame->GetParent()->GetAutoPageValue(); + mNextPageContentFramePageName = aFrame->ComputePageValue(autoValue); +} + nsresult nsCSSFrameConstructor::ReplicateFixedFrames( nsPageContentFrame* aParentFrame) { // Now deal with fixed-pos things.... They should appear on all pages, @@ -8743,15 +8668,14 @@ void nsCSSFrameConstructor::CreateNeededAnonFlexOrGridItems( mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle( PseudoStyleType::anonymousItem, aParentFrame->Style()); - static constexpr FrameConstructionData sBlockFormattingContextFCData( - ToCreationFunc(NS_NewBlockFormattingContext), - FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS | - FCDATA_IS_WRAPPER_ANON_BOX); + static constexpr FrameConstructionData sBlockFCData( + ToCreationFunc(NS_NewBlockFrame), FCDATA_SKIP_FRAMESET | + FCDATA_USE_CHILD_ITEMS | + FCDATA_IS_WRAPPER_ANON_BOX); - FrameConstructionItem* newItem = new (this) - FrameConstructionItem(&sBlockFormattingContextFCData, - // Use the content of our parent frame - parentContent, wrapperStyle.forget(), true); + // Use the content of our parent frame + auto* newItem = new (this) FrameConstructionItem( + &sBlockFCData, parentContent, wrapperStyle.forget(), true); newItem->mIsAllInline = newItem->mComputedStyle->StyleDisplay()->IsInlineOutsideStyle(); diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 1b48fd15dc..283d1385ce 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -297,12 +297,39 @@ class nsCSSFrameConstructor final : public nsFrameManager { nsContainerFrame* aParentFrame, bool aIsFluid = true); - void SetNextPageContentFramePageName(const nsAtom* aAtom) { + /** + * Sets the page name when a page break is being generated due to a change + * in page name. + * + * Should only be used during paginated reflow, to signal what page value + * the next page content frame should have. + * + * It is an error to set this if a new page name has already been set, either + * through SetNextPageContentFramePageName or + * MaybeSetNextPageContentFramePageName. + */ + void SetNextPageContentFramePageName(const nsAtom* aPageName) { + MOZ_ASSERT(aPageName, "New page name should never be null"); MOZ_ASSERT(!mNextPageContentFramePageName, "PageContentFrame page name was already set"); - mNextPageContentFramePageName = aAtom; + mNextPageContentFramePageName = aPageName; } + /** + * If a new page name has not been set for the next page, sets the value + * using the given frame. + * + * |aFrame| should be a frame to be placed on the new page. + * + * This function handles the work of resolving an atom for the frame, and + * avoids doing this extra work when not necessary. + * + * This is used during block reflow when a page break has occurred but it was + * not caused by a change in page name. It should only be used during + * paginated reflow. + */ + void MaybeSetNextPageContentFramePageName(const nsIFrame* aFrame); + // Copy over fixed frames from aParentFrame's prev-in-flow nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame); @@ -696,10 +723,6 @@ class nsCSSFrameConstructor final : public nsFrameManager { This can be used with or without FCDATA_FUNC_IS_FULL_CTOR. The child items might still need table pseudo processing. */ #define FCDATA_USE_CHILD_ITEMS 0x10000 - /* If FCDATA_FORCED_NON_SCROLLABLE_BLOCK is set, then this block - would have been scrollable but has been forced to be - non-scrollable due to being in a paginated context. */ -#define FCDATA_FORCED_NON_SCROLLABLE_BLOCK 0x20000 /* If FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, then create a block formatting context wrapper around the kids of this frame using the FrameConstructionData's mPseudoAtom for its anonymous @@ -1361,14 +1384,6 @@ class nsCSSFrameConstructor final : public nsFrameManager { nsFrameState aTypeBit); private: - // ConstructSelectFrame puts the new frame in aFrameList and - // handles the kids of the select. - nsIFrame* ConstructSelectFrame(nsFrameConstructorState& aState, - FrameConstructionItem& aItem, - nsContainerFrame* aParentFrame, - const nsStyleDisplay* aStyleDisplay, - nsFrameList& aFrameList); - // ConstructFieldSetFrame puts the new frame in aFrameList and // handles the kids of the fieldset nsIFrame* ConstructFieldSetFrame(nsFrameConstructorState& aState, @@ -1377,6 +1392,12 @@ class nsCSSFrameConstructor final : public nsFrameManager { const nsStyleDisplay* aStyleDisplay, nsFrameList& aFrameList); + nsIFrame* ConstructListBoxSelectFrame(nsFrameConstructorState& aState, + FrameConstructionItem& aItem, + nsContainerFrame* aParentFrame, + const nsStyleDisplay* aStyleDisplay, + nsFrameList& aFrameList); + // Creates a block frame wrapping an anonymous ruby frame. nsIFrame* ConstructBlockRubyFrame(nsFrameConstructorState& aState, FrameConstructionItem& aItem, @@ -1423,6 +1444,8 @@ class nsCSSFrameConstructor final : public nsFrameManager { nsIFrame* aParentFrame, ComputedStyle&); // HTML data-finding helper functions + static const FrameConstructionData* FindSelectData(const Element&, + ComputedStyle&); static const FrameConstructionData* FindImgData(const Element&, ComputedStyle&); static const FrameConstructionData* FindGeneratedImageData(const Element&, diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index ab61daae87..d1cf9bf237 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1143,6 +1143,8 @@ nsDocumentViewer::PermitUnload(PermitUnloadAction aAction, *aPermitUnload = true; + NS_ENSURE_STATE(mContainer); + RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext(); if (!bc) { return NS_OK; @@ -1224,7 +1226,7 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY PermitUnloadResult nsDocumentViewer::DispatchBeforeUnload() { AutoDontWarnAboutSyncXHR disableSyncXHRWarning; - if (!mDocument || mInPermitUnload || mInPermitUnloadPrompt) { + if (!mDocument || mInPermitUnload || mInPermitUnloadPrompt || !mContainer) { return eAllowNavigation; } @@ -2546,6 +2548,8 @@ nsDocumentViewer::ForgetReloadEncoding() { MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::GetContentSize( int32_t aMaxWidth, int32_t aMaxHeight, int32_t aPrefWidth, int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_STATE(mContainer); + RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext(); NS_ENSURE_TRUE(bc, NS_ERROR_NOT_AVAILABLE); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 28230421d5..db766f6603 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -9492,8 +9492,9 @@ nsRect nsLayoutUtils::ComputeSVGOriginBox(SVGViewportElement* aElement) { } /* static */ -nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame, - StyleGeometryBox aGeometryBox) { +nsRect nsLayoutUtils::ComputeSVGReferenceRect( + nsIFrame* aFrame, StyleGeometryBox aGeometryBox, + MayHaveNonScalingStrokeCyclicDependency aMayHaveCyclicDependency) { MOZ_ASSERT(aFrame->GetContent()->IsSVGElement()); nsRect r; @@ -9502,9 +9503,12 @@ nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame, // XXX Bug 1299876 // The size of stroke-box is not correct if this graphic element has // specific stroke-linejoin or stroke-linecap. - gfxRect bbox = - SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry | - SVGUtils::eBBoxIncludeStroke); + const uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | + SVGUtils::eBBoxIncludeStroke | + (bool(aMayHaveCyclicDependency) + ? SVGUtils::eAvoidCycleIfNonScalingStroke + : 0); + gfxRect bbox = SVGUtils::GetBBox(aFrame, flags); r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel()); break; } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 3cd01dce9f..697b139ed9 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -2913,7 +2913,13 @@ class nsLayoutUtils { // Compute the geometry box for SVG layout. The caller should map the CSS box // into the proper SVG box. - static nsRect ComputeSVGReferenceRect(nsIFrame*, StyleGeometryBox); + // |aMayHaveCyclicDependency| is used for stroke-box to avoid the cyclic + // dependency if any of its descendants uses non-scaling-stroke. + enum class MayHaveNonScalingStrokeCyclicDependency : bool { No, Yes }; + static nsRect ComputeSVGReferenceRect( + nsIFrame*, StyleGeometryBox, + MayHaveNonScalingStrokeCyclicDependency = + MayHaveNonScalingStrokeCyclicDependency::No); // Compute the geometry box for CSS layout. The caller should map the SVG box // into the proper CSS box. diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index c1b03aebf4..7d9515c495 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -906,7 +906,6 @@ Maybe<ColorScheme> nsPresContext::GetOverriddenOrEmbedderColorScheme() const { case dom::PrefersColorSchemeOverride::Light: return Some(ColorScheme::Light); case dom::PrefersColorSchemeOverride::None: - case dom::PrefersColorSchemeOverride::EndGuard_: break; } diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index bc4e70dbcf..563dac0eae 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -1397,7 +1397,8 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr { #ifdef DEBUG private: friend struct nsAutoLayoutPhase; - mozilla::EnumeratedArray<nsLayoutPhase, nsLayoutPhase::COUNT, uint32_t> + mozilla::EnumeratedArray<nsLayoutPhase, uint32_t, + size_t(nsLayoutPhase::COUNT)> mLayoutPhaseCount; public: diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index af780bb192..a5c2b1ded9 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -2264,7 +2264,8 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() { return false; } return ps->HasContentVisibilityAutoFrames() || - aDocument->HasResizeObservers(); + aDocument->HasResizeObservers() || + aDocument->HasElementsWithLastRememberedSize(); }; AutoTArray<RefPtr<Document>, 32> documents; diff --git a/layout/base/tests/mochitest.toml b/layout/base/tests/mochitest.toml index d4b7932cfc..24924809c0 100644 --- a/layout/base/tests/mochitest.toml +++ b/layout/base/tests/mochitest.toml @@ -234,6 +234,8 @@ support-files = ["bug1448730.html"] ["test_bug1756118.html"] +["test_bug1836801.html"] + ["test_caret_browsing_around_form_controls.html"] skip-if = ["os == 'android'"] diff --git a/layout/base/tests/test_bug1836801.html b/layout/base/tests/test_bug1836801.html new file mode 100644 index 0000000000..4584394600 --- /dev/null +++ b/layout/base/tests/test_bug1836801.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> + +<script> +// this is a crashtest, just getting to the end is a pass, fullscreen +// doesn't work in crashtests though not sure why --> +SimpleTest.waitForExplicitFinish(); + +SimpleTest.requestFlakyTimeout("crashtest sensitive to timing"); + +function begin() { + SimpleTest.waitForFocus(begin2); +} + +function begin2() { + SpecialPowers.pushPrefEnv({ + "set":[["full-screen-api.allow-trusted-requests-only", false]] + }, startTest); +} + +window.addEventListener("load", begin); + +const func_0 = async function(arg0) { + //SpecialPowers.wrap(document).notifyUserGestureActivation(); + await arg0.originalTarget.requestFullscreen() + arg0.originalTarget.ariaValueText = "a" +} + +async function startTest() { + let a = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas") + document.documentElement.appendChild(a) + document.addEventListener("DOMAttrModified", func_0, { }) + let b = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas") + document.documentElement.appendChild(b) + b.setAttribute("class", "x") + //SpecialPowers.wrap(document).notifyUserGestureActivation(); + await b.mozRequestFullScreen() + let c = document.createElementNS("http://www.w3.org/1999/xhtml", "slot") + document.documentElement.appendChild(c) + c.setAttribute("class", "x") + b.inert = true + setTimeout(async () => { + b.setAttribute("title", "a") + }, 200) + a.ariaSort = "descending" + setTimeout(finishup, 400); +} +async function finishup() { + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + await document.exitFullscreen(); + SimpleTest.finish(); +} +</script> +</html> diff --git a/layout/base/tests/test_event_target_iframe_oop.html b/layout/base/tests/test_event_target_iframe_oop.html deleted file mode 100644 index 562433c955..0000000000 --- a/layout/base/tests/test_event_target_iframe_oop.html +++ /dev/null @@ -1,177 +0,0 @@ -<!DOCTYPE HTML> -<html id="html" style="height:100%"> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=921928 ---> -<head> - <title>Test for bug 921928</title> - <script src="/tests/SimpleTest/SimpleTest.js"></script> - <script src="/tests/SimpleTest/EventUtils.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> - <style> - #dialer { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 50px; - background: green; - } - - #apps { - position: absolute; - left: 0; - top: 51px; - width: 100%; - height: 100px; - background: blue; - } - - .hit { - position: absolute; - width: 3px; - height: 3px; - z-index: 20; - background: red; - border: 1px solid red; - } - </style> -</head> -<body id="body" style="margin:0; width:100%; height:100%"> -<script type="application/javascript"> -SimpleTest.waitForExplicitFinish(); - -var prefs = [ - ["ui.mouse.radius.enabled", true], - ["ui.mouse.radius.inputSource.touchOnly", false], - ["ui.mouse.radius.leftmm", 12], - ["ui.mouse.radius.topmm", 8], - ["ui.mouse.radius.rightmm", 4], - ["ui.mouse.radius.bottommm", 4], - ["ui.mouse.radius.visitedweight", 50], -]; - -var eventTarget; -var debugHit = []; - -function endTest() { - SimpleTest.finish(); - SpecialPowers.removePermission("browser", location.href); - for (var pref in prefs) { - SpecialPowers.pushPrefEnv({"clear": pref[0]}, function() {}); - } -} - -function testMouseClick(idPosition, dx, dy, idTarget, msg, options) { - eventTarget = null; - synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {}); - try { - is(eventTarget.id, idTarget, - "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]"); - } catch (ex) { - ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget); - } -} - -function showDebug() { - for (var i = 0; i < debugHit.length; i++) { - document.body.appendChild(debugHit[i]); - } - - var screenshot = SpecialPowers.snapshotWindow(window, true); - dump('IMAGE:' + screenshot.toDataURL() + '\n'); -} - -/* - Setup the test environment: enabling event fluffing (all ui.* preferences), - and enabling remote process. -*/ -function setupTest(cont) { - SpecialPowers.addPermission("browser", true, document); - SpecialPowers.pushPrefEnv({"set": prefs}, cont); -} - -function execTest() { - /* - Creating two iframes that mimics the attention screen behavior on the - device: - - the 'dialer' iframe is the attention screen you have when a call is - in place. it is a green bar, so we copy it as green here too - - the 'apps' iframe mimics another application that is being run, be it - dialer, sms, ..., anything that the user might want to trigger during - a call - - The bug we intent to reproduce here is that in this case, if the user taps - onto the top of the 'apps', the event fluffing code will in fact redirect - the event to the 'dialer' iframe. In practice, this is bug 921928 where - during a call the user wants to place a second call, and while typing the - phone number, wants to tap onto the 'delete' key to erase a digit, but ends - tapping and activating the dialer. - */ - var dialer = document.createElement('iframe'); - dialer.id = 'dialer'; - dialer.src = ''; - // Force OOP - dialer.setAttribute('mozbrowser', 'true'); - dialer.setAttribute('remote', 'true'); - document.body.appendChild(dialer); - - var apps = document.createElement('iframe'); - apps.id = 'apps'; - apps.src = 'bug921928_event_target_iframe_apps_oop.html'; - // Force OOP - apps.setAttribute('mozbrowser', 'true'); - apps.setAttribute('remote', 'true'); - document.body.appendChild(apps); - - var handleEvent = function(event) { - eventTarget = event.target; - - // We draw a small red div to show where the event has tapped - var hit = document.createElement('div'); - hit.style.left = (event.clientX - 1.5) + 'px'; - hit.style.top = (event.clientY - 1.5) + 'px'; - hit.classList.add('hit'); - debugHit.push(hit); - }; - - // In real life, the 'dialer' has a 'mousedown', so we mimic one too, - // to reproduce the same behavior - dialer.addEventListener('mousedown', function(e) {}); - - // This event listener is just here to record what iframe has been hit, - // and sets the 'eventTarget' to the iframe's id value so that the - // testMouseClick() code can correctly check. We cannot add it on the - // 'apps' otherwise it will alter the behavior of the test. - document.addEventListener('mousedown', handleEvent); - - // In the following, the coordinates are relative to the iframe - - // First, we check that tapping onto the 'dialer' correctly triggers the - // dialer. - testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with mouse input"); - testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with touch input", { - inputSource: MouseEvent.MOZ_SOURCE_TOUCH - }); - - // Now this is it: we tap inside 'apps', but very close to the border between - // 'apps' and 'dialer'. Without the fix from this bug, this test will fail. - testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for mouse input"); - testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for touch input", { - inputSource: MouseEvent.MOZ_SOURCE_TOUCH - }); - - // Show small red spots of where the click happened - // showDebug(); - - endTest(); -} - -function runTest() { - setupTest(execTest); -} - -addEventListener('load', function() { SimpleTest.executeSoon(runTest); }); -</script> -</body> -</html> |