LCOV - code coverage report
Current view: top level - image/decoders/icon/gtk - nsIconChannel.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 194 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 11 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* vim:set ts=2 sw=2 sts=2 cin et: */
       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             : #include "nsIconChannel.h"
       7             : 
       8             : #include <stdlib.h>
       9             : #include <unistd.h>
      10             : 
      11             : #include "mozilla/DebugOnly.h"
      12             : #include "mozilla/EndianUtils.h"
      13             : #include <algorithm>
      14             : 
      15             : #include <gio/gio.h>
      16             : 
      17             : #include <gtk/gtk.h>
      18             : 
      19             : #include "nsMimeTypes.h"
      20             : #include "nsIMIMEService.h"
      21             : 
      22             : #include "nsServiceManagerUtils.h"
      23             : 
      24             : #include "nsNetUtil.h"
      25             : #include "nsComponentManagerUtils.h"
      26             : #include "nsIStringStream.h"
      27             : #include "nsServiceManagerUtils.h"
      28             : #include "NullPrincipal.h"
      29             : #include "nsIURL.h"
      30             : #include "prlink.h"
      31             : #include "gfxPlatform.h"
      32             : 
      33           0 : NS_IMPL_ISUPPORTS(nsIconChannel,
      34             :                   nsIRequest,
      35             :                   nsIChannel)
      36             : 
      37             : static nsresult
      38           0 : moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
      39             :                           nsIChannel** aChannel)
      40             : {
      41           0 :   int width = gdk_pixbuf_get_width(aPixbuf);
      42           0 :   int height = gdk_pixbuf_get_height(aPixbuf);
      43           0 :   NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 &&
      44             :                  gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB &&
      45             :                  gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 &&
      46             :                  gdk_pixbuf_get_has_alpha(aPixbuf) &&
      47             :                  gdk_pixbuf_get_n_channels(aPixbuf) == 4,
      48             :                  NS_ERROR_UNEXPECTED);
      49             : 
      50           0 :   const int n_channels = 4;
      51           0 :   gsize buf_size = 2 + n_channels * height * width;
      52           0 :   uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
      53           0 :   NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
      54           0 :   uint8_t* out = buf;
      55             : 
      56           0 :   *(out++) = width;
      57           0 :   *(out++) = height;
      58             : 
      59           0 :   const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
      60           0 :   int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels;
      61             : 
      62             :   // encode the RGB data and the A data
      63           0 :   const guchar* in = pixels;
      64           0 :   for (int y = 0; y < height; ++y, in += rowextra) {
      65           0 :     for (int x = 0; x < width; ++x) {
      66           0 :       uint8_t r = *(in++);
      67           0 :       uint8_t g = *(in++);
      68           0 :       uint8_t b = *(in++);
      69           0 :       uint8_t a = *(in++);
      70             : #define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
      71             : #if MOZ_LITTLE_ENDIAN
      72           0 :       *(out++) = DO_PREMULTIPLY(b);
      73           0 :       *(out++) = DO_PREMULTIPLY(g);
      74           0 :       *(out++) = DO_PREMULTIPLY(r);
      75           0 :       *(out++) = a;
      76             : #else
      77             :       *(out++) = a;
      78             :       *(out++) = DO_PREMULTIPLY(r);
      79             :       *(out++) = DO_PREMULTIPLY(g);
      80             :       *(out++) = DO_PREMULTIPLY(b);
      81             : #endif
      82             : #undef DO_PREMULTIPLY
      83             :     }
      84             :   }
      85             : 
      86           0 :   NS_ASSERTION(out == buf + buf_size, "size miscalculation");
      87             : 
      88             :   nsresult rv;
      89             :   nsCOMPtr<nsIStringInputStream> stream =
      90           0 :     do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
      91             : 
      92             :   // Prevent the leaking of buf
      93           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      94           0 :     free(buf);
      95           0 :     return rv;
      96             :   }
      97             : 
      98             :   // stream takes ownership of buf and will free it on destruction.
      99             :   // This function cannot fail.
     100           0 :   rv = stream->AdoptData((char*)buf, buf_size);
     101             : 
     102             :   // If this no longer holds then re-examine buf's lifetime.
     103           0 :   MOZ_ASSERT(NS_SUCCEEDED(rv));
     104           0 :   NS_ENSURE_SUCCESS(rv, rv);
     105             : 
     106             :   // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
     107             :   // this iconChannel. Use the most restrictive security settings for the
     108             :   // temporary loadInfo to make sure the channel can not be openend.
     109           0 :   nsCOMPtr<nsIPrincipal> nullPrincipal = NullPrincipal::Create();
     110           0 :   return NS_NewInputStreamChannel(aChannel,
     111             :                                   aURI,
     112             :                                   stream,
     113             :                                   nullPrincipal,
     114             :                                   nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
     115             :                                   nsIContentPolicy::TYPE_INTERNAL_IMAGE,
     116           0 :                                   NS_LITERAL_CSTRING(IMAGE_ICON_MS));
     117             : }
     118             : 
     119             : static GtkWidget* gProtoWindow = nullptr;
     120             : static GtkWidget* gStockImageWidget = nullptr;
     121             : 
     122             : static void
     123           0 : ensure_stock_image_widget()
     124             : {
     125             :   // Only the style of the GtkImage needs to be used, but the widget is kept
     126             :   // to track dynamic style changes.
     127           0 :   if (!gProtoWindow) {
     128           0 :     gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
     129           0 :     GtkWidget* protoLayout = gtk_fixed_new();
     130           0 :     gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout);
     131             : 
     132           0 :     gStockImageWidget = gtk_image_new();
     133           0 :     gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget);
     134             : 
     135           0 :     gtk_widget_ensure_style(gStockImageWidget);
     136             :   }
     137           0 : }
     138             : 
     139             : static GtkIconSize
     140           0 : moz_gtk_icon_size(const char* name)
     141             : {
     142           0 :   if (strcmp(name, "button") == 0) {
     143           0 :     return GTK_ICON_SIZE_BUTTON;
     144             :   }
     145             : 
     146           0 :   if (strcmp(name, "menu") == 0) {
     147           0 :     return GTK_ICON_SIZE_MENU;
     148             :   }
     149             : 
     150           0 :   if (strcmp(name, "toolbar") == 0) {
     151           0 :     return GTK_ICON_SIZE_LARGE_TOOLBAR;
     152             :   }
     153             : 
     154           0 :   if (strcmp(name, "toolbarsmall") == 0) {
     155           0 :     return GTK_ICON_SIZE_SMALL_TOOLBAR;
     156             :   }
     157             : 
     158           0 :   if (strcmp(name, "dnd") == 0) {
     159           0 :     return GTK_ICON_SIZE_DND;
     160             :   }
     161             : 
     162           0 :   if (strcmp(name, "dialog") == 0) {
     163           0 :     return GTK_ICON_SIZE_DIALOG;
     164             :   }
     165             : 
     166           0 :   return GTK_ICON_SIZE_MENU;
     167             : }
     168             : 
     169             : static int32_t
     170           0 : GetIconSize(nsIMozIconURI* aIconURI)
     171             : {
     172           0 :   nsAutoCString iconSizeString;
     173             : 
     174           0 :   aIconURI->GetIconSize(iconSizeString);
     175           0 :   if (iconSizeString.IsEmpty()) {
     176             :     uint32_t size;
     177           0 :     mozilla::DebugOnly<nsresult> rv = aIconURI->GetImageSize(&size);
     178           0 :     NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed");
     179           0 :     return size;
     180             :   }
     181             :   int size;
     182             : 
     183           0 :   GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
     184           0 :   gtk_icon_size_lookup(icon_size, &size, nullptr);
     185           0 :   return size;
     186             : }
     187             : 
     188             : /* Scale icon buffer to preferred size */
     189             : static nsresult
     190           0 : ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize)
     191             : {
     192             :   // Scale buffer only if width or height differ from preferred size
     193           0 :   if (gdk_pixbuf_get_width(*aBuf)  != iconSize &&
     194           0 :       gdk_pixbuf_get_height(*aBuf) != iconSize) {
     195           0 :     GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize,
     196           0 :                                                 GDK_INTERP_BILINEAR);
     197             :     // replace original buffer by scaled
     198           0 :     g_object_unref(*aBuf);
     199           0 :     *aBuf = scaled;
     200           0 :     if (!scaled) {
     201           0 :       return NS_ERROR_OUT_OF_MEMORY;
     202             :     }
     203             :   }
     204           0 :   return NS_OK;
     205             : }
     206             : 
     207             : nsresult
     208           0 : nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
     209             : {
     210           0 :   GIcon *icon = nullptr;
     211           0 :   nsCOMPtr<nsIURL> fileURI;
     212             : 
     213             :   // Read icon content
     214           0 :   aIconURI->GetIconURL(getter_AddRefs(fileURI));
     215             : 
     216             :   // Get icon for file specified by URI
     217           0 :   if (fileURI) {
     218             :     bool isFile;
     219           0 :     nsAutoCString spec;
     220           0 :     fileURI->GetAsciiSpec(spec);
     221           0 :     if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) {
     222           0 :       GFile* file = g_file_new_for_uri(spec.get());
     223             :       GFileInfo* fileInfo = g_file_query_info(file,
     224             :                                               G_FILE_ATTRIBUTE_STANDARD_ICON,
     225             :                                               G_FILE_QUERY_INFO_NONE,
     226           0 :                                               nullptr, nullptr);
     227           0 :       g_object_unref(file);
     228           0 :       if (fileInfo) {
     229             :         // icon from g_content_type_get_icon doesn't need unref
     230           0 :         icon = g_file_info_get_icon(fileInfo);
     231           0 :         if (icon) {
     232           0 :           g_object_ref(icon);
     233             :         }
     234           0 :         g_object_unref(fileInfo);
     235             :       }
     236             :     }
     237             :   }
     238             : 
     239             :   // Try to get icon by using MIME type
     240           0 :   if (!icon) {
     241           0 :     nsAutoCString type;
     242           0 :     aIconURI->GetContentType(type);
     243             :     // Try to get MIME type from file extension by using nsIMIMEService
     244           0 :     if (type.IsEmpty()) {
     245           0 :       nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1"));
     246           0 :       if (ms) {
     247           0 :         nsAutoCString fileExt;
     248           0 :         aIconURI->GetFileExtension(fileExt);
     249           0 :         ms->GetTypeFromExtension(fileExt, type);
     250             :       }
     251             :     }
     252           0 :     char* ctype = nullptr; // character representation of content type
     253           0 :     if (!type.IsEmpty()) {
     254           0 :       ctype = g_content_type_from_mime_type(type.get());
     255             :     }
     256           0 :     if (ctype) {
     257           0 :       icon = g_content_type_get_icon(ctype);
     258           0 :       g_free(ctype);
     259             :     }
     260             :   }
     261             : 
     262             :   // Get default icon theme
     263           0 :   GtkIconTheme* iconTheme = gtk_icon_theme_get_default();
     264           0 :   GtkIconInfo* iconInfo = nullptr;
     265             :   // Get icon size
     266           0 :   int32_t iconSize = GetIconSize(aIconURI);
     267             : 
     268           0 :   if (icon) {
     269             :     // Use icon and theme to get GtkIconInfo
     270             :     iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme,
     271             :                                               icon, iconSize,
     272           0 :                                               (GtkIconLookupFlags)0);
     273           0 :     g_object_unref(icon);
     274             :   }
     275             : 
     276           0 :   if (!iconInfo) {
     277             :     // Mozilla's mimetype lookup failed. Try the "unknown" icon.
     278             :     iconInfo = gtk_icon_theme_lookup_icon(iconTheme,
     279             :                                           "unknown", iconSize,
     280           0 :                                           (GtkIconLookupFlags)0);
     281           0 :     if (!iconInfo) {
     282           0 :       return NS_ERROR_NOT_AVAILABLE;
     283             :     }
     284             :   }
     285             : 
     286             :   // Create a GdkPixbuf buffer containing icon and scale it
     287           0 :   GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr);
     288           0 :   gtk_icon_info_free(iconInfo);
     289           0 :   if (!buf) {
     290           0 :     return NS_ERROR_UNEXPECTED;
     291             :   }
     292             : 
     293           0 :   nsresult rv = ScaleIconBuf(&buf, iconSize);
     294           0 :   NS_ENSURE_SUCCESS(rv, rv);
     295             : 
     296           0 :   rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
     297           0 :                                  getter_AddRefs(mRealChannel));
     298           0 :   g_object_unref(buf);
     299           0 :   return rv;
     300             : }
     301             : 
     302             : nsresult
     303           0 : nsIconChannel::Init(nsIURI* aURI)
     304             : {
     305           0 :   nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
     306           0 :   NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
     307             : 
     308           0 :   if (gfxPlatform::IsHeadless()) {
     309           0 :     return NS_ERROR_NOT_AVAILABLE;
     310             :   }
     311             : 
     312           0 :   nsAutoCString stockIcon;
     313           0 :   iconURI->GetStockIcon(stockIcon);
     314           0 :   if (stockIcon.IsEmpty()) {
     315           0 :     return InitWithGIO(iconURI);
     316             :   }
     317             : 
     318             :   // Search for stockIcon
     319           0 :   nsAutoCString iconSizeString;
     320           0 :   iconURI->GetIconSize(iconSizeString);
     321             : 
     322           0 :   nsAutoCString iconStateString;
     323           0 :   iconURI->GetIconState(iconStateString);
     324             : 
     325           0 :   GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
     326           0 :   GtkStateType state = iconStateString.EqualsLiteral("disabled") ?
     327           0 :     GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
     328             : 
     329             :   // First lookup the icon by stock id and text direction.
     330           0 :   GtkTextDirection direction = GTK_TEXT_DIR_NONE;
     331           0 :   if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) {
     332           0 :     direction = GTK_TEXT_DIR_LTR;
     333           0 :   } else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) {
     334           0 :     direction = GTK_TEXT_DIR_RTL;
     335             :   }
     336             : 
     337           0 :   bool forceDirection = direction != GTK_TEXT_DIR_NONE;
     338           0 :   nsAutoCString stockID;
     339           0 :   bool useIconName = false;
     340           0 :   if (!forceDirection) {
     341           0 :     direction = gtk_widget_get_default_direction();
     342           0 :     stockID = stockIcon;
     343             :   } else {
     344             :     // GTK versions < 2.22 use icon names from concatenating stock id with
     345             :     // -(rtl|ltr), which is how the moz-icon stock name is interpreted here.
     346           0 :     stockID = Substring(stockIcon, 0, stockIcon.Length() - 4);
     347             :     // However, if we lookup bidi icons by the stock name, then GTK versions
     348             :     // >= 2.22 will use a bidi lookup convention that most icon themes do not
     349             :     // yet follow.  Therefore, we first check to see if the theme supports the
     350             :     // old icon name as this will have bidi support (if found).
     351           0 :     GtkIconTheme* icon_theme = gtk_icon_theme_get_default();
     352             :     // Micking what gtk_icon_set_render_icon does with sizes, though it's not
     353             :     // critical as icons will be scaled to suit size.  It just means we follow
     354             :     // the same pathes and so share caches.
     355             :     gint width, height;
     356           0 :     if (gtk_icon_size_lookup(icon_size, &width, &height)) {
     357           0 :       gint size = std::min(width, height);
     358             :       // We use gtk_icon_theme_lookup_icon() without
     359             :       // GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so
     360             :       // we don't pick up fallback icons added by distributions for backward
     361             :       // compatibility.
     362             :       GtkIconInfo* icon =
     363           0 :         gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(),
     364           0 :                                    size, (GtkIconLookupFlags)0);
     365           0 :       if (icon) {
     366           0 :         useIconName = true;
     367           0 :         gtk_icon_info_free(icon);
     368             :       }
     369             :     }
     370             :   }
     371             : 
     372           0 :   ensure_stock_image_widget();
     373           0 :   GtkStyle* style = gtk_widget_get_style(gStockImageWidget);
     374           0 :   GtkIconSet* icon_set = nullptr;
     375           0 :   if (!useIconName) {
     376           0 :     icon_set = gtk_style_lookup_icon_set(style, stockID.get());
     377             :   }
     378             : 
     379           0 :   if (!icon_set) {
     380             :     // Either we have choosen icon-name lookup for a bidi icon, or stockIcon is
     381             :     // not a stock id so we assume it is an icon name.
     382           0 :     useIconName = true;
     383             :     // Creating a GtkIconSet is a convenient way to allow the style to
     384             :     // render the icon, possibly with variations suitable for insensitive
     385             :     // states.
     386           0 :     icon_set = gtk_icon_set_new();
     387           0 :     GtkIconSource* icon_source = gtk_icon_source_new();
     388             : 
     389           0 :     gtk_icon_source_set_icon_name(icon_source, stockIcon.get());
     390           0 :     gtk_icon_set_add_source(icon_set, icon_source);
     391           0 :     gtk_icon_source_free(icon_source);
     392             :   }
     393             : 
     394             :   GdkPixbuf* icon =
     395           0 :     gtk_icon_set_render_icon(icon_set, style, direction, state,
     396           0 :                              icon_size, gStockImageWidget, nullptr);
     397           0 :   if (useIconName) {
     398           0 :     gtk_icon_set_unref(icon_set);
     399             :   }
     400             : 
     401             :   // According to documentation, gtk_icon_set_render_icon() never returns
     402             :   // nullptr, but it does return nullptr when we have the problem reported
     403             :   // here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13
     404           0 :   if (!icon) {
     405           0 :     return NS_ERROR_NOT_AVAILABLE;
     406             :   }
     407             : 
     408           0 :   nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI,
     409           0 :                                           getter_AddRefs(mRealChannel));
     410             : 
     411           0 :   g_object_unref(icon);
     412             : 
     413           0 :   return rv;
     414             : }
     415             : 
     416             : void
     417           0 : nsIconChannel::Shutdown() {
     418           0 :   if (gProtoWindow) {
     419           0 :     gtk_widget_destroy(gProtoWindow);
     420           0 :     gProtoWindow = nullptr;
     421           0 :     gStockImageWidget = nullptr;
     422             :   }
     423           0 : }

Generated by: LCOV version 1.13