Bug 1534914 - Improve blocked cursor behavior to fall back to next image. r=smaug
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 13 Mar 2019 18:38:09 +0000
changeset 521758 a4d1dd58552d02854ce438e11dc4c97ec885f166
parent 521757 9f0635581b5c3450ad7521c9d1df0538da6e2469
child 521759 360f4359ab669cd5d4fcc7b641c0a6dd8fb65341
push id10867
push userdvarga@mozilla.com
push dateThu, 14 Mar 2019 15:20:45 +0000
treeherdermozilla-beta@abad13547875 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1534914
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1534914 - Improve blocked cursor behavior to fall back to next image. r=smaug Right now we just block the image returned from nsIFrame::GetCursor, which is the first loading cursor image. I think we should do the same we do when the image fails to load, which is to fall back to the next image instead. This patch moves all the custom cursor code selection logic to EventStateManager, and lets the frame return a CursorKind and whether custom images are allowed. Differential Revision: https://phabricator.services.mozilla.com/D23289
dom/events/EventStateManager.cpp
layout/forms/nsImageControlFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsFrame.h
layout/generic/nsFrameSetFrame.cpp
layout/generic/nsFrameSetFrame.h
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/generic/nsPluginFrame.cpp
layout/generic/nsPluginFrame.h
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/xul/nsSplitterFrame.cpp
layout/xul/nsSplitterFrame.h
layout/xul/tree/nsTreeBodyFrame.cpp
layout/xul/tree/nsTreeBodyFrame.h
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -3631,81 +3631,41 @@ void EventStateManager::SetPresContext(n
 }
 
 void EventStateManager::ClearFrameRefs(nsIFrame* aFrame) {
   if (aFrame && aFrame == mCurrentTarget) {
     mCurrentTargetContent = aFrame->GetContent();
   }
 }
 
-static Maybe<gfx::IntPoint> ComputeHotspot(const nsIFrame::Cursor& aCursor) {
-  if (!aCursor.mContainer) {
-    return {};
-  }
-
-  // css3-ui says to use the CSS-specified hotspot if present,
-  // otherwise use the intrinsic hotspot, otherwise use the top left
-  // corner.
-  uint32_t hotspotX, hotspotY;
-  if (aCursor.mHaveHotspot) {
-    int32_t imgWidth, imgHeight;
-    aCursor.mContainer->GetWidth(&imgWidth);
-    aCursor.mContainer->GetHeight(&imgHeight);
-
-    // XXX std::max(NS_lround(x), 0)?
-    hotspotX = aCursor.mHotspotX > 0.0f ? uint32_t(aCursor.mHotspotX + 0.5f)
-                                        : uint32_t(0);
-    if (hotspotX >= uint32_t(imgWidth)) hotspotX = imgWidth - 1;
-    hotspotY = aCursor.mHotspotY > 0.0f ? uint32_t(aCursor.mHotspotY + 0.5f)
-                                        : uint32_t(0);
-    if (hotspotY >= uint32_t(imgHeight)) hotspotY = imgHeight - 1;
-  } else {
-    hotspotX = 0;
-    hotspotY = 0;
-    nsCOMPtr<nsIProperties> props(do_QueryInterface(aCursor.mContainer));
-    if (props) {
-      nsCOMPtr<nsISupportsPRUint32> hotspotXWrap, hotspotYWrap;
-
-      props->Get("hotspotX", NS_GET_IID(nsISupportsPRUint32),
-                 getter_AddRefs(hotspotXWrap));
-      props->Get("hotspotY", NS_GET_IID(nsISupportsPRUint32),
-                 getter_AddRefs(hotspotYWrap));
-
-      if (hotspotXWrap) hotspotXWrap->GetData(&hotspotX);
-      if (hotspotYWrap) hotspotYWrap->GetData(&hotspotY);
-    }
-  }
-
-  return Some(gfx::IntPoint{hotspotX, hotspotY});
-}
+struct CursorImage {
+  gfx::IntPoint mHotspot;
+  nsCOMPtr<imgIContainer> mContainer;
+  bool mEarlierCursorLoading = false;
+};
 
 // Given the event that we're processing, and the computed cursor and hotspot,
 // determine whether the custom CSS cursor should be blocked (that is, not
 // honored).
 //
 // We will not honor it all of the following are true:
 //
 //  * layout.cursor.block.enabled is true.
 //  * the size of the custom cursor is bigger than layout.cursor.block.max-size.
 //  * the bounds of the cursor would end up outside of the viewport of the
 //    top-level content document.
 //
 // This is done in order to prevent hijacking the cursor, see bug 1445844 and
 // co.
 static bool ShouldBlockCustomCursor(nsPresContext* aPresContext,
                                     WidgetEvent* aEvent,
-                                    const nsIFrame::Cursor& aCursor,
-                                    const Maybe<gfx::IntPoint>& aHotspot) {
+                                    const CursorImage& aCursor) {
   if (!StaticPrefs::layout_cursor_block_enabled()) {
     return false;
   }
-  if (!aCursor.mContainer) {
-    return false;
-  }
-  MOZ_ASSERT(aHotspot);
 
   int32_t width = 0;
   int32_t height = 0;
   aCursor.mContainer->GetWidth(&width);
   aCursor.mContainer->GetHeight(&height);
 
   int32_t maxSize = StaticPrefs::layout_cursor_block_max_size();
 
@@ -3723,72 +3683,154 @@ static bool ShouldBlockCustomCursor(nsPr
   if (!topLevel) {
     return false;
   }
 
   nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
       aEvent, topLevel->PresShell()->GetRootFrame());
 
   nsSize size(CSSPixel::ToAppUnits(width), CSSPixel::ToAppUnits(height));
-  nsPoint hotspot(CSSPixel::ToAppUnits(aHotspot->x),
-                  CSSPixel::ToAppUnits(aHotspot->y));
+  nsPoint hotspot(CSSPixel::ToAppUnits(aCursor.mHotspot.x),
+                  CSSPixel::ToAppUnits(aCursor.mHotspot.y));
 
   nsRect cursorRect(point - hotspot, size);
   return !topLevel->GetVisibleArea().Contains(cursorRect);
 }
 
