widget/cocoa/nsMenuItemIconX.mm

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     6 /*
     7  * Retrieves and displays icons in native menu items on Mac OS X.
     8  */
    10 /* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
    11    exceptions and produces errors like: error: unexpected '@' in program'.
    12    If we define __EXCEPTIONS exception_defines.h will avoid doing this.
    14    See bug 666609 for more information.
    16    We use <limits> to get the libstdc++ version. */
    17 #include <limits>
    18 #if __GLIBCXX__ <= 20070719
    19 #define __EXCEPTIONS
    20 #endif
    22 #include "nsMenuItemIconX.h"
    23 #include "nsObjCExceptions.h"
    24 #include "nsIContent.h"
    25 #include "nsIDocument.h"
    26 #include "nsNameSpaceManager.h"
    27 #include "nsGkAtoms.h"
    28 #include "nsIDOMElement.h"
    29 #include "nsIDOMCSSStyleDeclaration.h"
    30 #include "nsIDOMCSSValue.h"
    31 #include "nsIDOMCSSPrimitiveValue.h"
    32 #include "nsIDOMRect.h"
    33 #include "nsThreadUtils.h"
    34 #include "nsToolkit.h"
    35 #include "nsNetUtil.h"
    36 #include "imgLoader.h"
    37 #include "imgRequestProxy.h"
    38 #include "nsMenuItemX.h"
    39 #include "gfxPlatform.h"
    40 #include "imgIContainer.h"
    41 #include "nsCocoaUtils.h"
    42 #include "mozIThirdPartyUtil.h"
    43 #include "nsContentUtils.h"
    45 using mozilla::gfx::SourceSurface;
    46 using mozilla::RefPtr;
    48 static const uint32_t kIconWidth = 16;
    49 static const uint32_t kIconHeight = 16;
    50 static const uint32_t kIconBitsPerComponent = 8;
    51 static const uint32_t kIconComponents = 4;
    52 static const uint32_t kIconBitsPerPixel = kIconBitsPerComponent *
    53                                           kIconComponents;
    54 static const uint32_t kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8;
    55 static const uint32_t kIconBytes = kIconBytesPerRow * kIconHeight;
    57 typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect,
    58                              GetBottom, (nsIDOMCSSPrimitiveValue**));
    60 NS_IMPL_ISUPPORTS(nsMenuItemIconX, imgINotificationObserver)
    62 nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
    63                                  nsIContent*    aContent,
    64                                  NSMenuItem*    aNativeMenuItem)
    65 : mContent(aContent)
    66 , mMenuObject(aMenuItem)
    67 , mLoadedIcon(false)
    68 , mSetIcon(false)
    69 , mNativeMenuItem(aNativeMenuItem)
    70 {
    71   //  printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem);
    72 }
    74 nsMenuItemIconX::~nsMenuItemIconX()
    75 {
    76   if (mIconRequest)
    77     mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
    78 }
    80 // Called from mMenuObjectX's destructor, to prevent us from outliving it
    81 // (as might otherwise happen if calls to our imgINotificationObserver methods
    82 // are still outstanding).  mMenuObjectX owns our nNativeMenuItem.
    83 void nsMenuItemIconX::Destroy()
    84 {
    85   if (mIconRequest) {
    86     mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
    87     mIconRequest = nullptr;
    88   }
    89   mMenuObject = nullptr;
    90   mNativeMenuItem = nil;
    91 }
    93 nsresult
    94 nsMenuItemIconX::SetupIcon()
    95 {
    96   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
    98   // Still don't have one, then something is wrong, get out of here.
    99   if (!mNativeMenuItem) {
   100     NS_ERROR("No native menu item");
   101     return NS_ERROR_FAILURE;
   102   }
   104   nsCOMPtr<nsIURI> iconURI;
   105   nsresult rv = GetIconURI(getter_AddRefs(iconURI));
   106   if (NS_FAILED(rv)) {
   107     // There is no icon for this menu item. An icon might have been set
   108     // earlier.  Clear it.
   109     [mNativeMenuItem setImage:nil];
   111     return NS_OK;
   112   }
   114   rv = LoadIcon(iconURI);
   115   if (NS_FAILED(rv)) {
   116     // There is no icon for this menu item, as an error occurred while loading it.
   117     // An icon might have been set earlier or the place holder icon may have
   118     // been set.  Clear it.
   119     [mNativeMenuItem setImage:nil];
   120   }
   121   return rv;
   123   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   124 }
   126 static int32_t
   127 GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod)
   128 {
   129   nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue;
   130   (aRect->*aMethod)(getter_AddRefs(dimensionValue));
   131   if (!dimensionValue)
   132     return -1;
   134   uint16_t primitiveType;
   135   nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType);
   136   if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX)
   137     return -1;
   139   float dimension = 0;
   140   rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX,
   141                                      &dimension);
   142   if (NS_FAILED(rv))
   143     return -1;
   145   return NSToIntRound(dimension);
   146 }
   148 nsresult
   149 nsMenuItemIconX::GetIconURI(nsIURI** aIconURI)
   150 {
   151   if (!mMenuObject)
   152     return NS_ERROR_FAILURE;
   154   // Mac native menu items support having both a checkmark and an icon
   155   // simultaneously, but this is unheard of in the cross-platform toolkit,
   156   // seemingly because the win32 theme is unable to cope with both at once.
   157   // The downside is that it's possible to get a menu item marked with a
   158   // native checkmark and a checkmark for an icon.  Head off that possibility
   159   // by pretending that no icon exists if this is a checkable menu item.
   160   if (mMenuObject->MenuObjectType() == eMenuItemObjectType) {
   161     nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject);
   162     if (menuItem->GetMenuItemType() != eRegularMenuItemType)
   163       return NS_ERROR_FAILURE;
   164   }
   166   if (!mContent)
   167     return NS_ERROR_FAILURE;
   169   // First, look at the content node's "image" attribute.
   170   nsAutoString imageURIString;
   171   bool hasImageAttr = mContent->GetAttr(kNameSpaceID_None,
   172                                         nsGkAtoms::image,
   173                                         imageURIString);
   175   nsresult rv;
   176   nsCOMPtr<nsIDOMCSSValue> cssValue;
   177   nsCOMPtr<nsIDOMCSSStyleDeclaration> cssStyleDecl;
   178   nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
   179   uint16_t primitiveType;
   180   if (!hasImageAttr) {
   181     // If the content node has no "image" attribute, get the
   182     // "list-style-image" property from CSS.
   183     nsCOMPtr<nsIDocument> document = mContent->GetDocument();
   184     if (!document)
   185       return NS_ERROR_FAILURE;
   187     nsCOMPtr<nsPIDOMWindow> window = document->GetWindow();
   188     if (!window)
   189       return NS_ERROR_FAILURE;
   191     nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(mContent);
   192     if (!domElement)
   193       return NS_ERROR_FAILURE;
   196     rv = window->GetComputedStyle(domElement, EmptyString(),
   197                                   getter_AddRefs(cssStyleDecl));
   198     if (NS_FAILED(rv))
   199       return rv;
   201     NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image");
   202     rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage,
   203                                            getter_AddRefs(cssValue));
   204     if (NS_FAILED(rv)) return rv;
   206     primitiveValue = do_QueryInterface(cssValue);
   207     if (!primitiveValue) return NS_ERROR_FAILURE;
   209     rv = primitiveValue->GetPrimitiveType(&primitiveType);
   210     if (NS_FAILED(rv)) return rv;
   211     if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI)
   212       return NS_ERROR_FAILURE;
   214     rv = primitiveValue->GetStringValue(imageURIString);
   215     if (NS_FAILED(rv)) return rv;
   216   }
   218   // Empty the mImageRegionRect initially as the image region CSS could
   219   // have been changed and now have an error or have been removed since the
   220   // last GetIconURI call.
   221   mImageRegionRect.SetEmpty();
   223   // If this menu item shouldn't have an icon, the string will be empty,
   224   // and NS_NewURI will fail.
   225   nsCOMPtr<nsIURI> iconURI;
   226   rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
   227   if (NS_FAILED(rv)) return rv;
   229   *aIconURI = iconURI;
   230   NS_ADDREF(*aIconURI);
   232   if (!hasImageAttr) {
   233     // Check if the icon has a specified image region so that it can be
   234     // cropped appropriately before being displayed.
   235     NS_NAMED_LITERAL_STRING(imageRegion, "-moz-image-region");
   236     rv = cssStyleDecl->GetPropertyCSSValue(imageRegion,
   237                                            getter_AddRefs(cssValue));
   238     // Just return NS_OK if there if there is a failure due to no
   239     // moz-image region specified so the whole icon will be drawn anyway.
   240     if (NS_FAILED(rv)) return NS_OK;
   242     primitiveValue = do_QueryInterface(cssValue);
   243     if (!primitiveValue) return NS_OK;
   245     rv = primitiveValue->GetPrimitiveType(&primitiveType);
   246     if (NS_FAILED(rv)) return NS_OK;
   247     if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_RECT)
   248       return NS_OK;
   250     nsCOMPtr<nsIDOMRect> imageRegionRect;
   251     rv = primitiveValue->GetRectValue(getter_AddRefs(imageRegionRect));
   252     if (NS_FAILED(rv)) return NS_OK;
   254     if (imageRegionRect) {
   255       // Return NS_ERROR_FAILURE if the image region is invalid so the image
   256       // is not drawn, and behavior is similar to XUL menus.
   257       int32_t bottom = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetBottom);
   258       int32_t right = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetRight);
   259       int32_t top = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetTop);
   260       int32_t left = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetLeft);
   262       if (top < 0 || left < 0 || bottom <= top || right <= left)
   263         return NS_ERROR_FAILURE;
   265       mImageRegionRect.SetRect(left, top, right - left, bottom - top);
   266     }
   267   }
   269   return NS_OK;
   270 }
   272 nsresult
   273 nsMenuItemIconX::LoadIcon(nsIURI* aIconURI)
   274 {
   275   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   277   if (mIconRequest) {
   278     // Another icon request is already in flight.  Kill it.
   279     mIconRequest->Cancel(NS_BINDING_ABORTED);
   280     mIconRequest = nullptr;
   281   }
   283   mLoadedIcon = false;
   285   if (!mContent) return NS_ERROR_FAILURE;
   287   nsCOMPtr<nsIDocument> document = mContent->OwnerDoc();
   289   nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
   290   if (!loadGroup) return NS_ERROR_FAILURE;
   292   nsRefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
   293   if (!loader) return NS_ERROR_FAILURE;
   295   if (!mSetIcon) {
   296     // Set a completely transparent 16x16 image as the icon on this menu item
   297     // as a placeholder.  This keeps the menu item text displayed in the same
   298     // position that it will be displayed when the real icon is loaded, and
   299     // prevents it from jumping around or looking misaligned.
   301     static bool sInitializedPlaceholder;
   302     static NSImage* sPlaceholderIconImage;
   303     if (!sInitializedPlaceholder) {
   304       sInitializedPlaceholder = true;
   306       // Note that we only create the one and reuse it forever, so this is not a leak.
   307       sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)];
   308     }
   310     if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
   312     if (mNativeMenuItem)
   313       [mNativeMenuItem setImage:sPlaceholderIconImage];
   314   }
   316   nsCOMPtr<nsIURI> firstPartyIsolationURI;
   317   nsCOMPtr<mozIThirdPartyUtil> thirdPartySvc
   318                                = do_GetService(THIRDPARTYUTIL_CONTRACTID);
   319   thirdPartySvc->GetFirstPartyURI(nullptr, document,
   320                                   getter_AddRefs(firstPartyIsolationURI));
   322   // Passing in null for channelPolicy here since nsMenuItemIconX::LoadIcon is
   323   // not exposed to web content
   324   nsresult rv = loader->LoadImage(aIconURI, firstPartyIsolationURI, nullptr, nullptr, loadGroup, this,
   325                                    nullptr, nsIRequest::LOAD_NORMAL, nullptr,
   326                                    nullptr, EmptyString(), getter_AddRefs(mIconRequest));
   327   if (NS_FAILED(rv)) return rv;
   329   // We need to request the icon be decoded (bug 573583, bug 705516).
   330   mIconRequest->StartDecoding();
   332   return NS_OK;
   334   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   335 }
   337 //
   338 // imgINotificationObserver
   339 //
   341 NS_IMETHODIMP
   342 nsMenuItemIconX::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
   343 {
   344   if (aType == imgINotificationObserver::FRAME_COMPLETE) {
   345     return OnStopFrame(aRequest);
   346   }
   348   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
   349     if (mIconRequest && mIconRequest == aRequest) {
   350       mIconRequest->Cancel(NS_BINDING_ABORTED);
   351       mIconRequest = nullptr;
   352     }
   353   }
   355   return NS_OK;
   356 }
   358 nsresult
   359 nsMenuItemIconX::OnStopFrame(imgIRequest*    aRequest)
   360 {
   361   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   363   if (aRequest != mIconRequest)
   364     return NS_ERROR_FAILURE;
   366   // Only support one frame.
   367   if (mLoadedIcon)
   368     return NS_OK;
   370   if (!mNativeMenuItem)
   371     return NS_ERROR_FAILURE;
   373   nsCOMPtr<imgIContainer> imageContainer;
   374   aRequest->GetImage(getter_AddRefs(imageContainer));
   375   if (!imageContainer) {
   376     [mNativeMenuItem setImage:nil];
   377     return NS_ERROR_FAILURE;
   378   }
   380   int32_t origWidth = 0, origHeight = 0;
   381   imageContainer->GetWidth(&origWidth);
   382   imageContainer->GetHeight(&origHeight);
   384   // If the image region is invalid, don't draw the image to almost match
   385   // the behavior of other platforms.
   386   if (!mImageRegionRect.IsEmpty() &&
   387       (mImageRegionRect.XMost() > origWidth ||
   388        mImageRegionRect.YMost() > origHeight)) {
   389     [mNativeMenuItem setImage:nil];
   390     return NS_ERROR_FAILURE;
   391   }
   393   if (mImageRegionRect.IsEmpty()) {
   394     mImageRegionRect.SetRect(0, 0, origWidth, origHeight);
   395   }
   397   RefPtr<SourceSurface> surface =
   398     imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
   399                              imgIContainer::FLAG_NONE);
   400   if (!surface) {
   401     [mNativeMenuItem setImage:nil];
   402     return NS_ERROR_FAILURE;
   403   }
   405   CGImageRef origImage = NULL;
   406   nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage);
   407   if (NS_FAILED(rv) || !origImage) {
   408     [mNativeMenuItem setImage:nil];
   409     return NS_ERROR_FAILURE;
   410   }
   412   bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 &&
   413                             mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight);
   415   CGImageRef finalImage = NULL;
   416   if (createSubImage) {
   417     // if mImageRegionRect is set using CSS, we need to slice a piece out of the overall 
   418     // image to use as the icon
   419     finalImage = ::CGImageCreateWithImageInRect(origImage, 
   420                                                 ::CGRectMake(mImageRegionRect.x, 
   421                                                 mImageRegionRect.y,
   422                                                 mImageRegionRect.width,
   423                                                 mImageRegionRect.height));
   424     ::CGImageRelease(origImage);
   425     if (!finalImage) {
   426       [mNativeMenuItem setImage:nil];
   427       return NS_ERROR_FAILURE;  
   428     }
   429   } else {
   430     finalImage = origImage;
   431   }
   432   // The image may not be the right size for a menu icon (16x16).
   433   // Create a new CGImage for the menu item.
   434   uint8_t* bitmap = (uint8_t*)malloc(kIconBytes);
   436   CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB();
   438   CGContextRef bitmapContext = ::CGBitmapContextCreate(bitmap, kIconWidth, kIconHeight,
   439                                                        kIconBitsPerComponent,
   440                                                        kIconBytesPerRow,
   441                                                        colorSpace,
   442                                                        kCGImageAlphaPremultipliedLast);
   443   ::CGColorSpaceRelease(colorSpace);
   444   if (!bitmapContext) {
   445     ::CGImageRelease(finalImage);
   446     free(bitmap);
   447     ::CGColorSpaceRelease(colorSpace);
   448     return NS_ERROR_FAILURE;
   449   }
   450   CGRect iconRect = ::CGRectMake(0, 0, kIconWidth, kIconHeight);
   451   ::CGContextClearRect(bitmapContext, iconRect);
   452   ::CGContextDrawImage(bitmapContext, iconRect, finalImage);
   454   CGImageRef iconImage = ::CGBitmapContextCreateImage(bitmapContext);
   456   ::CGImageRelease(finalImage);
   457   ::CGContextRelease(bitmapContext);
   458   free(bitmap);
   460   if (!iconImage) return NS_ERROR_FAILURE;
   462   NSImage *newImage = nil;
   463   rv = nsCocoaUtils::CreateNSImageFromCGImage(iconImage, &newImage);
   464   if (NS_FAILED(rv) || !newImage) {    
   465     [mNativeMenuItem setImage:nil];
   466     ::CGImageRelease(iconImage);
   467     return NS_ERROR_FAILURE;
   468   }
   470   [mNativeMenuItem setImage:newImage];
   472   [newImage release];
   473   ::CGImageRelease(iconImage);
   475   mLoadedIcon = true;
   476   mSetIcon = true;
   478   return NS_OK;
   480   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   481 }

mercurial