tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 00cdf3a94603b81c9d18fee7028bc3eb34d265bb
parent aeb36f1750c026dc00bb86c1218080f06173ef93
Author: Markus Stange <mstange.moz@gmail.com>
Date:   Thu, 23 Oct 2025 04:49:51 +0000

Bug 1820168 - Let the system re-set the current cursor when needed. r=mac-reviewers,bradwerth

This renames nsMacCursorManager to MOZDynamicCursor and makes it implement NSCursor.

We also install a cursor rect which covers the entire ChildView and which refers
to the MOZDynamicCursor singleton.

With the help of the cursor rect, the -[NSCursor set] implementation is now called
at the right times.

Here's a profile which contains the stacks under which -[MOZDynamicCursor set]
is now called: https://share.firefox.dev/3yEXLsC

Differential Revision: https://phabricator.services.mozilla.com/D172484

Diffstat:
Awidget/cocoa/MOZDynamicCursor.h | 37+++++++++++++++++++++++++++++++++++++
Awidget/cocoa/MOZDynamicCursor.mm | 308+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mwidget/cocoa/moz.build | 2+-
Mwidget/cocoa/nsCocoaWindow.mm | 13++++++++-----
Dwidget/cocoa/nsCursorManager.h | 37-------------------------------------
Dwidget/cocoa/nsCursorManager.mm | 301-------------------------------------------------------------------------------
6 files changed, 354 insertions(+), 344 deletions(-)