+static gfx::IntPoint ComputeHotspot(imgIContainer* aContainer,
+                                    const Maybe<gfx::Point>& aHotspot) {
+  MOZ_ASSERT(aContainer);
+  // css3-ui says to use the CSS-specified hotspot if present,
+  // otherwise use the intrinsic hotspot, otherwise use the top left
+  // corner.
+  uint32_t hotspotX, hotspotY;
+  if (aHotspot) {
+    int32_t imgWidth, imgHeight;
+    aContainer->GetWidth(&imgWidth);
+    aContainer->GetHeight(&imgHeight);
+
+    // XXX std::max(NS_lround(x), 0)?
+    hotspotX = aHotspot->x > 0.0f ? uint32_t(aHotspot->x + 0.5f) : uint32_t(0);
+    if (hotspotX >= uint32_t(imgWidth)) hotspotX = imgWidth - 1;
+    hotspotY = aHotspot->y > 0.0f ? uint32_t(aHotspot->y + 0.5f) : uint32_t(0);
+    if (hotspotY >= uint32_t(imgHeight)) hotspotY = imgHeight - 1;
+  } else {
+    hotspotX = 0;
+    hotspotY = 0;
+    nsCOMPtr<nsIProperties> props(do_QueryInterface(aContainer));
+    if (props) {
+      nsCOMPtr<nsISupportsPRUint32> hotspotXWrap, hotspotYWrap;
+
+      props->Get("hotspotX", NS_GET_IID(nsISupportsPRUint32),
+                 getter_AddRefs(hotspotXWrap));
+      props->Get("hotspotY", NS_GET_IID(nsISupportsPRUint32),
+                 getter_AddRefs(hotspotYWrap));
+
+      if (hotspotXWrap) hotspotXWrap->GetData(&hotspotX);
+      if (hotspotYWrap) hotspotYWrap->GetData(&hotspotY);
+    }
+  }
+  return {hotspotX, hotspotY};
+}
+
+static CursorImage ComputeCustomCursor(nsPresContext* aPresContext,
+                                       WidgetEvent* aEvent,
+                                       const nsIFrame& aFrame,
+                                       const nsIFrame::Cursor& aCursor) {
+  if (aCursor.mAllowCustomCursor == nsIFrame::AllowCustomCursorImage::No) {
+    return {};
+  }
+  const ComputedStyle& style =
+      aCursor.mStyle ? *aCursor.mStyle : *aFrame.Style();
+
+  // If we are falling back because any cursor before us is loading, let the
+  // consumer know.
+  bool loading = false;
+  for (const nsCursorImage& image : style.StyleUI()->mCursorImages) {
+    uint32_t status;
+    imgRequestProxy* req = image.GetImage();
+    if (!req || NS_FAILED(req->GetImageStatus(&status))) {
+      continue;
+    }
+    if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+      loading = true;
+      continue;
+    }
+    if (status & imgIRequest::STATUS_ERROR) {
+      continue;
+    }
+    nsCOMPtr<imgIContainer> container;
+    req->GetImage(getter_AddRefs(container));
+    if (!container) {
+      continue;
+    }
+    Maybe<gfx::Point> specifiedHotspot =
+        image.mHaveHotspot ? Some(gfx::Point{image.mHotspotX, image.mHotspotY})
+                           : Nothing();
+    gfx::IntPoint hotspot = ComputeHotspot(container, specifiedHotspot);
+    CursorImage result{hotspot, std::move(container), loading};
+    if (ShouldBlockCustomCursor(aPresContext, aEvent, result)) {
+      continue;
+    }
+    // This is the one we want!
+    return result;
+  }
+  return {{}, nullptr, loading};
+}
+
 void EventStateManager::UpdateCursor(nsPresContext* aPresContext,
                                      WidgetEvent* aEvent,
                                      nsIFrame* aTargetFrame,
                                      nsEventStatus* aStatus) {
   if (aTargetFrame && IsRemoteTarget(aTargetFrame->GetContent())) {
     return;
   }
 
   auto cursor = StyleCursorKind::Default;
-  imgIContainer* container = nullptr;
+  nsCOMPtr<imgIContainer> container;
   Maybe<gfx::IntPoint> hotspot;
 
   // If cursor is locked just use the locked one
   if (mLockCursor != kInvalidCursorKind) {
     cursor = mLockCursor;
   }
   // If not locked, look for correct cursor
   else if (aTargetFrame) {
-    nsIFrame::Cursor framecursor;
     nsPoint pt =
         nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, aTargetFrame);
+    Maybe<nsIFrame::Cursor> framecursor = aTargetFrame->GetCursor(pt);
     // Avoid setting cursor when the mouse is over a windowless plugin.
-    if (NS_FAILED(aTargetFrame->GetCursor(pt, framecursor))) {
+    if (!framecursor) {
       if (XRE_IsContentProcess()) {
         mLastFrameConsumedSetCursor = true;
       }
       return;
     }
     // Make sure cursors get reset after the mouse leaves a
     // windowless plugin frame.
     if (mLastFrameConsumedSetCursor) {
       ClearCachedWidgetCursor(aTargetFrame);
       mLastFrameConsumedSetCursor = false;
     }
+
+    CursorImage customCursor =
+        ComputeCustomCursor(aPresContext, aEvent, *aTargetFrame, *framecursor);
+
     // If the current cursor is from the same frame, and it is now
     // loading some new image for the cursor, we should wait for a
     // while rather than taking its fallback cursor directly.
-    if (framecursor.mLoading && gLastCursorSourceFrame == aTargetFrame &&
+    if (customCursor.mEarlierCursorLoading &&
+        gLastCursorSourceFrame == aTargetFrame &&
         TimeStamp::NowLoRes() - gLastCursorUpdateTime <
             TimeDuration::FromMilliseconds(kCursorLoadingTimeout)) {
       return;
     }
-    cursor = framecursor.mCursor;
-    container = framecursor.mContainer;
-    hotspot = ComputeHotspot(framecursor);
-
-    if (ShouldBlockCustomCursor(aPresContext, aEvent, framecursor, hotspot)) {
-      container = nullptr;
-    }
+    cursor = framecursor->mCursor;
+    container = customCursor.mContainer.forget();
+    hotspot = Some(customCursor.mHotspot);
   }
 
   if (nsContentUtils::UseActivityCursor()) {
     // Check whether or not to show the busy cursor
     nsCOMPtr<nsIDocShell> docShell(aPresContext->GetDocShell());
     if (!docShell) return;
     auto busyFlags = docShell->GetBusyFlags();
 
--- a/layout/forms/nsImageControlFrame.cpp
+++ b/layout/forms/nsImageControlFrame.cpp
@@ -44,18 +44,18 @@ class nsImageControlFrame final : public
 #endif
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override {
     return MakeFrameName(NS_LITERAL_STRING("ImageControl"), aResult);
   }
 #endif
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  Maybe<Cursor> GetCursor(const nsPoint&) override;
+
   // nsIFormContromFrame
   virtual void SetFocus(bool aOn, bool aRepaint) override;
   virtual nsresult SetFormProperty(nsAtom* aName,
                                    const nsAString& aValue) override;
 };
 
 nsImageControlFrame::nsImageControlFrame(ComputedStyle* aStyle,
                                          nsPresContext* aPresContext)
@@ -147,25 +147,20 @@ nsresult nsImageControlFrame::HandleEven
       TranslateEventCoords(pt, *lastClickPoint);
     }
   }
   return nsImageFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
 }
 
 void nsImageControlFrame::SetFocus(bool aOn, bool aRepaint) {}
 
