widget/cocoa/nsDragService.mm

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:6b5104a6e878
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #ifdef MOZ_LOGGING
7 #define FORCE_PR_LOG
8 #endif
9 #include "prlog.h"
10
11 #include "nsDragService.h"
12 #include "nsObjCExceptions.h"
13 #include "nsITransferable.h"
14 #include "nsString.h"
15 #include "nsClipboard.h"
16 #include "nsXPCOM.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsCOMPtr.h"
19 #include "nsPrimitiveHelpers.h"
20 #include "nsLinebreakConverter.h"
21 #include "nsIMacUtils.h"
22 #include "nsIDOMNode.h"
23 #include "nsRect.h"
24 #include "nsPoint.h"
25 #include "nsIIOService.h"
26 #include "nsNetUtil.h"
27 #include "nsIDocument.h"
28 #include "nsIContent.h"
29 #include "nsView.h"
30 #include "gfxContext.h"
31 #include "nsCocoaUtils.h"
32 #include "mozilla/gfx/2D.h"
33 #include "gfxPlatform.h"
34
35 using namespace mozilla;
36 using namespace mozilla::gfx;
37
38 #ifdef PR_LOGGING
39 extern PRLogModuleInfo* sCocoaLog;
40 #endif
41
42 extern void EnsureLogInitialized();
43
44 extern NSPasteboard* globalDragPboard;
45 extern NSView* gLastDragView;
46 extern NSEvent* gLastDragMouseDownEvent;
47 extern bool gUserCancelledDrag;
48
49 // This global makes the transferable array available to Cocoa's promised
50 // file destination callback.
51 nsISupportsArray *gDraggedTransferables = nullptr;
52
53 NSString* const kWildcardPboardType = @"MozillaWildcard";
54 NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
55 NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc
56 NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
57
58 nsDragService::nsDragService()
59 {
60 mNativeDragView = nil;
61 mNativeDragEvent = nil;
62
63 EnsureLogInitialized();
64 }
65
66 nsDragService::~nsDragService()
67 {
68 }
69
70 static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray)
71 {
72 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
73
74 if (!aTransferableArray)
75 return NS_ERROR_FAILURE;
76
77 uint32_t count = 0;
78 aTransferableArray->Count(&count);
79
80 NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
81
82 for (uint32_t i = 0; i < count; i++) {
83 nsCOMPtr<nsISupports> currentTransferableSupports;
84 aTransferableArray->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
85 if (!currentTransferableSupports)
86 return NS_ERROR_FAILURE;
87
88 nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
89 if (!currentTransferable)
90 return NS_ERROR_FAILURE;
91
92 // Transform the transferable to an NSDictionary
93 NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
94 if (!pasteboardOutputDict)
95 return NS_ERROR_FAILURE;
96
97 // write everything out to the general pasteboard
98 unsigned int typeCount = [pasteboardOutputDict count];
99 NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
100 [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
101 // Gecko is initiating this drag so we always want its own views to consider
102 // it. Add our wildcard type to the pasteboard to accomplish this.
103 [types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
104 [dragPBoard declareTypes:types owner:nil];
105 for (unsigned int i = 0; i < typeCount; i++) {
106 NSString* currentKey = [types objectAtIndex:i];
107 id currentValue = [pasteboardOutputDict valueForKey:currentKey];
108 if (currentKey == NSStringPboardType ||
109 currentKey == kCorePboardType_url ||
110 currentKey == kCorePboardType_urld ||
111 currentKey == kCorePboardType_urln) {
112 [dragPBoard setString:currentValue forType:currentKey];
113 }
114 else if (currentKey == NSHTMLPboardType) {
115 [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
116 forType:currentKey];
117 }
118 else if (currentKey == NSTIFFPboardType) {
119 [dragPBoard setData:currentValue forType:currentKey];
120 }
121 else if (currentKey == NSFilesPromisePboardType ||
122 currentKey == NSFilenamesPboardType) {
123 [dragPBoard setPropertyList:currentValue forType:currentKey];
124 }
125 }
126 }
127
128 return NS_OK;
129
130 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
131 }
132
133 NSImage*
134 nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
135 nsIntRect* aDragRect,
136 nsIScriptableRegion* aRegion)
137 {
138 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
139
140 NSPoint screenPoint =
141 [[gLastDragView window] convertBaseToScreen:
142 [gLastDragMouseDownEvent locationInWindow]];
143 // Y coordinates are bottom to top, so reverse this
144 screenPoint.y = nsCocoaUtils::FlippedScreenY(screenPoint.y);
145
146 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
147
148 RefPtr<SourceSurface> surface;
149 nsPresContext* pc;
150 nsresult rv = DrawDrag(aDOMNode, aRegion,
151 NSToIntRound(screenPoint.x),
152 NSToIntRound(screenPoint.y),
153 aDragRect, &surface, &pc);
154 if (!aDragRect->width || !aDragRect->height) {
155 // just use some suitable defaults
156 int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
157 aDragRect->SetRect(nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.x, scaleFactor),
158 nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.y, scaleFactor),
159 size, size);
160 }
161
162 if (NS_FAILED(rv) || !surface)
163 return nil;
164
165 uint32_t width = aDragRect->width;
166 uint32_t height = aDragRect->height;
167
168 nsRefPtr<gfxImageSurface> imgSurface = new gfxImageSurface(
169 gfxIntSize(width, height), gfxImageFormat::ARGB32);
170 if (!imgSurface)
171 return nil;
172
173 RefPtr<DrawTarget> dt =
174 gfxPlatform::GetPlatform()->
175 CreateDrawTargetForSurface(imgSurface, IntSize(width, height));
176 if (!dt)
177 return nil;
178
179 dt->FillRect(gfx::Rect(0, 0, width, height),
180 SurfacePattern(surface, ExtendMode::CLAMP),
181 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
182
183 uint32_t* imageData = (uint32_t*)imgSurface->Data();
184 int32_t stride = imgSurface->Stride();
185
186 NSBitmapImageRep* imageRep =
187 [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
188 pixelsWide:width
189 pixelsHigh:height
190 bitsPerSample:8
191 samplesPerPixel:4
192 hasAlpha:YES
193 isPlanar:NO
194 colorSpaceName:NSDeviceRGBColorSpace
195 bytesPerRow:width * 4
196 bitsPerPixel:32];
197
198 uint8_t* dest = [imageRep bitmapData];
199 for (uint32_t i = 0; i < height; ++i) {
200 uint8_t* src = (uint8_t *)imageData + i * stride;
201 for (uint32_t j = 0; j < width; ++j) {
202 // Reduce transparency overall by multipying by a factor. Remember, Alpha
203 // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
204 #ifdef IS_BIG_ENDIAN
205 dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
206 dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
207 dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
208 dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
209 #else
210 dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
211 dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
212 dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
213 dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
214 #endif
215 src += 4;
216 dest += 4;
217 }
218 }
219
220 NSImage* image =
221 [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
222 height / scaleFactor)];
223 [image addRepresentation:imageRep];
224 [imageRep release];
225
226 return [image autorelease];
227
228 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
229 }
230
231 // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
232 // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
233 // stack when InvokeDragSession gets called.
234 NS_IMETHODIMP
235 nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransferableArray,
236 nsIScriptableRegion* aDragRgn, uint32_t aActionType)
237 {
238 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
239
240 nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
241 aTransferableArray,
242 aDragRgn, aActionType);
243 NS_ENSURE_SUCCESS(rv, rv);
244
245 mDataItems = aTransferableArray;
246
247 // put data on the clipboard
248 if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
249 return NS_ERROR_FAILURE;
250
251 nsIntRect dragRect(0, 0, 20, 20);
252 NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn);
253 if (!image) {
254 // if no image was returned, just draw a rectangle
255 NSSize size;
256 size.width = dragRect.width;
257 size.height = dragRect.height;
258 image = [[NSImage alloc] initWithSize:size];
259 [image lockFocus];
260 [[NSColor grayColor] set];
261 NSBezierPath* path = [NSBezierPath bezierPath];
262 [path setLineWidth:2.0];
263 [path moveToPoint:NSMakePoint(0, 0)];
264 [path lineToPoint:NSMakePoint(0, size.height)];
265 [path lineToPoint:NSMakePoint(size.width, size.height)];
266 [path lineToPoint:NSMakePoint(size.width, 0)];
267 [path lineToPoint:NSMakePoint(0, 0)];
268 [path stroke];
269 [image unlockFocus];
270 }
271
272 nsIntPoint pt(dragRect.x, dragRect.YMost());
273 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
274 NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
275 point.y = nsCocoaUtils::FlippedScreenY(point.y);
276
277 point = [[gLastDragView window] convertScreenToBase: point];
278 NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
279
280 // Save the transferables away in case a promised file callback is invoked.
281 gDraggedTransferables = aTransferableArray;
282
283 nsBaseDragService::StartDragSession();
284 nsBaseDragService::OpenDragPopup();
285
286 // We need to retain the view and the event during the drag in case either gets destroyed.
287 mNativeDragView = [gLastDragView retain];
288 mNativeDragEvent = [gLastDragMouseDownEvent retain];
289
290 gUserCancelledDrag = false;
291 [mNativeDragView dragImage:image
292 at:localPoint
293 offset:NSZeroSize
294 event:mNativeDragEvent
295 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
296 source:mNativeDragView
297 slideBack:YES];
298 gUserCancelledDrag = false;
299
300 if (mDoingDrag)
301 nsBaseDragService::EndDragSession(false);
302
303 return NS_OK;
304
305 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
306 }
307
308 NS_IMETHODIMP
309 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
310 {
311 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
312
313 if (!aTransferable)
314 return NS_ERROR_FAILURE;
315
316 // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
317 nsCOMPtr<nsISupportsArray> flavorList;
318 nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
319 if (NS_FAILED(rv))
320 return NS_ERROR_FAILURE;
321
322 uint32_t acceptableFlavorCount;
323 flavorList->Count(&acceptableFlavorCount);
324
325 // if this drag originated within Mozilla we should just use the cached data from
326 // when the drag started if possible
327 if (mDataItems) {
328 nsCOMPtr<nsISupports> currentTransferableSupports;
329 mDataItems->GetElementAt(aItemIndex, getter_AddRefs(currentTransferableSupports));
330 if (currentTransferableSupports) {
331 nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
332 if (currentTransferable) {
333 for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
334 nsCOMPtr<nsISupports> genericFlavor;
335 flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
336 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
337 if (!currentFlavor)
338 continue;
339 nsXPIDLCString flavorStr;
340 currentFlavor->ToString(getter_Copies(flavorStr));
341
342 nsCOMPtr<nsISupports> dataSupports;
343 uint32_t dataSize = 0;
344 rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
345 if (NS_SUCCEEDED(rv)) {
346 aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
347 return NS_OK; // maybe try to fill in more types? Is there a point?
348 }
349 }
350 }
351 }
352 }
353
354 // now check the actual clipboard for data
355 for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
356 nsCOMPtr<nsISupports> genericFlavor;
357 flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
358 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
359
360 if (!currentFlavor)
361 continue;
362
363 nsXPIDLCString flavorStr;
364 currentFlavor->ToString(getter_Copies(flavorStr));
365
366 PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
367
368 if (flavorStr.EqualsLiteral(kFileMime)) {
369 NSArray* pFiles = [globalDragPboard propertyListForType:NSFilenamesPboardType];
370 if (!pFiles || [pFiles count] < (aItemIndex + 1))
371 continue;
372
373 NSString* filePath = [pFiles objectAtIndex:aItemIndex];
374 if (!filePath)
375 continue;
376
377 unsigned int stringLength = [filePath length];
378 unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
379 char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
380 if (!clipboardDataPtr)
381 return NS_ERROR_OUT_OF_MEMORY;
382 [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
383 clipboardDataPtr[stringLength] = 0; // null terminate
384
385 nsCOMPtr<nsIFile> file;
386 nsresult rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
387 free(clipboardDataPtr);
388 if (NS_FAILED(rv))
389 continue;
390
391 aTransferable->SetTransferData(flavorStr, file, dataLength);
392
393 break;
394 }
395
396 NSString *pboardType = NSStringPboardType;
397
398 if (nsClipboard::IsStringType(flavorStr, &pboardType) ||
399 flavorStr.EqualsLiteral(kURLMime) ||
400 flavorStr.EqualsLiteral(kURLDataMime) ||
401 flavorStr.EqualsLiteral(kURLDescriptionMime)) {
402 NSString* pString = [globalDragPboard stringForType:pboardType];
403 if (!pString)
404 continue;
405
406 NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
407 unsigned int dataLength = [stringData length];
408 void* clipboardDataPtr = malloc(dataLength);
409 if (!clipboardDataPtr)
410 return NS_ERROR_OUT_OF_MEMORY;
411 [stringData getBytes:clipboardDataPtr];
412
413 // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
414 int32_t signedDataLength = dataLength;
415 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
416 dataLength = signedDataLength;
417
418 // skip BOM (Byte Order Mark to distinguish little or big endian)
419 char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
420 if ((dataLength > 2) &&
421 ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
422 (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
423 dataLength -= sizeof(char16_t);
424 clipboardDataPtrNoBOM += 1;
425 }
426
427 nsCOMPtr<nsISupports> genericDataWrapper;
428 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
429 getter_AddRefs(genericDataWrapper));
430 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
431 free(clipboardDataPtr);
432 break;
433 }
434
435 // We have never supported this on Mac OS X, we should someday. Normally dragging images
436 // in is accomplished with a file path drag instead of the image data itself.
437 /*
438 if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
439 flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
440
441 }
442 */
443 }
444 return NS_OK;
445
446 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
447 }
448
449 NS_IMETHODIMP
450 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
451 {
452 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
453
454 *_retval = false;
455
456 if (!globalDragPboard)
457 return NS_ERROR_FAILURE;
458
459 nsDependentCString dataFlavor(aDataFlavor);
460
461 // first see if we have data for this in our cached transferable
462 if (mDataItems) {
463 uint32_t dataItemsCount;
464 mDataItems->Count(&dataItemsCount);
465 for (unsigned int i = 0; i < dataItemsCount; i++) {
466 nsCOMPtr<nsISupports> currentTransferableSupports;
467 mDataItems->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
468 if (!currentTransferableSupports)
469 continue;
470
471 nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
472 if (!currentTransferable)
473 continue;
474
475 nsCOMPtr<nsISupportsArray> flavorList;
476 nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
477 if (NS_FAILED(rv))
478 continue;
479
480 uint32_t flavorCount;
481 flavorList->Count(&flavorCount);
482 for (uint32_t j = 0; j < flavorCount; j++) {
483 nsCOMPtr<nsISupports> genericFlavor;
484 flavorList->GetElementAt(j, getter_AddRefs(genericFlavor));
485 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
486 if (!currentFlavor)
487 continue;
488 nsXPIDLCString flavorStr;
489 currentFlavor->ToString(getter_Copies(flavorStr));
490 if (dataFlavor.Equals(flavorStr)) {
491 *_retval = true;
492 return NS_OK;
493 }
494 }
495 }
496 }
497
498 NSString *pboardType = nil;
499
500 if (dataFlavor.EqualsLiteral(kFileMime)) {
501 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
502 if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
503 *_retval = true;
504 }
505 else if (dataFlavor.EqualsLiteral(kURLMime)) {
506 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
507 if (availableType && [availableType isEqualToString:kCorePboardType_url])
508 *_retval = true;
509 }
510 else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
511 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
512 if (availableType && [availableType isEqualToString:pboardType])
513 *_retval = true;
514 }
515
516 return NS_OK;
517
518 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
519 }
520
521 NS_IMETHODIMP
522 nsDragService::GetNumDropItems(uint32_t* aNumItems)
523 {
524 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
525
526 *aNumItems = 0;
527
528 // first check to see if we have a number of items cached
529 if (mDataItems) {
530 mDataItems->Count(aNumItems);
531 return NS_OK;
532 }
533
534 // if there is a clipboard and there is something on it, then there is at least 1 item
535 NSArray* clipboardTypes = [globalDragPboard types];
536 if (globalDragPboard && [clipboardTypes count] > 0)
537 *aNumItems = 1;
538 else
539 return NS_OK;
540
541 // if there is a list of files, send the number of files in that list
542 NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
543 if (fileNames)
544 *aNumItems = [fileNames count];
545
546 return NS_OK;
547
548 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
549 }
550
551 NS_IMETHODIMP
552 nsDragService::EndDragSession(bool aDoneDrag)
553 {
554 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
555
556 if (mNativeDragView) {
557 [mNativeDragView release];
558 mNativeDragView = nil;
559 }
560 if (mNativeDragEvent) {
561 [mNativeDragEvent release];
562 mNativeDragEvent = nil;
563 }
564
565 mUserCancelled = gUserCancelledDrag;
566
567 nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
568 mDataItems = nullptr;
569 return rv;
570
571 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
572 }

mercurial