diff --git a/widget/cocoa/MOZDynamicCursor.h b/widget/cocoa/MOZDynamicCursor.h @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZDynamicCursor_h_ +#define MOZDynamicCursor_h_ + +#import <Cocoa/Cocoa.h> +#include "nsIWidget.h" + +// MOZDynamicCursor.sharedInstance is a singleton NSCursor object whose +// underlying cursor can be changed at runtime. +// It can be used in an NSView cursorRect so that the system will call +// -[NSCursor set] on it at the right moments, for example when the +// mouse moves into a window or when the cursor needs to be set after +// a drag operation or when a context menu closes. +@interface MOZDynamicCursor : NSCursor { + @private + NSMutableDictionary* mCursors; + NSCursor* mCurrentCursor; + nsCursor mCurrentCursorType; +} + +// Sets non-custom cursors and can be used as a fallback if setting +// a custom cursor did not succeed. +- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor; + +// As above, but returns an error if the cursor isn't custom or we couldn't set +// it for some reason. +- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor + widgetScaleFactor:(CGFloat)aWidgetScaleFactor + forceUpdate:(bool)aForceUpdate; + ++ (MOZDynamicCursor*)sharedInstance; +@end + +#endif // MOZDynamicCursor_h_ diff --git a/widget/cocoa/MOZDynamicCursor.mm b/widget/cocoa/MOZDynamicCursor.mm @@ -0,0 +1,308 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "imgIContainer.h" +#include "nsCocoaUtils.h" +#include "MOZDynamicCursor.h" +#include "nsObjCExceptions.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include <math.h> + +static MOZDynamicCursor* gInstance; +static CGFloat sCurrentCursorScaleFactor = 0.0f; +MOZ_RUNINIT static nsIWidget::Cursor sCurrentCursor; +static constexpr nsCursor kCustomCursor = eCursorCount; + +@interface MOZDynamicCursor (PrivateMethods) ++ (NSCursor*)freshCursorWithType:(nsCursor)aCursor; +- (NSCursor*)cursorWithType:(nsCursor)aCursor; + +// Set the cursor. +- (void)setCursor:(NSCursor*)aMacCursor; + +@end + +@interface NSCursor (CreateWithImageNamed) ++ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint; +@end + +@interface NSCursor (Undocumented) +// busyButClickableCursor is an undocumented NSCursor API, but has been in use +// since at least OS X 10.4 and through 10.9. ++ (NSCursor*)busyButClickableCursor; +@end + +@implementation MOZDynamicCursor + ++ (MOZDynamicCursor*)sharedInstance { + if (!gInstance) { + gInstance = [[MOZDynamicCursor alloc] init]; + } + return gInstance; +} + ++ (NSCursor*)freshCursorWithType:(enum nsCursor)aCursor { + switch (aCursor) { + case eCursor_standard: + return [NSCursor arrowCursor]; + case eCursor_wait: + case eCursor_spinning: { + return [NSCursor busyButClickableCursor]; + } + case eCursor_select: + return [NSCursor IBeamCursor]; + case eCursor_hyperlink: + return [NSCursor pointingHandCursor]; + case eCursor_crosshair: + return [NSCursor crosshairCursor]; + case eCursor_move: + return [NSCursor cursorWithImageNamed:@"move" + hotSpot:NSMakePoint(12, 12)]; + case eCursor_help: + return [NSCursor cursorWithImageNamed:@"help" + hotSpot:NSMakePoint(12, 12)]; + case eCursor_copy: { + return [NSCursor dragCopyCursor]; + } + case eCursor_alias: { + return [NSCursor dragLinkCursor]; + } + case eCursor_context_menu: { + return [NSCursor contextualMenuCursor]; + } + case eCursor_cell: + return [NSCursor cursorWithImageNamed:@"cell" + hotSpot:NSMakePoint(12, 12)]; + case eCursor_grab: + return [NSCursor openHandCursor]; + case eCursor_grabbing: + return [NSCursor closedHandCursor]; + case eCursor_zoom_in: + return [NSCursor cursorWithImageNamed:@"zoomIn" + hotSpot:NSMakePoint(10, 10)]; + case eCursor_zoom_out: + return [NSCursor cursorWithImageNamed:@"zoomOut" + hotSpot:NSMakePoint(10, 10)]; + case eCursor_vertical_text: + return [NSCursor cursorWithImageNamed:@"vtIBeam" + hotSpot:NSMakePoint(12, 11)]; + case eCursor_all_scroll: + return [NSCursor openHandCursor]; + case eCursor_not_allowed: + case eCursor_no_drop: { + return [NSCursor operationNotAllowedCursor]; + } + // Resize Cursors: + // North + case eCursor_n_resize: + return [NSCursor resizeUpCursor]; + // North East + case eCursor_ne_resize: + return [NSCursor cursorWithImageNamed:@"sizeNE" + hotSpot:NSMakePoint(12, 11)]; + // East + case eCursor_e_resize: + return [NSCursor resizeRightCursor]; + // South East + case eCursor_se_resize: + return [NSCursor cursorWithImageNamed:@"sizeSE" + hotSpot:NSMakePoint(12, 12)]; + // South + case eCursor_s_resize: + return [NSCursor resizeDownCursor]; + // South West + case eCursor_sw_resize: + return [NSCursor cursorWithImageNamed:@"sizeSW" + hotSpot:NSMakePoint(10, 12)]; + // West + case eCursor_w_resize: + return [NSCursor resizeLeftCursor]; + // North West + case eCursor_nw_resize: + return [NSCursor cursorWithImageNamed:@"sizeNW" + hotSpot:NSMakePoint(11, 11)]; + // North & South + case eCursor_ns_resize: + return [NSCursor resizeUpDownCursor]; + // East & West + case eCursor_ew_resize: + return [NSCursor resizeLeftRightCursor]; + // North East & South West + case eCursor_nesw_resize: + return [NSCursor cursorWithImageNamed:@"sizeNESW" + hotSpot:NSMakePoint(12, 12)]; + // North West & South East + case eCursor_nwse_resize: + return [NSCursor cursorWithImageNamed:@"sizeNWSE" + hotSpot:NSMakePoint(12, 12)]; + // Column Resize + case eCursor_col_resize: + return [NSCursor cursorWithImageNamed:@"colResize" + hotSpot:NSMakePoint(12, 12)]; + // Row Resize + case eCursor_row_resize: + return [NSCursor cursorWithImageNamed:@"rowResize" + hotSpot:NSMakePoint(12, 12)]; + default: + return [NSCursor arrowCursor]; + } +} + +- (id)init { + if ((self = [super init])) { + mCursors = [[NSMutableDictionary alloc] initWithCapacity:25]; + mCurrentCursor = [[NSCursor arrowCursor] retain]; + mCurrentCursorType = eCursor_standard; + } + return self; +} + +- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor { + [self setCursor:[self cursorWithType:aCursor.mDefaultCursor] + type:aCursor.mDefaultCursor]; + sCurrentCursor = aCursor; +} + +- (void)setCursor:(NSCursor*)aMacCursor type:(nsCursor)aType { + if (mCurrentCursorType != aType) { + if (aType == eCursor_none) { + [NSCursor hide]; + } else if (mCurrentCursorType == eCursor_none) { + [NSCursor unhide]; + } + mCurrentCursorType = aType; + } + + if (mCurrentCursor != aMacCursor) { + [mCurrentCursor release]; + mCurrentCursor = [aMacCursor retain]; + [mCurrentCursor set]; + } +} + +- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor + widgetScaleFactor:(CGFloat)scaleFactor + forceUpdate:(bool)aForceUpdate { + // As the user moves the mouse, this gets called repeatedly with the same + // aCursorImage + if (!aForceUpdate && sCurrentCursor == aCursor && + sCurrentCursorScaleFactor == scaleFactor && mCurrentCursor) { + return NS_OK; + } + + sCurrentCursor = aCursor; + sCurrentCursorScaleFactor = scaleFactor; + + if (!aCursor.IsCustom()) { + return NS_ERROR_FAILURE; + } + + nsIntSize size = nsIWidget::CustomCursorSize(aCursor); + // prevent DoS attacks + if (size.width > 128 || size.height > 128) { + return NS_ERROR_FAILURE; + } + + const NSSize cocoaSize = NSMakeSize(size.width, size.height); + NSImage* cursorImage; + nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer( + aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, cocoaSize, + &cursorImage, scaleFactor); + if (NS_FAILED(rv) || !cursorImage) { + return NS_ERROR_FAILURE; + } + + [cursorImage setSize:cocoaSize]; + [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize]; + + // if the hotspot is nonsensical, make it 0,0 + uint32_t hotspotX = + aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX; + uint32_t hotspotY = + aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY; + NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY); + [self setCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot] + type:kCustomCursor]; + [cursorImage release]; + return NS_OK; +} + +- (NSCursor*)cursorWithType:(enum nsCursor)aCursor { + NSCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]]; + if (!result) { + result = [MOZDynamicCursor freshCursorWithType:aCursor]; + [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]]; + } + return result; +} + +// This method gets called by ChildView's cursor rect (or rather its underlying +// NSTrackingArea) whenever the mouse enters it, for example after a dragging +// operation, after a menu closes, or when the mouse enters a window. +- (void)set { + [mCurrentCursor set]; +} + +- (void)dealloc { + [mCurrentCursor release]; + [mCursors release]; + sCurrentCursor = {}; + [super dealloc]; +} + +@end + +@implementation NSCursor (CreateWithImageName) + ++ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint { + nsCOMPtr<nsIFile> resDir; + nsAutoCString resPath; + NSString *pathToImage, *pathToHiDpiImage; + NSImage *cursorImage, *hiDpiCursorImage; + + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir)); + if (NS_FAILED(rv)) goto INIT_FAILURE; + resDir->AppendNative("res"_ns); + resDir->AppendNative("cursors"_ns); + + rv = resDir->GetNativePath(resPath); + if (NS_FAILED(rv)) goto INIT_FAILURE; + + pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()]; + if (!pathToImage) goto INIT_FAILURE; + pathToImage = [pathToImage stringByAppendingPathComponent:imageName]; + pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"]; + // Add same extension to both image paths. + pathToImage = [pathToImage stringByAppendingPathExtension:@"png"]; + pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"]; + + cursorImage = + [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease]; + if (!cursorImage) goto INIT_FAILURE; + + // Note 1: There are a few different ways to get a hidpi image via + // initWithContentsOfFile. We let the OS handle this here: when the + // file basename ends in "@2x", it will be displayed at native resolution + // instead of being pixel-doubled. See bug 784909 comment 7 for alternate + // ways. + // + // Note 2: The OS is picky, and will ignore the hidpi representation + // unless it is exactly twice the size of the lowdpi image. + hiDpiCursorImage = + [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease]; + if (hiDpiCursorImage) { + NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0]; + [cursorImage addRepresentation:imageRep]; + } + return [[[NSCursor alloc] initWithImage:cursorImage + hotSpot:aPoint] autorelease]; + +INIT_FAILURE: + NS_WARNING("Problem getting path to cursor image file!"); + [self release]; + return nil; +} + +@end diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build @@ -33,6 +33,7 @@ UNIFIED_SOURCES += [ "CompositorWidgetChild.cpp", "CompositorWidgetParent.cpp", "GfxInfo.mm", + "MOZDynamicCursor.mm", "MOZIconHelper.mm", "MOZMenuOpeningCoordinator.mm", "NativeKeyBindings.mm", @@ -44,7 +45,6 @@ UNIFIED_SOURCES += [ "nsCocoaUtils.mm", "nsCocoaWindow.mm", "nsColorPicker.mm", - "nsCursorManager.mm", "nsDeviceContextSpecX.mm", "nsFilePicker.mm", "nsLookAndFeel.mm", diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm @@ -7,7 +7,7 @@ #include "nsCocoaWindow.h" #include "nsArrayUtils.h" -#include "nsCursorManager.h" +#include "MOZDynamicCursor.h" #include "nsIAppStartup.h" #include "nsIDOMWindowUtils.h" #include "nsILocalFileMac.h" @@ -315,14 +315,14 @@ void nsCocoaWindow::SetCursor(const Cursor& aCursor) { bool forceUpdate = mUpdateCursor; mUpdateCursor = false; - if (mCustomCursorAllowed && NS_SUCCEEDED([[nsCursorManager sharedInstance] + if (mCustomCursorAllowed && NS_SUCCEEDED([MOZDynamicCursor.sharedInstance setCustomCursor:aCursor widgetScaleFactor:BackingScaleFactor() forceUpdate:forceUpdate])) { return; } - [[nsCursorManager sharedInstance] setNonCustomCursor:aCursor]; + [MOZDynamicCursor.sharedInstance setNonCustomCursor:aCursor]; NS_OBJC_END_TRY_IGNORE_BLOCK; } @@ -3710,6 +3710,10 @@ static gfx::IntPoint GetIntegerDeltaForEvent(NSEvent* aEvent) { NS_OBJC_END_TRY_BLOCK_RETURN(NSDragOperationNone); } +- (void)resetCursorRects { + [self addCursorRect:self.bounds cursor:MOZDynamicCursor.sharedInstance]; +} + // NSDraggingDestination - (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; @@ -4535,7 +4539,7 @@ void ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, // After the cursor exits the window set it to a visible regular arrow // cursor. if (exitFrom == WidgetMouseEvent::ePlatformTopLevel) { - [[nsCursorManager sharedInstance] + [MOZDynamicCursor.sharedInstance setNonCustomCursor:nsIWidget::Cursor{eCursor_standard}]; } [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent @@ -4997,7 +5001,6 @@ nsresult nsCocoaWindow::CreateNativeWindow(const NSRect& aRect, mWindow.collectionBehavior | NSWindowCollectionBehaviorCanJoinAllSpaces; } mWindow.contentMinSize = NSMakeSize(60, 60); - [mWindow disableCursorRects]; // Make the window use CoreAnimation from the start, so that we don't // switch from a non-CA window to a CA-window in the middle. diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef nsCursorManager_h_ -#define nsCursorManager_h_ - -#import <Cocoa/Cocoa.h> -#include "nsIWidget.h" - -@interface nsCursorManager : NSObject { - @private - NSMutableDictionary* mCursors; - NSCursor* mCurrentCursor; - nsCursor mCurrentCursorType; -} - -// Sets non-custom cursors and can be used as a fallback if setting -// a custom cursor did not succeed. -- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor; - -// As above, but returns an error if the cursor isn't custom or we couldn't set -// it for some reason. -- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor - widgetScaleFactor:(CGFloat)aWidgetScaleFactor - forceUpdate:(bool)aForceUpdate; - -+ (nsCursorManager*)sharedInstance; -@end - -@interface NSCursor (Undocumented) -// busyButClickableCursor is an undocumented NSCursor API, but has been in use -// since at least OS X 10.4 and through 10.9. -+ (NSCursor*)busyButClickableCursor; -@end - -#endif // nsCursorManager_h_ diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm @@ -1,301 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "imgIContainer.h" -#include "nsCocoaUtils.h" -#include "nsCursorManager.h" -#include "nsObjCExceptions.h" -#include "nsDirectoryServiceDefs.h" -#include "nsIFile.h" -#include <math.h> - -static nsCursorManager* gInstance; -static CGFloat sCurrentCursorScaleFactor = 0.0f; -MOZ_RUNINIT static nsIWidget::Cursor sCurrentCursor; -static constexpr nsCursor kCustomCursor = eCursorCount; - -@interface nsCursorManager (PrivateMethods) -+ (NSCursor*)freshCursorWithType:(nsCursor)aCursor; -- (NSCursor*)cursorWithType:(nsCursor)aCursor; - -// Set the cursor. -- (void)setCursor:(NSCursor*)aMacCursor; - -@end - -@interface NSCursor (CreateWithImageNamed) -+ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint; -@end - -@implementation nsCursorManager - -+ (nsCursorManager*)sharedInstance { - if (!gInstance) { - gInstance = [[nsCursorManager alloc] init]; - } - return gInstance; -} - -+ (NSCursor*)freshCursorWithType:(enum nsCursor)aCursor { - switch (aCursor) { - case eCursor_standard: - return [NSCursor arrowCursor]; - case eCursor_wait: - case eCursor_spinning: { - return [NSCursor busyButClickableCursor]; - } - case eCursor_select: - return [NSCursor IBeamCursor]; - case eCursor_hyperlink: - return [NSCursor pointingHandCursor]; - case eCursor_crosshair: - return [NSCursor crosshairCursor]; - case eCursor_move: - return [NSCursor cursorWithImageNamed:@"move" - hotSpot:NSMakePoint(12, 12)]; - case eCursor_help: - return [NSCursor cursorWithImageNamed:@"help" - hotSpot:NSMakePoint(12, 12)]; - case eCursor_copy: { - return [NSCursor dragCopyCursor]; - } - case eCursor_alias: { - return [NSCursor dragLinkCursor]; - } - case eCursor_context_menu: { - return [NSCursor contextualMenuCursor]; - } - case eCursor_cell: - return [NSCursor cursorWithImageNamed:@"cell" - hotSpot:NSMakePoint(12, 12)]; - case eCursor_grab: - return [NSCursor openHandCursor]; - case eCursor_grabbing: - return [NSCursor closedHandCursor]; - case eCursor_zoom_in: - return [NSCursor cursorWithImageNamed:@"zoomIn" - hotSpot:NSMakePoint(10, 10)]; - case eCursor_zoom_out: - return [NSCursor cursorWithImageNamed:@"zoomOut" - hotSpot:NSMakePoint(10, 10)]; - case eCursor_vertical_text: - return [NSCursor cursorWithImageNamed:@"vtIBeam" - hotSpot:NSMakePoint(12, 11)]; - case eCursor_all_scroll: - return [NSCursor openHandCursor]; - case eCursor_not_allowed: - case eCursor_no_drop: { - return [NSCursor operationNotAllowedCursor]; - } - // Resize Cursors: - // North - case eCursor_n_resize: - return [NSCursor resizeUpCursor]; - // North East - case eCursor_ne_resize: - return [NSCursor cursorWithImageNamed:@"sizeNE" - hotSpot:NSMakePoint(12, 11)]; - // East - case eCursor_e_resize: - return [NSCursor resizeRightCursor]; - // South East - case eCursor_se_resize: - return [NSCursor cursorWithImageNamed:@"sizeSE" - hotSpot:NSMakePoint(12, 12)]; - // South - case eCursor_s_resize: - return [NSCursor resizeDownCursor]; - // South West - case eCursor_sw_resize: - return [NSCursor cursorWithImageNamed:@"sizeSW" - hotSpot:NSMakePoint(10, 12)]; - // West - case eCursor_w_resize: - return [NSCursor resizeLeftCursor]; - // North West - case eCursor_nw_resize: - return [NSCursor cursorWithImageNamed:@"sizeNW" - hotSpot:NSMakePoint(11, 11)]; - // North & South - case eCursor_ns_resize: - return [NSCursor resizeUpDownCursor]; - // East & West - case eCursor_ew_resize: - return [NSCursor resizeLeftRightCursor]; - // North East & South West - case eCursor_nesw_resize: - return [NSCursor cursorWithImageNamed:@"sizeNESW" - hotSpot:NSMakePoint(12, 12)]; - // North West & South East - case eCursor_nwse_resize: - return [NSCursor cursorWithImageNamed:@"sizeNWSE" - hotSpot:NSMakePoint(12, 12)]; - // Column Resize - case eCursor_col_resize: - return [NSCursor cursorWithImageNamed:@"colResize" - hotSpot:NSMakePoint(12, 12)]; - // Row Resize - case eCursor_row_resize: - return [NSCursor cursorWithImageNamed:@"rowResize" - hotSpot:NSMakePoint(12, 12)]; - default: - return [NSCursor arrowCursor]; - } -} - -- (id)init { - if ((self = [super init])) { - mCursors = [[NSMutableDictionary alloc] initWithCapacity:25]; - mCurrentCursor = [[NSCursor arrowCursor] retain]; - mCurrentCursorType = eCursor_standard; - } - return self; -} - -- (void)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor { - [self setCursor:[self cursorWithType:aCursor.mDefaultCursor] - type:aCursor.mDefaultCursor]; - sCurrentCursor = aCursor; -} - -- (void)setCursor:(NSCursor*)aMacCursor type:(nsCursor)aType { - if (mCurrentCursorType != aType) { - if (aType == eCursor_none) { - [NSCursor hide]; - } else if (mCurrentCursorType == eCursor_none) { - [NSCursor unhide]; - } - mCurrentCursorType = aType; - } - - if (mCurrentCursor != aMacCursor || - [NSCursor currentCursor] != mCurrentCursor) { - [aMacCursor retain]; - [aMacCursor set]; - [mCurrentCursor release]; - mCurrentCursor = aMacCursor; - } -} - -- (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor - widgetScaleFactor:(CGFloat)scaleFactor - forceUpdate:(bool)aForceUpdate { - // As the user moves the mouse, this gets called repeatedly with the same - // aCursorImage - if (!aForceUpdate && sCurrentCursor == aCursor && - sCurrentCursorScaleFactor == scaleFactor && mCurrentCursor) { - // Native dragging can unset our cursor apparently (see bug 1739352). - if (MOZ_UNLIKELY([NSCursor currentCursor] != mCurrentCursor)) { - [mCurrentCursor set]; - } - return NS_OK; - } - - sCurrentCursor = aCursor; - sCurrentCursorScaleFactor = scaleFactor; - - if (!aCursor.IsCustom()) { - return NS_ERROR_FAILURE; - } - - nsIntSize size = nsIWidget::CustomCursorSize(aCursor); - // prevent DoS attacks - if (size.width > 128 || size.height > 128) { - return NS_ERROR_FAILURE; - } - - const NSSize cocoaSize = NSMakeSize(size.width, size.height); - NSImage* cursorImage; - nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer( - aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, cocoaSize, - &cursorImage, scaleFactor); - if (NS_FAILED(rv) || !cursorImage) { - return NS_ERROR_FAILURE; - } - - [cursorImage setSize:cocoaSize]; - [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize]; - - // if the hotspot is nonsensical, make it 0,0 - uint32_t hotspotX = - aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX; - uint32_t hotspotY = - aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY; - NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY); - [self setCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot] - type:kCustomCursor]; - [cursorImage release]; - return NS_OK; -} - -- (NSCursor*)cursorWithType:(enum nsCursor)aCursor { - NSCursor* result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]]; - if (!result) { - result = [nsCursorManager freshCursorWithType:aCursor]; - [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]]; - } - return result; -} - -- (void)dealloc { - [mCurrentCursor release]; - [mCursors release]; - sCurrentCursor = {}; - [super dealloc]; -} - -@end - -@implementation NSCursor (CreateWithImageName) - -+ (NSCursor*)cursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint { - nsCOMPtr<nsIFile> resDir; - nsAutoCString resPath; - NSString *pathToImage, *pathToHiDpiImage; - NSImage *cursorImage, *hiDpiCursorImage; - - nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir)); - if (NS_FAILED(rv)) goto INIT_FAILURE; - resDir->AppendNative("res"_ns); - resDir->AppendNative("cursors"_ns); - - rv = resDir->GetNativePath(resPath); - if (NS_FAILED(rv)) goto INIT_FAILURE; - - pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()]; - if (!pathToImage) goto INIT_FAILURE; - pathToImage = [pathToImage stringByAppendingPathComponent:imageName]; - pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"]; - // Add same extension to both image paths. - pathToImage = [pathToImage stringByAppendingPathExtension:@"png"]; - pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"]; - - cursorImage = - [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease]; - if (!cursorImage) goto INIT_FAILURE; - - // Note 1: There are a few different ways to get a hidpi image via - // initWithContentsOfFile. We let the OS handle this here: when the - // file basename ends in "@2x", it will be displayed at native resolution - // instead of being pixel-doubled. See bug 784909 comment 7 for alternate - // ways. - // - // Note 2: The OS is picky, and will ignore the hidpi representation - // unless it is exactly twice the size of the lowdpi image. - hiDpiCursorImage = - [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease]; - if (hiDpiCursorImage) { - NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0]; - [cursorImage addRepresentation:imageRep]; - } - return [[[NSCursor alloc] initWithImage:cursorImage - hotSpot:aPoint] autorelease]; - -INIT_FAILURE: - NS_WARNING("Problem getting path to cursor image file!"); - [self release]; - return nil; -} - -@end