-nsresult nsImageControlFrame::GetCursor(const nsPoint& aPoint,
-                                        nsIFrame::Cursor& aCursor) {
-  // Use style defined cursor if one is provided, otherwise when
-  // the cursor style is "auto" we use the pointer cursor.
-  FillCursorInformationFromStyle(StyleUI(), aCursor);
-
-  if (StyleCursorKind::Auto == aCursor.mCursor) {
-    aCursor.mCursor = StyleCursorKind::Pointer;
+Maybe<nsIFrame::Cursor> nsImageControlFrame::GetCursor(const nsPoint&) {
+  StyleCursorKind kind = StyleUI()->mCursor;
+  if (kind == StyleCursorKind::Auto) {
+    kind = StyleCursorKind::Pointer;
   }
-
-  return NS_OK;
+  return Some(Cursor{kind, AllowCustomCursorImage::Yes});
 }
 
 nsresult nsImageControlFrame::SetFormProperty(nsAtom* aName,
                                               const nsAString& aValue) {
   return NS_OK;
 }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5130,32 +5130,30 @@ void nsIFrame::AssociateImage(const nsSt
   }
   mozilla::css::ImageLoader* loader =
       aPresContext->Document()->StyleImageLoader();
 
   // If this fails there's not much we can do ...
   loader->AssociateRequestToFrame(req, this, aImageLoaderFlags);
 }
 
-nsresult nsFrame::GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) {
-  FillCursorInformationFromStyle(StyleUI(), aCursor);
-  if (StyleCursorKind::Auto == aCursor.mCursor) {
+Maybe<nsIFrame::Cursor> nsIFrame::GetCursor(const nsPoint&) {
+  StyleCursorKind kind = StyleUI()->mCursor;
+  if (kind == StyleCursorKind::Auto) {
     // If this is editable, I-beam cursor is better for most elements.
-    aCursor.mCursor = (mContent && mContent->IsEditable())
-                          ? StyleCursorKind::Text
-                          : StyleCursorKind::Default;
-  }
-  if (StyleCursorKind::Text == aCursor.mCursor &&
-      GetWritingMode().IsVertical()) {
+    kind = (mContent && mContent->IsEditable()) ? StyleCursorKind::Text
+                                                : StyleCursorKind::Default;
+  }
+  if (kind == StyleCursorKind::Text && GetWritingMode().IsVertical()) {
     // Per CSS UI spec, UA may treat value 'text' as
     // 'vertical-text' for vertical text.
-    aCursor.mCursor = StyleCursorKind::VerticalText;
-  }
-
-  return NS_OK;
+    kind = StyleCursorKind::VerticalText;
+  }
+
+  return Some(Cursor{kind, AllowCustomCursorImage::Yes});
 }
 
 // Resize and incremental reflow
 
 /* virtual */
 void nsFrame::MarkIntrinsicISizesDirty() {
   // This version is meant only for what used to be box-to-block adaptors.
   // It should not be called by other derived classes.
@@ -9745,45 +9743,16 @@ uint8_t nsIFrame::VerticalAlignEnum() co
   const nsStyleCoord& verticalAlign = StyleDisplay()->mVerticalAlign;
   if (verticalAlign.GetUnit() == eStyleUnit_Enumerated) {
     return verticalAlign.GetIntValue();
   }
 
   return eInvalidVerticalAlign;
 }
 
-/* static */
-void nsFrame::FillCursorInformationFromStyle(const nsStyleUI* ui,
-                                             nsIFrame::Cursor& aCursor) {
-  aCursor.mCursor = ui->mCursor;
-  aCursor.mHaveHotspot = false;
-  aCursor.mLoading = false;
-  aCursor.mHotspotX = aCursor.mHotspotY = 0.0f;
-
-  for (const nsCursorImage& item : ui->mCursorImages) {
-    uint32_t status;
-    imgRequestProxy* req = item.GetImage();
-    if (!req || NS_FAILED(req->GetImageStatus(&status))) {
-      continue;
-    }
-    if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
-      // If we are falling back because any cursor before is loading,
-      // let the consumer know.
-      aCursor.mLoading = true;
-    } else if (!(status & imgIRequest::STATUS_ERROR)) {
-      // This is the one we want
-      req->GetImage(getter_AddRefs(aCursor.mContainer));
-      aCursor.mHaveHotspot = item.mHaveHotspot;
-      aCursor.mHotspotX = item.mHotspotX;
-      aCursor.mHotspotY = item.mHotspotY;
-      break;
-    }
-  }
-}
-
 NS_IMETHODIMP
 nsFrame::RefreshSizeCache(nsBoxLayoutState& aState) {
   // XXXbz this comment needs some rewriting to make sense in the
   // post-reflow-branch world.
 
   // Ok we need to compute our minimum, preferred, and maximum sizes.
   // 1) Maximum size. This is easy. Its infinite unless it is overloaded by CSS.
   // 2) Preferred size. This is a little harder. This is the size the
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -172,17 +172,16 @@ class nsFrame : public nsBox {
   const nsFrameList& GetChildList(ChildListID aListID) const override;
   void GetChildLists(nsTArray<ChildList>* aLists) const override;
 
   nsresult HandleEvent(nsPresContext* aPresContext,
                        mozilla::WidgetGUIEvent* aEvent,
                        nsEventStatus* aEventStatus) override;
   nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
                               nsIContent** aContent) override;
-  nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) override;
 
   nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) override;
   nsresult GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength,
                                     nsTArray<nsRect>& aOutRect) override;
 
   nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint,
                                          int32_t* outFrameContentOffset,
                                          nsIFrame** outChildFrame) override;
