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 : }
|