1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsMenuItemIconX.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,481 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* 1.10 + * Retrieves and displays icons in native menu items on Mac OS X. 1.11 + */ 1.12 + 1.13 +/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c 1.14 + exceptions and produces errors like: error: unexpected '@' in program'. 1.15 + If we define __EXCEPTIONS exception_defines.h will avoid doing this. 1.16 + 1.17 + See bug 666609 for more information. 1.18 + 1.19 + We use <limits> to get the libstdc++ version. */ 1.20 +#include <limits> 1.21 +#if __GLIBCXX__ <= 20070719 1.22 +#define __EXCEPTIONS 1.23 +#endif 1.24 + 1.25 +#include "nsMenuItemIconX.h" 1.26 +#include "nsObjCExceptions.h" 1.27 +#include "nsIContent.h" 1.28 +#include "nsIDocument.h" 1.29 +#include "nsNameSpaceManager.h" 1.30 +#include "nsGkAtoms.h" 1.31 +#include "nsIDOMElement.h" 1.32 +#include "nsIDOMCSSStyleDeclaration.h" 1.33 +#include "nsIDOMCSSValue.h" 1.34 +#include "nsIDOMCSSPrimitiveValue.h" 1.35 +#include "nsIDOMRect.h" 1.36 +#include "nsThreadUtils.h" 1.37 +#include "nsToolkit.h" 1.38 +#include "nsNetUtil.h" 1.39 +#include "imgLoader.h" 1.40 +#include "imgRequestProxy.h" 1.41 +#include "nsMenuItemX.h" 1.42 +#include "gfxPlatform.h" 1.43 +#include "imgIContainer.h" 1.44 +#include "nsCocoaUtils.h" 1.45 +#include "mozIThirdPartyUtil.h" 1.46 +#include "nsContentUtils.h" 1.47 + 1.48 +using mozilla::gfx::SourceSurface; 1.49 +using mozilla::RefPtr; 1.50 + 1.51 +static const uint32_t kIconWidth = 16; 1.52 +static const uint32_t kIconHeight = 16; 1.53 +static const uint32_t kIconBitsPerComponent = 8; 1.54 +static const uint32_t kIconComponents = 4; 1.55 +static const uint32_t kIconBitsPerPixel = kIconBitsPerComponent * 1.56 + kIconComponents; 1.57 +static const uint32_t kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8; 1.58 +static const uint32_t kIconBytes = kIconBytesPerRow * kIconHeight; 1.59 + 1.60 +typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect, 1.61 + GetBottom, (nsIDOMCSSPrimitiveValue**)); 1.62 + 1.63 +NS_IMPL_ISUPPORTS(nsMenuItemIconX, imgINotificationObserver) 1.64 + 1.65 +nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem, 1.66 + nsIContent* aContent, 1.67 + NSMenuItem* aNativeMenuItem) 1.68 +: mContent(aContent) 1.69 +, mMenuObject(aMenuItem) 1.70 +, mLoadedIcon(false) 1.71 +, mSetIcon(false) 1.72 +, mNativeMenuItem(aNativeMenuItem) 1.73 +{ 1.74 + // printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem); 1.75 +} 1.76 + 1.77 +nsMenuItemIconX::~nsMenuItemIconX() 1.78 +{ 1.79 + if (mIconRequest) 1.80 + mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 1.81 +} 1.82 + 1.83 +// Called from mMenuObjectX's destructor, to prevent us from outliving it 1.84 +// (as might otherwise happen if calls to our imgINotificationObserver methods 1.85 +// are still outstanding). mMenuObjectX owns our nNativeMenuItem. 1.86 +void nsMenuItemIconX::Destroy() 1.87 +{ 1.88 + if (mIconRequest) { 1.89 + mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 1.90 + mIconRequest = nullptr; 1.91 + } 1.92 + mMenuObject = nullptr; 1.93 + mNativeMenuItem = nil; 1.94 +} 1.95 + 1.96 +nsresult 1.97 +nsMenuItemIconX::SetupIcon() 1.98 +{ 1.99 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.100 + 1.101 + // Still don't have one, then something is wrong, get out of here. 1.102 + if (!mNativeMenuItem) { 1.103 + NS_ERROR("No native menu item"); 1.104 + return NS_ERROR_FAILURE; 1.105 + } 1.106 + 1.107 + nsCOMPtr<nsIURI> iconURI; 1.108 + nsresult rv = GetIconURI(getter_AddRefs(iconURI)); 1.109 + if (NS_FAILED(rv)) { 1.110 + // There is no icon for this menu item. An icon might have been set 1.111 + // earlier. Clear it. 1.112 + [mNativeMenuItem setImage:nil]; 1.113 + 1.114 + return NS_OK; 1.115 + } 1.116 + 1.117 + rv = LoadIcon(iconURI); 1.118 + if (NS_FAILED(rv)) { 1.119 + // There is no icon for this menu item, as an error occurred while loading it. 1.120 + // An icon might have been set earlier or the place holder icon may have 1.121 + // been set. Clear it. 1.122 + [mNativeMenuItem setImage:nil]; 1.123 + } 1.124 + return rv; 1.125 + 1.126 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.127 +} 1.128 + 1.129 +static int32_t 1.130 +GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod) 1.131 +{ 1.132 + nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue; 1.133 + (aRect->*aMethod)(getter_AddRefs(dimensionValue)); 1.134 + if (!dimensionValue) 1.135 + return -1; 1.136 + 1.137 + uint16_t primitiveType; 1.138 + nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType); 1.139 + if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX) 1.140 + return -1; 1.141 + 1.142 + float dimension = 0; 1.143 + rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX, 1.144 + &dimension); 1.145 + if (NS_FAILED(rv)) 1.146 + return -1; 1.147 + 1.148 + return NSToIntRound(dimension); 1.149 +} 1.150 + 1.151 +nsresult 1.152 +nsMenuItemIconX::GetIconURI(nsIURI** aIconURI) 1.153 +{ 1.154 + if (!mMenuObject) 1.155 + return NS_ERROR_FAILURE; 1.156 + 1.157 + // Mac native menu items support having both a checkmark and an icon 1.158 + // simultaneously, but this is unheard of in the cross-platform toolkit, 1.159 + // seemingly because the win32 theme is unable to cope with both at once. 1.160 + // The downside is that it's possible to get a menu item marked with a 1.161 + // native checkmark and a checkmark for an icon. Head off that possibility 1.162 + // by pretending that no icon exists if this is a checkable menu item. 1.163 + if (mMenuObject->MenuObjectType() == eMenuItemObjectType) { 1.164 + nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject); 1.165 + if (menuItem->GetMenuItemType() != eRegularMenuItemType) 1.166 + return NS_ERROR_FAILURE; 1.167 + } 1.168 + 1.169 + if (!mContent) 1.170 + return NS_ERROR_FAILURE; 1.171 + 1.172 + // First, look at the content node's "image" attribute. 1.173 + nsAutoString imageURIString; 1.174 + bool hasImageAttr = mContent->GetAttr(kNameSpaceID_None, 1.175 + nsGkAtoms::image, 1.176 + imageURIString); 1.177 + 1.178 + nsresult rv; 1.179 + nsCOMPtr<nsIDOMCSSValue> cssValue; 1.180 + nsCOMPtr<nsIDOMCSSStyleDeclaration> cssStyleDecl; 1.181 + nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue; 1.182 + uint16_t primitiveType; 1.183 + if (!hasImageAttr) { 1.184 + // If the content node has no "image" attribute, get the 1.185 + // "list-style-image" property from CSS. 1.186 + nsCOMPtr<nsIDocument> document = mContent->GetDocument(); 1.187 + if (!document) 1.188 + return NS_ERROR_FAILURE; 1.189 + 1.190 + nsCOMPtr<nsPIDOMWindow> window = document->GetWindow(); 1.191 + if (!window) 1.192 + return NS_ERROR_FAILURE; 1.193 + 1.194 + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(mContent); 1.195 + if (!domElement) 1.196 + return NS_ERROR_FAILURE; 1.197 + 1.198 + 1.199 + rv = window->GetComputedStyle(domElement, EmptyString(), 1.200 + getter_AddRefs(cssStyleDecl)); 1.201 + if (NS_FAILED(rv)) 1.202 + return rv; 1.203 + 1.204 + NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image"); 1.205 + rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage, 1.206 + getter_AddRefs(cssValue)); 1.207 + if (NS_FAILED(rv)) return rv; 1.208 + 1.209 + primitiveValue = do_QueryInterface(cssValue); 1.210 + if (!primitiveValue) return NS_ERROR_FAILURE; 1.211 + 1.212 + rv = primitiveValue->GetPrimitiveType(&primitiveType); 1.213 + if (NS_FAILED(rv)) return rv; 1.214 + if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI) 1.215 + return NS_ERROR_FAILURE; 1.216 + 1.217 + rv = primitiveValue->GetStringValue(imageURIString); 1.218 + if (NS_FAILED(rv)) return rv; 1.219 + } 1.220 + 1.221 + // Empty the mImageRegionRect initially as the image region CSS could 1.222 + // have been changed and now have an error or have been removed since the 1.223 + // last GetIconURI call. 1.224 + mImageRegionRect.SetEmpty(); 1.225 + 1.226 + // If this menu item shouldn't have an icon, the string will be empty, 1.227 + // and NS_NewURI will fail. 1.228 + nsCOMPtr<nsIURI> iconURI; 1.229 + rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString); 1.230 + if (NS_FAILED(rv)) return rv; 1.231 + 1.232 + *aIconURI = iconURI; 1.233 + NS_ADDREF(*aIconURI); 1.234 + 1.235 + if (!hasImageAttr) { 1.236 + // Check if the icon has a specified image region so that it can be 1.237 + // cropped appropriately before being displayed. 1.238 + NS_NAMED_LITERAL_STRING(imageRegion, "-moz-image-region"); 1.239 + rv = cssStyleDecl->GetPropertyCSSValue(imageRegion, 1.240 + getter_AddRefs(cssValue)); 1.241 + // Just return NS_OK if there if there is a failure due to no 1.242 + // moz-image region specified so the whole icon will be drawn anyway. 1.243 + if (NS_FAILED(rv)) return NS_OK; 1.244 + 1.245 + primitiveValue = do_QueryInterface(cssValue); 1.246 + if (!primitiveValue) return NS_OK; 1.247 + 1.248 + rv = primitiveValue->GetPrimitiveType(&primitiveType); 1.249 + if (NS_FAILED(rv)) return NS_OK; 1.250 + if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_RECT) 1.251 + return NS_OK; 1.252 + 1.253 + nsCOMPtr<nsIDOMRect> imageRegionRect; 1.254 + rv = primitiveValue->GetRectValue(getter_AddRefs(imageRegionRect)); 1.255 + if (NS_FAILED(rv)) return NS_OK; 1.256 + 1.257 + if (imageRegionRect) { 1.258 + // Return NS_ERROR_FAILURE if the image region is invalid so the image 1.259 + // is not drawn, and behavior is similar to XUL menus. 1.260 + int32_t bottom = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetBottom); 1.261 + int32_t right = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetRight); 1.262 + int32_t top = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetTop); 1.263 + int32_t left = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetLeft); 1.264 + 1.265 + if (top < 0 || left < 0 || bottom <= top || right <= left) 1.266 + return NS_ERROR_FAILURE; 1.267 + 1.268 + mImageRegionRect.SetRect(left, top, right - left, bottom - top); 1.269 + } 1.270 + } 1.271 + 1.272 + return NS_OK; 1.273 +} 1.274 + 1.275 +nsresult 1.276 +nsMenuItemIconX::LoadIcon(nsIURI* aIconURI) 1.277 +{ 1.278 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.279 + 1.280 + if (mIconRequest) { 1.281 + // Another icon request is already in flight. Kill it. 1.282 + mIconRequest->Cancel(NS_BINDING_ABORTED); 1.283 + mIconRequest = nullptr; 1.284 + } 1.285 + 1.286 + mLoadedIcon = false; 1.287 + 1.288 + if (!mContent) return NS_ERROR_FAILURE; 1.289 + 1.290 + nsCOMPtr<nsIDocument> document = mContent->OwnerDoc(); 1.291 + 1.292 + nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup(); 1.293 + if (!loadGroup) return NS_ERROR_FAILURE; 1.294 + 1.295 + nsRefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document); 1.296 + if (!loader) return NS_ERROR_FAILURE; 1.297 + 1.298 + if (!mSetIcon) { 1.299 + // Set a completely transparent 16x16 image as the icon on this menu item 1.300 + // as a placeholder. This keeps the menu item text displayed in the same 1.301 + // position that it will be displayed when the real icon is loaded, and 1.302 + // prevents it from jumping around or looking misaligned. 1.303 + 1.304 + static bool sInitializedPlaceholder; 1.305 + static NSImage* sPlaceholderIconImage; 1.306 + if (!sInitializedPlaceholder) { 1.307 + sInitializedPlaceholder = true; 1.308 + 1.309 + // Note that we only create the one and reuse it forever, so this is not a leak. 1.310 + sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)]; 1.311 + } 1.312 + 1.313 + if (!sPlaceholderIconImage) return NS_ERROR_FAILURE; 1.314 + 1.315 + if (mNativeMenuItem) 1.316 + [mNativeMenuItem setImage:sPlaceholderIconImage]; 1.317 + } 1.318 + 1.319 + nsCOMPtr<nsIURI> firstPartyIsolationURI; 1.320 + nsCOMPtr<mozIThirdPartyUtil> thirdPartySvc 1.321 + = do_GetService(THIRDPARTYUTIL_CONTRACTID); 1.322 + thirdPartySvc->GetFirstPartyURI(nullptr, document, 1.323 + getter_AddRefs(firstPartyIsolationURI)); 1.324 + 1.325 + // Passing in null for channelPolicy here since nsMenuItemIconX::LoadIcon is 1.326 + // not exposed to web content 1.327 + nsresult rv = loader->LoadImage(aIconURI, firstPartyIsolationURI, nullptr, nullptr, loadGroup, this, 1.328 + nullptr, nsIRequest::LOAD_NORMAL, nullptr, 1.329 + nullptr, EmptyString(), getter_AddRefs(mIconRequest)); 1.330 + if (NS_FAILED(rv)) return rv; 1.331 + 1.332 + // We need to request the icon be decoded (bug 573583, bug 705516). 1.333 + mIconRequest->StartDecoding(); 1.334 + 1.335 + return NS_OK; 1.336 + 1.337 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.338 +} 1.339 + 1.340 +// 1.341 +// imgINotificationObserver 1.342 +// 1.343 + 1.344 +NS_IMETHODIMP 1.345 +nsMenuItemIconX::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) 1.346 +{ 1.347 + if (aType == imgINotificationObserver::FRAME_COMPLETE) { 1.348 + return OnStopFrame(aRequest); 1.349 + } 1.350 + 1.351 + if (aType == imgINotificationObserver::DECODE_COMPLETE) { 1.352 + if (mIconRequest && mIconRequest == aRequest) { 1.353 + mIconRequest->Cancel(NS_BINDING_ABORTED); 1.354 + mIconRequest = nullptr; 1.355 + } 1.356 + } 1.357 + 1.358 + return NS_OK; 1.359 +} 1.360 + 1.361 +nsresult 1.362 +nsMenuItemIconX::OnStopFrame(imgIRequest* aRequest) 1.363 +{ 1.364 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; 1.365 + 1.366 + if (aRequest != mIconRequest) 1.367 + return NS_ERROR_FAILURE; 1.368 + 1.369 + // Only support one frame. 1.370 + if (mLoadedIcon) 1.371 + return NS_OK; 1.372 + 1.373 + if (!mNativeMenuItem) 1.374 + return NS_ERROR_FAILURE; 1.375 + 1.376 + nsCOMPtr<imgIContainer> imageContainer; 1.377 + aRequest->GetImage(getter_AddRefs(imageContainer)); 1.378 + if (!imageContainer) { 1.379 + [mNativeMenuItem setImage:nil]; 1.380 + return NS_ERROR_FAILURE; 1.381 + } 1.382 + 1.383 + int32_t origWidth = 0, origHeight = 0; 1.384 + imageContainer->GetWidth(&origWidth); 1.385 + imageContainer->GetHeight(&origHeight); 1.386 + 1.387 + // If the image region is invalid, don't draw the image to almost match 1.388 + // the behavior of other platforms. 1.389 + if (!mImageRegionRect.IsEmpty() && 1.390 + (mImageRegionRect.XMost() > origWidth || 1.391 + mImageRegionRect.YMost() > origHeight)) { 1.392 + [mNativeMenuItem setImage:nil]; 1.393 + return NS_ERROR_FAILURE; 1.394 + } 1.395 + 1.396 + if (mImageRegionRect.IsEmpty()) { 1.397 + mImageRegionRect.SetRect(0, 0, origWidth, origHeight); 1.398 + } 1.399 + 1.400 + RefPtr<SourceSurface> surface = 1.401 + imageContainer->GetFrame(imgIContainer::FRAME_CURRENT, 1.402 + imgIContainer::FLAG_NONE); 1.403 + if (!surface) { 1.404 + [mNativeMenuItem setImage:nil]; 1.405 + return NS_ERROR_FAILURE; 1.406 + } 1.407 + 1.408 + CGImageRef origImage = NULL; 1.409 + nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage); 1.410 + if (NS_FAILED(rv) || !origImage) { 1.411 + [mNativeMenuItem setImage:nil]; 1.412 + return NS_ERROR_FAILURE; 1.413 + } 1.414 + 1.415 + bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 && 1.416 + mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight); 1.417 + 1.418 + CGImageRef finalImage = NULL; 1.419 + if (createSubImage) { 1.420 + // if mImageRegionRect is set using CSS, we need to slice a piece out of the overall 1.421 + // image to use as the icon 1.422 + finalImage = ::CGImageCreateWithImageInRect(origImage, 1.423 + ::CGRectMake(mImageRegionRect.x, 1.424 + mImageRegionRect.y, 1.425 + mImageRegionRect.width, 1.426 + mImageRegionRect.height)); 1.427 + ::CGImageRelease(origImage); 1.428 + if (!finalImage) { 1.429 + [mNativeMenuItem setImage:nil]; 1.430 + return NS_ERROR_FAILURE; 1.431 + } 1.432 + } else { 1.433 + finalImage = origImage; 1.434 + } 1.435 + // The image may not be the right size for a menu icon (16x16). 1.436 + // Create a new CGImage for the menu item. 1.437 + uint8_t* bitmap = (uint8_t*)malloc(kIconBytes); 1.438 + 1.439 + CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB(); 1.440 + 1.441 + CGContextRef bitmapContext = ::CGBitmapContextCreate(bitmap, kIconWidth, kIconHeight, 1.442 + kIconBitsPerComponent, 1.443 + kIconBytesPerRow, 1.444 + colorSpace, 1.445 + kCGImageAlphaPremultipliedLast); 1.446 + ::CGColorSpaceRelease(colorSpace); 1.447 + if (!bitmapContext) { 1.448 + ::CGImageRelease(finalImage); 1.449 + free(bitmap); 1.450 + ::CGColorSpaceRelease(colorSpace); 1.451 + return NS_ERROR_FAILURE; 1.452 + } 1.453 + CGRect iconRect = ::CGRectMake(0, 0, kIconWidth, kIconHeight); 1.454 + ::CGContextClearRect(bitmapContext, iconRect); 1.455 + ::CGContextDrawImage(bitmapContext, iconRect, finalImage); 1.456 + 1.457 + CGImageRef iconImage = ::CGBitmapContextCreateImage(bitmapContext); 1.458 + 1.459 + ::CGImageRelease(finalImage); 1.460 + ::CGContextRelease(bitmapContext); 1.461 + free(bitmap); 1.462 + 1.463 + if (!iconImage) return NS_ERROR_FAILURE; 1.464 + 1.465 + NSImage *newImage = nil; 1.466 + rv = nsCocoaUtils::CreateNSImageFromCGImage(iconImage, &newImage); 1.467 + if (NS_FAILED(rv) || !newImage) { 1.468 + [mNativeMenuItem setImage:nil]; 1.469 + ::CGImageRelease(iconImage); 1.470 + return NS_ERROR_FAILURE; 1.471 + } 1.472 + 1.473 + [mNativeMenuItem setImage:newImage]; 1.474 + 1.475 + [newImage release]; 1.476 + ::CGImageRelease(iconImage); 1.477 + 1.478 + mLoadedIcon = true; 1.479 + mSetIcon = true; 1.480 + 1.481 + return NS_OK; 1.482 + 1.483 + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; 1.484 +}