@@ -650,19 +649,16 @@ class nsFrame : public nsBox {
   //  table supported) (enums for this are defined in nsIFrame.h)
   nsresult GetDataForTableSelection(const nsFrameSelection* aFrameSelection,
                                     nsIPresShell* aPresShell,
                                     mozilla::WidgetMouseEvent* aMouseEvent,
                                     nsIContent** aParentContent,
                                     int32_t* aContentOffset,
                                     mozilla::TableSelection* aTarget);
 
-  // Fills aCursor with the appropriate information from ui
-  static void FillCursorInformationFromStyle(const nsStyleUI* ui,
-                                             nsIFrame::Cursor& aCursor);
   NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
 
   nsBoxLayoutMetrics* BoxMetrics() const;
 
   // Fire DOM event. If no aContent argument use frame's mContent.
   void FireDOMEvent(const nsAString& aDOMEventName,
                     nsIContent* aContent = nullptr);
 
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -82,18 +82,17 @@ class nsHTMLFramesetBorderFrame final : 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
   virtual nsresult HandleEvent(nsPresContext* aPresContext,
                                WidgetGUIEvent* aEvent,
                                nsEventStatus* aEventStatus) override;
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  Maybe<Cursor> GetCursor(const nsPoint&) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override;
 
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus) override;
 
