nsIconChannel.cpp 13 KB

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