Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / cocoa / nsCursorManager.mm
blob135b7216f27646834a5e525920db1948f667a3e0
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "imgIContainer.h"
6 #include "nsCocoaUtils.h"
7 #include "nsCursorManager.h"
8 #include "nsObjCExceptions.h"
9 #include <math.h>
11 static nsCursorManager* gInstance;
12 static CGFloat sCurrentCursorScaleFactor = 0.0f;
13 MOZ_RUNINIT static nsIWidget::Cursor sCurrentCursor;
14 static constexpr nsCursor kCustomCursor = eCursorCount;
16 /*! @category nsCursorManager(PrivateMethods)
17     Private methods for the cursor manager class.
19 @interface nsCursorManager (PrivateMethods)
20 /*! @method     getCursor:
21     @abstract   Get a reference to the native Mac representation of a cursor.
22     @discussion Gets a reference to the Mac native implementation of a cursor.
23                 If the cursor has been requested before, it is retreived from
24                 the cursor cache, otherwise it is created and cached.
25     @param      aCursor the cursor to get
26     @result     the Mac native implementation of the cursor
28 - (nsMacCursor*)getCursor:(nsCursor)aCursor;
30 /*! @method     setMacCursor:
31  @abstract   Set the current Mac native cursor
32  @discussion Sets the current cursor - this routine is what actually causes the
33              cursor to change. The argument is retained and the old cursor is
34              released.
35  @param      aMacCursor the cursor to set
36  @result     NS_OK
37  */
38 - (nsresult)setMacCursor:(nsMacCursor*)aMacCursor;
40 /*! @method     createCursor:
41     @abstract   Create a Mac native representation of a cursor.
42     @discussion Creates a version of the Mac native representation of this
43                 cursor.
44     @param      aCursor the cursor to create
45     @result     the Mac native implementation of the cursor
47 + (nsMacCursor*)createCursor:(enum nsCursor)aCursor;
49 @end
51 @implementation nsCursorManager
53 + (nsCursorManager*)sharedInstance {
54   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
56   if (!gInstance) {
57     gInstance = [[nsCursorManager alloc] init];
58   }
59   return gInstance;
61   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
64 + (void)dispose {
65   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
67   [gInstance release];
68   gInstance = nil;
70   NS_OBJC_END_TRY_IGNORE_BLOCK;
73 + (nsMacCursor*)createCursor:(enum nsCursor)aCursor {
74   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
76   switch (aCursor) {
77     case eCursor_standard:
78       return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
79     case eCursor_wait:
80     case eCursor_spinning: {
81       return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor]
82                                       type:aCursor];
83     }
84     case eCursor_select:
85       return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
86     case eCursor_hyperlink:
87       return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor]
88                                       type:aCursor];
89     case eCursor_crosshair:
90       return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor]
91                                       type:aCursor];
92     case eCursor_move:
93       return [nsMacCursor cursorWithImageNamed:@"move"
94                                        hotSpot:NSMakePoint(12, 12)
95                                           type:aCursor];
96     case eCursor_help:
97       return [nsMacCursor cursorWithImageNamed:@"help"
98                                        hotSpot:NSMakePoint(12, 12)
99                                           type:aCursor];
100     case eCursor_copy: {
101       SEL cursorSelector = @selector(dragCopyCursor);
102       return [nsMacCursor
103           cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
104                                ? [NSCursor performSelector:cursorSelector]
105                                : [NSCursor arrowCursor]
106                       type:aCursor];
107     }
108     case eCursor_alias: {
109       SEL cursorSelector = @selector(dragLinkCursor);
110       return [nsMacCursor
111           cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
112                                ? [NSCursor performSelector:cursorSelector]
113                                : [NSCursor arrowCursor]
114                       type:aCursor];
115     }
116     case eCursor_context_menu: {
117       SEL cursorSelector = @selector(contextualMenuCursor);
118       return [nsMacCursor
119           cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
120                                ? [NSCursor performSelector:cursorSelector]
121                                : [NSCursor arrowCursor]
122                       type:aCursor];
123     }
124     case eCursor_cell:
125       return [nsMacCursor cursorWithImageNamed:@"cell"
126                                        hotSpot:NSMakePoint(12, 12)
127                                           type:aCursor];
128     case eCursor_grab:
129       return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor]
130                                       type:aCursor];
131     case eCursor_grabbing:
132       return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor]
133                                       type:aCursor];
134     case eCursor_zoom_in:
135       return [nsMacCursor cursorWithImageNamed:@"zoomIn"
136                                        hotSpot:NSMakePoint(10, 10)
137                                           type:aCursor];
138     case eCursor_zoom_out:
139       return [nsMacCursor cursorWithImageNamed:@"zoomOut"
140                                        hotSpot:NSMakePoint(10, 10)
141                                           type:aCursor];
142     case eCursor_vertical_text:
143       return [nsMacCursor cursorWithImageNamed:@"vtIBeam"
144                                        hotSpot:NSMakePoint(12, 11)
145                                           type:aCursor];
146     case eCursor_all_scroll:
147       return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor]
148                                       type:aCursor];
149     case eCursor_not_allowed:
150     case eCursor_no_drop: {
151       SEL cursorSelector = @selector(operationNotAllowedCursor);
152       return [nsMacCursor
153           cursorWithCursor:[NSCursor respondsToSelector:cursorSelector]
154                                ? [NSCursor performSelector:cursorSelector]
155                                : [NSCursor arrowCursor]
156                       type:aCursor];
157     }
158     // Resize Cursors:
159     // North
160     case eCursor_n_resize:
161       return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor]
162                                       type:aCursor];
163     // North East
164     case eCursor_ne_resize:
165       return [nsMacCursor cursorWithImageNamed:@"sizeNE"
166                                        hotSpot:NSMakePoint(12, 11)
167                                           type:aCursor];
168     // East
169     case eCursor_e_resize:
170       return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor]
171                                       type:aCursor];
172     // South East
173     case eCursor_se_resize:
174       return [nsMacCursor cursorWithImageNamed:@"sizeSE"
175                                        hotSpot:NSMakePoint(12, 12)
176                                           type:aCursor];
177     // South
178     case eCursor_s_resize:
179       return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor]
180                                       type:aCursor];
181     // South West
182     case eCursor_sw_resize:
183       return [nsMacCursor cursorWithImageNamed:@"sizeSW"
184                                        hotSpot:NSMakePoint(10, 12)
185                                           type:aCursor];
186     // West
187     case eCursor_w_resize:
188       return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor]
189                                       type:aCursor];
190     // North West
191     case eCursor_nw_resize:
192       return [nsMacCursor cursorWithImageNamed:@"sizeNW"
193                                        hotSpot:NSMakePoint(11, 11)
194                                           type:aCursor];
195     // North & South
196     case eCursor_ns_resize:
197       return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor]
198                                       type:aCursor];
199     // East & West
200     case eCursor_ew_resize:
201       return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor]
202                                       type:aCursor];
203     // North East & South West
204     case eCursor_nesw_resize:
205       return [nsMacCursor cursorWithImageNamed:@"sizeNESW"
206                                        hotSpot:NSMakePoint(12, 12)
207                                           type:aCursor];
208     // North West & South East
209     case eCursor_nwse_resize:
210       return [nsMacCursor cursorWithImageNamed:@"sizeNWSE"
211                                        hotSpot:NSMakePoint(12, 12)
212                                           type:aCursor];
213     // Column Resize
214     case eCursor_col_resize:
215       return [nsMacCursor cursorWithImageNamed:@"colResize"
216                                        hotSpot:NSMakePoint(12, 12)
217                                           type:aCursor];
218     // Row Resize
219     case eCursor_row_resize:
220       return [nsMacCursor cursorWithImageNamed:@"rowResize"
221                                        hotSpot:NSMakePoint(12, 12)
222                                           type:aCursor];
223     default:
224       return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
225   }
227   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
230 - (id)init {
231   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
233   if ((self = [super init])) {
234     mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
235   }
236   return self;
238   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
241 - (nsresult)setNonCustomCursor:(const nsIWidget::Cursor&)aCursor {
242   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
244   [self setMacCursor:[self getCursor:aCursor.mDefaultCursor]];
246   sCurrentCursor = aCursor;
247   return NS_OK;
249   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
252 - (nsresult)setMacCursor:(nsMacCursor*)aMacCursor {
253   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
255   nsCursor oldType = [mCurrentMacCursor type];
256   nsCursor newType = [aMacCursor type];
257   if (oldType != newType) {
258     if (newType == eCursor_none) {
259       [NSCursor hide];
260     } else if (oldType == eCursor_none) {
261       [NSCursor unhide];
262     }
263   }
265   if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
266     [aMacCursor retain];
267     [mCurrentMacCursor unset];
268     [aMacCursor set];
269     [mCurrentMacCursor release];
270     mCurrentMacCursor = aMacCursor;
271   }
273   return NS_OK;
275   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
278 - (nsresult)setCustomCursor:(const nsIWidget::Cursor&)aCursor
279           widgetScaleFactor:(CGFloat)scaleFactor
280                 forceUpdate:(bool)aForceUpdate {
281   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
283   // As the user moves the mouse, this gets called repeatedly with the same
284   // aCursorImage
285   if (!aForceUpdate && sCurrentCursor == aCursor &&
286       sCurrentCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
287     // Native dragging can unset our cursor apparently (see bug 1739352).
288     if (MOZ_UNLIKELY(![mCurrentMacCursor isSet])) {
289       [mCurrentMacCursor set];
290     }
291     return NS_OK;
292   }
294   sCurrentCursor = aCursor;
295   sCurrentCursorScaleFactor = scaleFactor;
297   if (!aCursor.IsCustom()) {
298     return NS_ERROR_FAILURE;
299   }
301   nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
302   // prevent DoS attacks
303   if (size.width > 128 || size.height > 128) {
304     return NS_ERROR_FAILURE;
305   }
307   const NSSize cocoaSize = NSMakeSize(size.width, size.height);
308   NSImage* cursorImage;
309   nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(
310       aCursor.mContainer, imgIContainer::FRAME_FIRST, nullptr, cocoaSize,
311       &cursorImage, scaleFactor);
312   if (NS_FAILED(rv) || !cursorImage) {
313     return NS_ERROR_FAILURE;
314   }
316   [cursorImage setSize:cocoaSize];
317   [[[cursorImage representations] objectAtIndex:0] setSize:cocoaSize];
319   // if the hotspot is nonsensical, make it 0,0
320   uint32_t hotspotX =
321       aCursor.mHotspotX > (uint32_t(size.width) - 1) ? 0 : aCursor.mHotspotX;
322   uint32_t hotspotY =
323       aCursor.mHotspotY > (uint32_t(size.height) - 1) ? 0 : aCursor.mHotspotY;
324   NSPoint hotSpot = ::NSMakePoint(hotspotX, hotspotY);
325   [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc]
326                                                        initWithImage:cursorImage
327                                                              hotSpot:hotSpot]
328                                               type:kCustomCursor]];
329   [cursorImage release];
330   return NS_OK;
332   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
335 - (nsMacCursor*)getCursor:(enum nsCursor)aCursor {
336   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
338   nsMacCursor* result =
339       [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
340   if (!result) {
341     result = [nsCursorManager createCursor:aCursor];
342     [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
343   }
344   return result;
346   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
349 - (void)dealloc {
350   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
352   [mCurrentMacCursor unset];
353   [mCurrentMacCursor release];
354   [mCursors release];
355   sCurrentCursor = {};
356   [super dealloc];
358   NS_OBJC_END_TRY_IGNORE_BLOCK;
361 @end