@@ -625,26 +624,23 @@ nsresult nsHTMLFramesetFrame::HandleEven
     }
     *aEventStatus = nsEventStatus_eConsumeNoDefault;
   } else {
     *aEventStatus = nsEventStatus_eIgnore;
   }
   return NS_OK;
 }
 
-nsresult nsHTMLFramesetFrame::GetCursor(const nsPoint& aPoint,
-                                        nsIFrame::Cursor& aCursor) {
-  aCursor.mLoading = false;
+Maybe<nsIFrame::Cursor> nsHTMLFramesetFrame::GetCursor(const nsPoint&) {
+  auto kind = StyleCursorKind::Default;
   if (mDragger) {
-    aCursor.mCursor = (mDragger->mVertical) ? StyleCursorKind::EwResize
-                                            : StyleCursorKind::NsResize;
-  } else {
-    aCursor.mCursor = StyleCursorKind::Default;
+    kind = mDragger->mVertical ? StyleCursorKind::EwResize
+                               : StyleCursorKind::NsResize;
   }
-  return NS_OK;
+  return Some(Cursor{kind, AllowCustomCursorImage::No});
 }
 
 void nsHTMLFramesetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                            const nsDisplayListSet& aLists) {
   BuildDisplayListForInline(aBuilder, aLists);
 
   if (mDragger && aBuilder->IsForEventDelivery()) {
     aLists.Content()->AppendToTop(
@@ -1461,26 +1457,22 @@ nsresult nsHTMLFramesetBorderFrame::Hand
     if (parentFrame) {
       parentFrame->StartMouseDrag(aPresContext, this, aEvent);
       *aEventStatus = nsEventStatus_eConsumeNoDefault;
     }
   }
   return NS_OK;
 }
 
-nsresult nsHTMLFramesetBorderFrame::GetCursor(const nsPoint& aPoint,
-                                              nsIFrame::Cursor& aCursor) {
-  aCursor.mLoading = false;
-  if (!mCanResize) {
-    aCursor.mCursor = StyleCursorKind::Default;
-  } else {
-    aCursor.mCursor =
-        (mVertical) ? StyleCursorKind::EwResize : StyleCursorKind::NsResize;
+Maybe<nsIFrame::Cursor> nsHTMLFramesetBorderFrame::GetCursor(const nsPoint&) {
+  auto kind = StyleCursorKind::Default;
+  if (mCanResize) {
+    kind = mVertical ? StyleCursorKind::EwResize : StyleCursorKind::NsResize;
   }
-  return NS_OK;
+  return Some(Cursor{kind, AllowCustomCursorImage::No});
 }
 
 #ifdef DEBUG_FRAME_DUMP
 nsresult nsHTMLFramesetBorderFrame::GetFrameName(nsAString& aResult) const {
   return MakeFrameName(NS_LITERAL_STRING("FramesetBorder"), aResult);
 }
 #endif
 
--- a/layout/generic/nsFrameSetFrame.h
+++ b/layout/generic/nsFrameSetFrame.h
@@ -83,18 +83,17 @@ class nsHTMLFramesetFrame final : public
 
   void GetSizeOfChildAt(int32_t aIndexInParent, mozilla::WritingMode aWM,
                         mozilla::LogicalSize& aSize, nsIntPoint& aCellIndex);
 
   virtual nsresult HandleEvent(nsPresContext* aPresContext,
                                mozilla::WidgetGUIEvent* aEvent,
                                nsEventStatus* aEventStatus) override;
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  mozilla::Maybe<Cursor> GetCursor(const nsPoint&) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override;
 
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus) override;
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1987,32 +1987,38 @@ class nsIFrame : public nsQueryFrame {
 
   /**
    * Ensure that aImage gets notifed when the underlying image request loads
    * or animates.
    */
   void AssociateImage(const nsStyleImage& aImage, nsPresContext* aPresContext,
                       uint32_t aImageLoaderFlags);
 
-  /**
-   * This structure holds information about a cursor. mContainer represents a
-   * loaded image that should be preferred. If it is not possible to use it, or
-   * if it is null, mCursor should be used.
+  enum class AllowCustomCursorImage {
+    No,
+    Yes,
+  };
+
+  /**
+   * This structure holds information about a cursor. AllowCustomCursorImage
+   * is `No`, then no cursor image should be loaded from the style specified on
+   * `mStyle`, or the frame's style.
+   *
+   * The `mStyle` member is used for `<area>` elements.
    */
   struct MOZ_STACK_CLASS Cursor {
-    nsCOMPtr<imgIContainer> mContainer;
     mozilla::StyleCursorKind mCursor = mozilla::StyleCursorKind::Auto;
-    bool mHaveHotspot = false;
-    bool mLoading = false;
-    float mHotspotX = 0.0f, mHotspotY = 0.0f;
+    AllowCustomCursorImage mAllowCustomCursor = AllowCustomCursorImage::Yes;
+    RefPtr<mozilla::ComputedStyle> mStyle;
   };
+
   /**
    * Get the cursor for a given frame.
    */
-  virtual nsresult GetCursor(const nsPoint& aPoint, Cursor& aCursor) = 0;
+  virtual mozilla::Maybe<Cursor> GetCursor(const nsPoint&);
 
   /**
    * Get a point (in the frame's coordinate space) given an offset into
    * the content. This point should be on the baseline of text with
    * the correct horizontal offset
    */
   virtual nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) = 0;
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -2298,39 +2298,36 @@ nsresult nsImageFrame::HandleEvent(nsPre
       }
     }
   }
 
   return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
                                              aEventStatus);
 }
 
-nsresult nsImageFrame::GetCursor(const nsPoint& aPoint,
-                                 nsIFrame::Cursor& aCursor) {
-  if (nsImageMap* map = GetImageMap()) {
-    nsIntPoint p;
-    TranslateEventCoords(aPoint, p);
-    nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
-    if (area) {
-      // Use the cursor from the style of the *area* element.
-      // XXX Using the image as the parent ComputedStyle isn't
-      // technically correct, but it's probably the right thing to do
-      // here, since it means that areas on which the cursor isn't
-      // specified will inherit the style from the image.
-      RefPtr<ComputedStyle> areaStyle =
-          PresShell()->StyleSet()->ResolveStyleFor(area->AsElement(),
-                                                   LazyComputeBehavior::Allow);
-      FillCursorInformationFromStyle(areaStyle->StyleUI(), aCursor);
-      if (StyleCursorKind::Auto == aCursor.mCursor) {
-        aCursor.mCursor = StyleCursorKind::Default;
-      }
-      return NS_OK;
-    }
+Maybe<nsIFrame::Cursor> nsImageFrame::GetCursor(const nsPoint& aPoint) {
+  nsImageMap* map = GetImageMap();
+  if (!map) {
+    return nsFrame::GetCursor(aPoint);
   }
-  return nsFrame::GetCursor(aPoint, aCursor);
+  nsIntPoint p;
+  TranslateEventCoords(aPoint, p);
+  nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
+  if (!area) {
+    return nsFrame::GetCursor(aPoint);
+  }
+
+  // Use the cursor from the style of the *area* element.
+  RefPtr<ComputedStyle> areaStyle = PresShell()->StyleSet()->ResolveStyleFor(
+      area->AsElement(), LazyComputeBehavior::Allow);
+  StyleCursorKind kind = areaStyle->StyleUI()->mCursor;
+  if (kind == StyleCursorKind::Auto) {
+    kind = StyleCursorKind::Default;
+  }
+  return Some(Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)});
 }
 
 nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
                                         nsAtom* aAttribute, int32_t aModType) {
   nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
                                                          aAttribute, aModType);
   if (NS_FAILED(rv)) {
     return rv;
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -89,18 +89,17 @@ class nsImageFrame : public nsAtomicCont
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus) override;
 
   virtual nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
                                       nsIContent** aContent) override;
   virtual nsresult HandleEvent(nsPresContext* aPresContext,
                                mozilla::WidgetGUIEvent* aEvent,
                                nsEventStatus* aEventStatus) override;
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  mozilla::Maybe<Cursor> GetCursor(const nsPoint&) override;
   virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
                                     int32_t aModType) override;
 
   void OnVisibilityChange(
       Visibility aNewVisibility,
       const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override;
 
   void ResponsiveContentDensityChanged();
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -1583,34 +1583,33 @@ bool nsPluginFrame::WantsToHandleWheelEv
 nsNPAPIPluginInstance* nsPluginFrame::GetPluginInstance() {
   if (!mInstanceOwner) {
     return nullptr;
   }
 
   return mInstanceOwner->GetInstance();
 }
 
-nsresult nsPluginFrame::GetCursor(const nsPoint& aPoint,
-                                  nsIFrame::Cursor& aCursor) {
+Maybe<nsIFrame::Cursor> nsPluginFrame::GetCursor(const nsPoint& aPoint) {
   if (!mInstanceOwner) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
   RefPtr<nsNPAPIPluginInstance> inst = mInstanceOwner->GetInstance();
   if (!inst) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
   bool useDOMCursor =
       static_cast<nsNPAPIPluginInstance*>(inst.get())->UsesDOMForCursor();
   if (!useDOMCursor) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
-  return nsFrame::GetCursor(aPoint, aCursor);
+  return nsFrame::GetCursor(aPoint);
 }
 
 void nsPluginFrame::SetIsDocumentActive(bool aIsActive) {
   if (mInstanceOwner) {
     mInstanceOwner->UpdateDocumentActiveState(aIsActive);
   }
 }
 
--- a/layout/generic/nsPluginFrame.h
+++ b/layout/generic/nsPluginFrame.h
@@ -101,18 +101,17 @@ class nsPluginFrame final : public nsFra
                            PostDestroyData& aPostDestroyData) override;
 
   virtual void DidSetComputedStyle(ComputedStyle* aOldComputedStyle) override;
 
   nsNPAPIPluginInstance* GetPluginInstance() override;
 
   virtual void SetIsDocumentActive(bool aIsActive) override;
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  mozilla::Maybe<Cursor> GetCursor(const nsPoint&) override;
 
   // APIs used by nsRootPresContext to set up the widget position/size/clip
   // region.
   /**
    * Set the next widget configuration for the plugin to the desired
    * position of the plugin's widget, on the assumption that it is not visible
    * (clipped out or covered by opaque content).
    * This will only be called for plugins which have been registered
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4589,31 +4589,27 @@ nsIFrame* NS_NewContinuingTextFrame(nsIP
   return new (aPresShell)
       nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
 
 nsTextFrame::~nsTextFrame() {}
 
-nsresult nsTextFrame::GetCursor(const nsPoint& aPoint,
-                                nsIFrame::Cursor& aCursor) {
-  FillCursorInformationFromStyle(StyleUI(), aCursor);
-  if (StyleCursorKind::Auto == aCursor.mCursor) {
+Maybe<nsIFrame::Cursor> nsTextFrame::GetCursor(const nsPoint& aPoint) {
+  StyleCursorKind kind = StyleUI()->mCursor;
+  if (kind == StyleCursorKind::Auto) {
     if (!IsSelectable(nullptr)) {
-      aCursor.mCursor = StyleCursorKind::Default;
+      kind = StyleCursorKind::Default;
     } else {
-      aCursor.mCursor = GetWritingMode().IsVertical()
-                            ? StyleCursorKind::VerticalText
-                            : StyleCursorKind::Text;
-    }
-    return NS_OK;
-  } else {
-    return nsFrame::GetCursor(aPoint, aCursor);
-  }
+      kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
+                                           : StyleCursorKind::Text;
+    }
+  }
+  return Some(Cursor{kind, AllowCustomCursorImage::Yes});
 }
 
 nsTextFrame* nsTextFrame::LastInFlow() const {
   nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
   while (lastInFlow->GetNextInFlow()) {
     lastInFlow = lastInFlow->GetNextInFlow();
   }
   MOZ_ASSERT(lastInFlow, "post-condition failed");
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -74,17 +74,17 @@ class nsTextFrame : public nsFrame {
                         const nsDisplayListSet& aLists) final;
 
   void Init(nsIContent* aContent, nsContainerFrame* aParent,
             nsIFrame* aPrevInFlow) override;
 
   void DestroyFrom(nsIFrame* aDestructRoot,
                    PostDestroyData& aPostDestroyData) override;
 
-  nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) final;
+  mozilla::Maybe<Cursor> GetCursor(const nsPoint&) final;
 
   nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final;
 
   nsTextFrame* GetPrevContinuation() const override { return nullptr; }
   nsTextFrame* GetNextContinuation() const final { return mNextContinuation; }
   void SetNextContinuation(nsIFrame* aNextContinuation) final {
     NS_ASSERTION(!aNextContinuation || Type() == aNextContinuation->Type(),
                  "setting a next continuation with incorrect type!");
--- a/layout/xul/nsSplitterFrame.cpp
+++ b/layout/xul/nsSplitterFrame.cpp
@@ -211,30 +211,16 @@ void nsSplitterFrame::DestroyFrom(nsIFra
     mInner->RemoveListener();
     mInner->Disconnect();
     mInner->Release();
     mInner = nullptr;
   }
   nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
-nsresult nsSplitterFrame::GetCursor(const nsPoint& aPoint,
-                                    nsIFrame::Cursor& aCursor) {
-  return nsBoxFrame::GetCursor(aPoint, aCursor);
-
-  /*
-    if (IsXULHorizontal())
-      aCursor = NS_STYLE_CURSOR_N_RESIZE;
-    else
-      aCursor = NS_STYLE_CURSOR_W_RESIZE;
-
-    return NS_OK;
-  */
-}
-
 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
                                            nsAtom* aAttribute,
                                            int32_t aModType) {
   nsresult rv =
       nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
   if (aAttribute == nsGkAtoms::state) {
     mInner->UpdateState();
   }
--- a/layout/xul/nsSplitterFrame.h
+++ b/layout/xul/nsSplitterFrame.h
@@ -35,19 +35,16 @@ class nsSplitterFrame final : public nsB
 
   // nsIFrame overrides
   virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
                                     int32_t aModType) override;
 
   virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
                     nsIFrame* aPrevInFlow) override;
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
-
   NS_IMETHOD DoXULLayout(nsBoxLayoutState& aBoxLayoutState) override;
 
   NS_IMETHOD HandlePress(nsPresContext* aPresContext,
                          mozilla::WidgetGUIEvent* aEvent,
                          nsEventStatus* aEventStatus) override;
 
   NS_IMETHOD HandleMultiplePress(nsPresContext* aPresContext,
                                  mozilla::WidgetGUIEvent* aEvent,
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -2214,40 +2214,38 @@ nscoord nsTreeBodyFrame::CalcHorzWidth(c
   }
 
   // If no horz scrolling periphery is present, then just return our width
   if (width == 0) width = mRect.width;
 
   return width;
 }
 
-nsresult nsTreeBodyFrame::GetCursor(const nsPoint& aPoint,
-                                    nsIFrame::Cursor& aCursor) {
+Maybe<nsIFrame::Cursor> nsTreeBodyFrame::GetCursor(const nsPoint& aPoint) {
   // Check the GetScriptHandlingObject so we don't end up running code when
   // the document is a zombie.
   bool dummy;
   if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
     int32_t row;
     nsTreeColumn* col;
     nsCSSAnonBoxPseudoStaticAtom* child;
     GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
 
     if (child) {
       // Our scratch array is already prefilled.
-      ComputedStyle* childContext = GetPseudoComputedStyle(child);
-
-      FillCursorInformationFromStyle(childContext->StyleUI(), aCursor);
-      if (aCursor.mCursor == StyleCursorKind::Auto)
-        aCursor.mCursor = StyleCursorKind::Default;
-
-      return NS_OK;
+      RefPtr<ComputedStyle> childContext = GetPseudoComputedStyle(child);
+      StyleCursorKind kind = childContext->StyleUI()->mCursor;
+      if (kind == StyleCursorKind::Auto) {
+        kind = StyleCursorKind::Default;
+      }
+      return Some(
+          Cursor{kind, AllowCustomCursorImage::Yes, std::move(childContext)});
     }
   }
-
-  return nsLeafBoxFrame::GetCursor(aPoint, aCursor);
+  return nsLeafBoxFrame::GetCursor(aPoint);
 }
 
 static uint32_t GetDropEffect(WidgetGUIEvent* aEvent) {
   NS_ASSERTION(aEvent->mClass == eDragEventClass, "wrong event type");
   WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
   nsContentUtils::SetDataTransferInEvent(dragEvent);
 
   uint32_t action = 0;
--- a/layout/xul/tree/nsTreeBodyFrame.h
+++ b/layout/xul/tree/nsTreeBodyFrame.h
@@ -151,18 +151,17 @@ class nsTreeBodyFrame final : public nsL
   }
 
   // Overridden from nsIFrame to cache our pres context.
   virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
                     nsIFrame* aPrevInFlow) override;
   virtual void DestroyFrom(nsIFrame* aDestructRoot,
                            PostDestroyData& aPostDestroyData) override;
 
-  virtual nsresult GetCursor(const nsPoint& aPoint,
-                             nsIFrame::Cursor& aCursor) override;
+  mozilla::Maybe<Cursor> GetCursor(const nsPoint&) override;
 
   virtual nsresult HandleEvent(nsPresContext* aPresContext,
                                mozilla::WidgetGUIEvent* aEvent,
                                nsEventStatus* aEventStatus) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override;