From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- image/decoders/icon/android/moz.build | 13 + image/decoders/icon/android/nsIconChannel.cpp | 145 +++++ image/decoders/icon/android/nsIconChannel.h | 46 ++ image/decoders/icon/gtk/moz.build | 16 + image/decoders/icon/gtk/nsIconChannel.cpp | 427 +++++++++++++ image/decoders/icon/gtk/nsIconChannel.h | 43 ++ image/decoders/icon/mac/moz.build | 11 + image/decoders/icon/mac/nsIconChannel.h | 59 ++ image/decoders/icon/mac/nsIconChannelCocoa.mm | 565 +++++++++++++++++ image/decoders/icon/moz.build | 32 + image/decoders/icon/nsIconModule.cpp | 60 ++ image/decoders/icon/nsIconProtocolHandler.cpp | 126 ++++ image/decoders/icon/nsIconProtocolHandler.h | 26 + image/decoders/icon/nsIconURI.cpp | 699 +++++++++++++++++++++ image/decoders/icon/nsIconURI.h | 63 ++ image/decoders/icon/win/moz.build | 11 + image/decoders/icon/win/nsIconChannel.cpp | 841 ++++++++++++++++++++++++++ image/decoders/icon/win/nsIconChannel.h | 66 ++ 18 files changed, 3249 insertions(+) create mode 100644 image/decoders/icon/android/moz.build create mode 100644 image/decoders/icon/android/nsIconChannel.cpp create mode 100644 image/decoders/icon/android/nsIconChannel.h create mode 100644 image/decoders/icon/gtk/moz.build create mode 100644 image/decoders/icon/gtk/nsIconChannel.cpp create mode 100644 image/decoders/icon/gtk/nsIconChannel.h create mode 100644 image/decoders/icon/mac/moz.build create mode 100644 image/decoders/icon/mac/nsIconChannel.h create mode 100644 image/decoders/icon/mac/nsIconChannelCocoa.mm create mode 100644 image/decoders/icon/moz.build create mode 100644 image/decoders/icon/nsIconModule.cpp create mode 100644 image/decoders/icon/nsIconProtocolHandler.cpp create mode 100644 image/decoders/icon/nsIconProtocolHandler.h create mode 100644 image/decoders/icon/nsIconURI.cpp create mode 100644 image/decoders/icon/nsIconURI.h create mode 100644 image/decoders/icon/win/moz.build create mode 100644 image/decoders/icon/win/nsIconChannel.cpp create mode 100644 image/decoders/icon/win/nsIconChannel.h (limited to 'image/decoders/icon') diff --git a/image/decoders/icon/android/moz.build b/image/decoders/icon/android/moz.build new file mode 100644 index 000000000..5e58ff0b6 --- /dev/null +++ b/image/decoders/icon/android/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/android/nsIconChannel.cpp b/image/decoders/icon/android/nsIconChannel.cpp new file mode 100644 index 000000000..5670bf2f9 --- /dev/null +++ b/image/decoders/icon/android/nsIconChannel.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "mozilla/dom/ContentChild.h" +#include "nsMimeTypes.h" +#include "nsIURL.h" +#include "nsXULAppAPI.h" +#include "AndroidBridge.h" +#include "nsIconChannel.h" +#include "nsIStringStream.h" +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsNullPrincipal.h" + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIRequest, + nsIChannel) + +using namespace mozilla; +using mozilla::dom::ContentChild; + +static nsresult +GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf) +{ + if (!AndroidBridge::Bridge()) { + return NS_ERROR_FAILURE; + } + + AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, aBuf); + + return NS_OK; +} + +static nsresult +CallRemoteGetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf) +{ + NS_ENSURE_TRUE(aBuf != nullptr, NS_ERROR_NULL_POINTER); + + // An array has to be used to get data from remote process + InfallibleTArray bits; + uint32_t bufSize = aIconSize * aIconSize * 4; + + if (!ContentChild::GetSingleton()->SendGetIconForExtension( + PromiseFlatCString(aFileExt), aIconSize, &bits)) { + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(bits.Length() == bufSize, "Pixels array is incomplete"); + if (bits.Length() != bufSize) { + return NS_ERROR_FAILURE; + } + + memcpy(aBuf, bits.Elements(), bufSize); + + return NS_OK; +} + +static nsresult +moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt, + uint32_t aIconSize, nsIChannel** aChannel) +{ + NS_ENSURE_TRUE(aIconSize < 256 && aIconSize > 0, NS_ERROR_UNEXPECTED); + + int width = aIconSize; + int height = aIconSize; + + // moz-icon data should have two bytes for the size, + // then the ARGB pixel values with pre-multiplied Alpha + const int channels = 4; + long int buf_size = 2 + channels * height * width; + uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size); + NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); + uint8_t* out = buf; + + *(out++) = width; + *(out++) = height; + + nsresult rv; + if (XRE_IsParentProcess()) { + rv = GetIconForExtension(aFileExt, aIconSize, out); + } else { + rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Encode the RGBA data + const uint8_t* in = out; + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + uint8_t r = *(in++); + uint8_t g = *(in++); + uint8_t b = *(in++); + uint8_t a = *(in++); +#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255)) + *(out++) = DO_PREMULTIPLY(b); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(r); + *(out++) = a; +#undef DO_PREMULTIPLY + } + } + + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->AdoptData((char*)buf, buf_size); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for + // this iconChannel. Use the most restrictive security settings for the + // temporary loadInfo to make sure the channel can not be openend. + nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); + return NS_NewInputStreamChannel(aChannel, + aURI, + stream, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, + NS_LITERAL_CSTRING(IMAGE_ICON_MS)); +} + +nsresult +nsIconChannel::Init(nsIURI* aURI) +{ + nsCOMPtr iconURI = do_QueryInterface(aURI); + NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + + uint32_t desiredImageSize; + iconURI->GetImageSize(&desiredImageSize); + + nsAutoCString iconFileExt; + iconURI->GetFileExtension(iconFileExt); + + return moz_icon_to_channel(iconURI, iconFileExt, desiredImageSize, + getter_AddRefs(mRealChannel)); +} diff --git a/image/decoders/icon/android/nsIconChannel.h b/image/decoders/icon/android/nsIconChannel.h new file mode 100644 index 000000000..be5542998 --- /dev/null +++ b/image/decoders/icon/android/nsIconChannel.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_android_nsIconChannel_h +#define mozilla_image_decoders_icon_android_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsIChannel.h" +#include "nsIURI.h" +#include "nsIIconURI.h" +#include "nsCOMPtr.h" + +/** + * This class is the Android implementation of nsIconChannel. + * It asks Android for an icon, and creates a new channel for + * that file to which all calls will be proxied. + */ +class nsIconChannel final : public nsIChannel { + public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIREQUEST(mRealChannel->) + NS_FORWARD_NSICHANNEL(mRealChannel->) + + nsIconChannel() { } + + /** + * Called by nsIconProtocolHandler after it creates this channel. + * Must be called before calling any other function on this object. + * If this method fails, no other function must be called on this object. + */ + nsresult Init(nsIURI* aURI); + + private: + ~nsIconChannel() { } + + /** + * The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html). + * Will always be non-null after a successful Init. + */ + nsCOMPtr mRealChannel; +}; + +#endif // mozilla_image_decoders_icon_android_nsIconChannel_h diff --git a/image/decoders/icon/gtk/moz.build b/image/decoders/icon/gtk/moz.build new file mode 100644 index 000000000..a8116ca87 --- /dev/null +++ b/image/decoders/icon/gtk/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['MOZ_ENABLE_GNOMEUI']: + CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS'] +else: + CXXFLAGS += CONFIG['TK_CFLAGS'] diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp new file mode 100644 index 000000000..b014e4c04 --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.cpp @@ -0,0 +1,427 @@ +/* vim:set ts=2 sw=2 sts=2 cin et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconChannel.h" + +#include +#include + +#include "mozilla/DebugOnly.h" +#include "mozilla/EndianUtils.h" +#include + +#ifdef MOZ_ENABLE_GIO +#include +#endif + +#include + +#include "nsMimeTypes.h" +#include "nsIMIMEService.h" + +#include "nsServiceManagerUtils.h" + +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsIStringStream.h" +#include "nsServiceManagerUtils.h" +#include "nsNullPrincipal.h" +#include "nsIURL.h" +#include "prlink.h" + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIRequest, + nsIChannel) + +static nsresult +moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI, + nsIChannel** aChannel) +{ + int width = gdk_pixbuf_get_width(aPixbuf); + int height = gdk_pixbuf_get_height(aPixbuf); + NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 && + gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && + gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && + gdk_pixbuf_get_has_alpha(aPixbuf) && + gdk_pixbuf_get_n_channels(aPixbuf) == 4, + NS_ERROR_UNEXPECTED); + + const int n_channels = 4; + gsize buf_size = 2 + n_channels * height * width; + uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size); + NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); + uint8_t* out = buf; + + *(out++) = width; + *(out++) = height; + + const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf); + int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels; + + // encode the RGB data and the A data + const guchar* in = pixels; + for (int y = 0; y < height; ++y, in += rowextra) { + for (int x = 0; x < width; ++x) { + uint8_t r = *(in++); + uint8_t g = *(in++); + uint8_t b = *(in++); + uint8_t a = *(in++); +#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255)) +#if MOZ_LITTLE_ENDIAN + *(out++) = DO_PREMULTIPLY(b); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(r); + *(out++) = a; +#else + *(out++) = a; + *(out++) = DO_PREMULTIPLY(r); + *(out++) = DO_PREMULTIPLY(g); + *(out++) = DO_PREMULTIPLY(b); +#endif +#undef DO_PREMULTIPLY + } + } + + NS_ASSERTION(out == buf + buf_size, "size miscalculation"); + + nsresult rv; + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + + // Prevent the leaking of buf + if (NS_WARN_IF(NS_FAILED(rv))) { + free(buf); + return rv; + } + + // stream takes ownership of buf and will free it on destruction. + // This function cannot fail. + rv = stream->AdoptData((char*)buf, buf_size); + + // If this no longer holds then re-examine buf's lifetime. + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for + // this iconChannel. Use the most restrictive security settings for the + // temporary loadInfo to make sure the channel can not be openend. + nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); + return NS_NewInputStreamChannel(aChannel, + aURI, + stream, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_INTERNAL_IMAGE, + NS_LITERAL_CSTRING(IMAGE_ICON_MS)); +} + +static GtkWidget* gProtoWindow = nullptr; +static GtkWidget* gStockImageWidget = nullptr; + +static void +ensure_stock_image_widget() +{ + // Only the style of the GtkImage needs to be used, but the widget is kept + // to track dynamic style changes. + if (!gProtoWindow) { + gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP); + GtkWidget* protoLayout = gtk_fixed_new(); + gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout); + + gStockImageWidget = gtk_image_new(); + gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget); + + gtk_widget_ensure_style(gStockImageWidget); + } +} + +static GtkIconSize +moz_gtk_icon_size(const char* name) +{ + if (strcmp(name, "button") == 0) { + return GTK_ICON_SIZE_BUTTON; + } + + if (strcmp(name, "menu") == 0) { + return GTK_ICON_SIZE_MENU; + } + + if (strcmp(name, "toolbar") == 0) { + return GTK_ICON_SIZE_LARGE_TOOLBAR; + } + + if (strcmp(name, "toolbarsmall") == 0) { + return GTK_ICON_SIZE_SMALL_TOOLBAR; + } + + if (strcmp(name, "dnd") == 0) { + return GTK_ICON_SIZE_DND; + } + + if (strcmp(name, "dialog") == 0) { + return GTK_ICON_SIZE_DIALOG; + } + + return GTK_ICON_SIZE_MENU; +} + +#ifdef MOZ_ENABLE_GIO +static int32_t +GetIconSize(nsIMozIconURI* aIconURI) +{ + nsAutoCString iconSizeString; + + aIconURI->GetIconSize(iconSizeString); + if (iconSizeString.IsEmpty()) { + uint32_t size; + mozilla::DebugOnly rv = aIconURI->GetImageSize(&size); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed"); + return size; + } else { + int size; + + GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get()); + gtk_icon_size_lookup(icon_size, &size, nullptr); + return size; + } +} + +/* Scale icon buffer to preferred size */ +static nsresult +ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize) +{ + // Scale buffer only if width or height differ from preferred size + if (gdk_pixbuf_get_width(*aBuf) != iconSize && + gdk_pixbuf_get_height(*aBuf) != iconSize) { + GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize, + GDK_INTERP_BILINEAR); + // replace original buffer by scaled + g_object_unref(*aBuf); + *aBuf = scaled; + if (!scaled) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + return NS_OK; +} + +nsresult +nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI) +{ + GIcon *icon = nullptr; + nsCOMPtr fileURI; + + // Read icon content + aIconURI->GetIconURL(getter_AddRefs(fileURI)); + + // Get icon for file specified by URI + if (fileURI) { + bool isFile; + nsAutoCString spec; + fileURI->GetAsciiSpec(spec); + if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) { + GFile* file = g_file_new_for_uri(spec.get()); + GFileInfo* fileInfo = g_file_query_info(file, + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + nullptr, nullptr); + g_object_unref(file); + if (fileInfo) { + // icon from g_content_type_get_icon doesn't need unref + icon = g_file_info_get_icon(fileInfo); + if (icon) { + g_object_ref(icon); + } + g_object_unref(fileInfo); + } + } + } + + // Try to get icon by using MIME type + if (!icon) { + nsAutoCString type; + aIconURI->GetContentType(type); + // Try to get MIME type from file extension by using nsIMIMEService + if (type.IsEmpty()) { + nsCOMPtr ms(do_GetService("@mozilla.org/mime;1")); + if (ms) { + nsAutoCString fileExt; + aIconURI->GetFileExtension(fileExt); + ms->GetTypeFromExtension(fileExt, type); + } + } + char* ctype = nullptr; // character representation of content type + if (!type.IsEmpty()) { + ctype = g_content_type_from_mime_type(type.get()); + } + if (ctype) { + icon = g_content_type_get_icon(ctype); + g_free(ctype); + } + } + + // Get default icon theme + GtkIconTheme* iconTheme = gtk_icon_theme_get_default(); + GtkIconInfo* iconInfo = nullptr; + // Get icon size + int32_t iconSize = GetIconSize(aIconURI); + + if (icon) { + // Use icon and theme to get GtkIconInfo + iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme, + icon, iconSize, + (GtkIconLookupFlags)0); + g_object_unref(icon); + } + + if (!iconInfo) { + // Mozilla's mimetype lookup failed. Try the "unknown" icon. + iconInfo = gtk_icon_theme_lookup_icon(iconTheme, + "unknown", iconSize, + (GtkIconLookupFlags)0); + if (!iconInfo) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + // Create a GdkPixbuf buffer containing icon and scale it + GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr); + gtk_icon_info_free(iconInfo); + if (!buf) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = ScaleIconBuf(&buf, iconSize); + NS_ENSURE_SUCCESS(rv, rv); + + rv = moz_gdk_pixbuf_to_channel(buf, aIconURI, + getter_AddRefs(mRealChannel)); + g_object_unref(buf); + return rv; +} +#endif // MOZ_ENABLE_GIO + +nsresult +nsIconChannel::Init(nsIURI* aURI) +{ + nsCOMPtr iconURI = do_QueryInterface(aURI); + NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (stockIcon.IsEmpty()) { +#ifdef MOZ_ENABLE_GIO + return InitWithGIO(iconURI); +#else + return NS_ERROR_NOT_AVAILABLE; +#endif + } + + // Search for stockIcon + nsAutoCString iconSizeString; + iconURI->GetIconSize(iconSizeString); + + nsAutoCString iconStateString; + iconURI->GetIconState(iconStateString); + + GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get()); + GtkStateType state = iconStateString.EqualsLiteral("disabled") ? + GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL; + + // First lookup the icon by stock id and text direction. + GtkTextDirection direction = GTK_TEXT_DIR_NONE; + if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) { + direction = GTK_TEXT_DIR_LTR; + } else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) { + direction = GTK_TEXT_DIR_RTL; + } + + bool forceDirection = direction != GTK_TEXT_DIR_NONE; + nsAutoCString stockID; + bool useIconName = false; + if (!forceDirection) { + direction = gtk_widget_get_default_direction(); + stockID = stockIcon; + } else { + // GTK versions < 2.22 use icon names from concatenating stock id with + // -(rtl|ltr), which is how the moz-icon stock name is interpreted here. + stockID = Substring(stockIcon, 0, stockIcon.Length() - 4); + // However, if we lookup bidi icons by the stock name, then GTK versions + // >= 2.22 will use a bidi lookup convention that most icon themes do not + // yet follow. Therefore, we first check to see if the theme supports the + // old icon name as this will have bidi support (if found). + GtkIconTheme* icon_theme = gtk_icon_theme_get_default(); + // Micking what gtk_icon_set_render_icon does with sizes, though it's not + // critical as icons will be scaled to suit size. It just means we follow + // the same pathes and so share caches. + gint width, height; + if (gtk_icon_size_lookup(icon_size, &width, &height)) { + gint size = std::min(width, height); + // We use gtk_icon_theme_lookup_icon() without + // GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so + // we don't pick up fallback icons added by distributions for backward + // compatibility. + GtkIconInfo* icon = + gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(), + size, (GtkIconLookupFlags)0); + if (icon) { + useIconName = true; + gtk_icon_info_free(icon); + } + } + } + + ensure_stock_image_widget(); + GtkStyle* style = gtk_widget_get_style(gStockImageWidget); + GtkIconSet* icon_set = nullptr; + if (!useIconName) { + icon_set = gtk_style_lookup_icon_set(style, stockID.get()); + } + + if (!icon_set) { + // Either we have choosen icon-name lookup for a bidi icon, or stockIcon is + // not a stock id so we assume it is an icon name. + useIconName = true; + // Creating a GtkIconSet is a convenient way to allow the style to + // render the icon, possibly with variations suitable for insensitive + // states. + icon_set = gtk_icon_set_new(); + GtkIconSource* icon_source = gtk_icon_source_new(); + + gtk_icon_source_set_icon_name(icon_source, stockIcon.get()); + gtk_icon_set_add_source(icon_set, icon_source); + gtk_icon_source_free(icon_source); + } + + GdkPixbuf* icon = + gtk_icon_set_render_icon(icon_set, style, direction, state, + icon_size, gStockImageWidget, nullptr); + if (useIconName) { + gtk_icon_set_unref(icon_set); + } + + // According to documentation, gtk_icon_set_render_icon() never returns + // nullptr, but it does return nullptr when we have the problem reported + // here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13 + if (!icon) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI, + getter_AddRefs(mRealChannel)); + + g_object_unref(icon); + + return rv; +} + +void +nsIconChannel::Shutdown() { + if (gProtoWindow) { + gtk_widget_destroy(gProtoWindow); + gProtoWindow = nullptr; + gStockImageWidget = nullptr; + } +} diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h new file mode 100644 index 000000000..5d59272d5 --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_gtk_nsIconChannel_h +#define mozilla_image_decoders_icon_gtk_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsIChannel.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" +#include "nsIIconURI.h" +#include "nsCOMPtr.h" + +/// This class is the gnome implementation of nsIconChannel. It basically asks +/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for +/// that file to which all calls will be proxied. +class nsIconChannel final : public nsIChannel +{ + public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIREQUEST(mRealChannel->) + NS_FORWARD_NSICHANNEL(mRealChannel->) + + nsIconChannel() { } + + static void Shutdown(); + + /// Called by nsIconProtocolHandler after it creates this channel. + /// Must be called before calling any other function on this object. + /// If this method fails, no other function must be called on this object. + nsresult Init(nsIURI* aURI); + private: + ~nsIconChannel() { } + /// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html). + /// Will always be non-null after a successful Init. + nsCOMPtr mRealChannel; + + nsresult InitWithGIO(nsIMozIconURI* aIconURI); +}; + +#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h diff --git a/image/decoders/icon/mac/moz.build b/image/decoders/icon/mac/moz.build new file mode 100644 index 000000000..158c326ea --- /dev/null +++ b/image/decoders/icon/mac/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannelCocoa.mm', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h new file mode 100644 index 000000000..9fef17119 --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannel.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h +#define mozilla_image_encoders_icon_mac_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIChannel.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIInputStreamPump.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" + +class nsIFile; + +class nsIconChannel final : public nsIChannel, public nsIStreamListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsIconChannel(); + + nsresult Init(nsIURI* uri); + +protected: + virtual ~nsIconChannel(); + + nsCOMPtr mUrl; + nsCOMPtr mOriginalURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mCallbacks; + nsCOMPtr mOwner; + nsCOMPtr mLoadInfo; + + nsCOMPtr mPump; + nsCOMPtr mListener; + + nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking); + + nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsACString& aContentType, + nsACString& aFileExtension); +}; + +#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h diff --git a/image/decoders/icon/mac/nsIconChannelCocoa.mm b/image/decoders/icon/mac/nsIconChannelCocoa.mm new file mode 100644 index 000000000..9c2686cdd --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsContentUtils.h" +#include "nsIconChannel.h" +#include "mozilla/EndianUtils.h" +#include "nsIIconURI.h" +#include "nsIServiceManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsXPIDLString.h" +#include "nsMimeTypes.h" +#include "nsMemory.h" +#include "nsIStringStream.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsIPipe.h" +#include "nsIOutputStream.h" +#include "nsIMIMEService.h" +#include "nsCExternalHandlerService.h" +#include "nsILocalFileMac.h" +#include "nsIFileURL.h" +#include "nsTArray.h" +#include "nsObjCExceptions.h" +#include "nsProxyRelease.h" +#include "nsContentSecurityManager.h" + +#include + +// nsIconChannel methods +nsIconChannel::nsIconChannel() +{ +} + +nsIconChannel::~nsIconChannel() +{ + if (mLoadInfo) { + NS_ReleaseOnMainThread(mLoadInfo.forget()); + } +} + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIChannel, + nsIRequest, + nsIRequestObserver, + nsIStreamListener) + +nsresult +nsIconChannel::Init(nsIURI* uri) +{ + NS_ASSERTION(uri, "no uri"); + mUrl = uri; + mOriginalURI = uri; + nsresult rv; + mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsIconChannel::GetName(nsACString& result) +{ + return mUrl->GetSpec(result); +} + +NS_IMETHODIMP +nsIconChannel::IsPending(bool* result) +{ + return mPump->IsPending(result); +} + +NS_IMETHODIMP +nsIconChannel::GetStatus(nsresult* status) +{ + return mPump->GetStatus(status); +} + +NS_IMETHODIMP +nsIconChannel::Cancel(nsresult status) +{ + return mPump->Cancel(status); +} + +NS_IMETHODIMP +nsIconChannel::Suspend(void) +{ + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsIconChannel::Resume(void) +{ + return mPump->Resume(); +} + +// nsIRequestObserver methods +NS_IMETHODIMP +nsIconChannel::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + if (mListener) { + return mListener->OnStartRequest(this, aContext); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + if (mListener) { + mListener->OnStopRequest(this, aContext, aStatus); + mListener = nullptr; + } + + // Remove from load group + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + } + + return NS_OK; +} + +// nsIStreamListener methods +NS_IMETHODIMP +nsIconChannel::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + if (mListener) { + return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIChannel methods: + +NS_IMETHODIMP +nsIconChannel::GetOriginalURI(nsIURI** aURI) +{ + *aURI = mOriginalURI; + NS_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOriginalURI(nsIURI* aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetURI(nsIURI** aURI) +{ + *aURI = mUrl; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::Open(nsIInputStream** _retval) +{ + return MakeInputStream(_retval, false); +} + +NS_IMETHODIMP +nsIconChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +nsresult +nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsACString& aContentType, + nsACString& aFileExtension) +{ + nsresult rv = NS_OK; + nsCOMPtr iconURI (do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + iconURI->GetImageSize(aDesiredImageSize); + iconURI->GetContentType(aContentType); + iconURI->GetFileExtension(aFileExtension); + + nsCOMPtr url; + rv = iconURI->GetIconURL(getter_AddRefs(url)); + if (NS_FAILED(rv) || !url) return NS_OK; + + nsCOMPtr fileURL = do_QueryInterface(url, &rv); + if (NS_FAILED(rv) || !fileURL) return NS_OK; + + nsCOMPtr file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) return NS_OK; + + nsCOMPtr localFileMac (do_QueryInterface(file, &rv)); + if (NS_FAILED(rv) || !localFileMac) return NS_OK; + + *aLocalFile = file; + NS_IF_ADDREF(*aLocalFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen(nsIStreamListener* aListener, + nsISupports* ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + nsCOMPtr inStream; + nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); + NS_ENSURE_SUCCESS(rv, rv); + + // Init our stream pump + rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mPump->AsyncRead(this, ctxt); + if (NS_SUCCEEDED(rv)) { + // Store our real listener + mListener = aListener; + // Add ourself to the load group, if available + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + } + + return rv; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen2(nsIStreamListener* aListener) +{ + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +nsresult +nsIconChannel::MakeInputStream(nsIInputStream** _retval, + bool aNonBlocking) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsXPIDLCString contentType; + nsAutoCString fileExt; + nsCOMPtr fileloc; // file we want an icon for + uint32_t desiredImageSize; + nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc), + &desiredImageSize, contentType, fileExt); + NS_ENSURE_SUCCESS(rv, rv); + + bool fileExists = false; + if (fileloc) { + // ensure that we DO NOT resolve aliases, very important for file views + fileloc->SetFollowLinks(false); + fileloc->Exists(&fileExists); + } + + NSImage* iconImage = nil; + + // first try to get the icon from the file if it exists + if (fileExists) { + nsCOMPtr localFileMac(do_QueryInterface(fileloc, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + CFURLRef macURL; + if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) { + iconImage = [[NSWorkspace sharedWorkspace] + iconForFile:[(NSURL*)macURL path]]; + ::CFRelease(macURL); + } + } + + // if we don't have an icon yet try to get one by extension + if (!iconImage && !fileExt.IsEmpty()) { + NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()]; + iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension]; + } + + // If we still don't have an icon, get the generic document icon. + if (!iconImage) { + iconImage = [[NSWorkspace sharedWorkspace] + iconForFileType:NSFileTypeUnknown]; + } + + if (!iconImage) { + return NS_ERROR_FAILURE; + } + + // we have an icon now, size it + NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize); + [iconImage setSize:desiredSizeRect.size]; + + [iconImage lockFocus]; + NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc] + initWithFocusedViewRect:desiredSizeRect] + autorelease]; + [iconImage unlockFocus]; + + // we expect the following things to be true about our bitmapRep + NS_ENSURE_TRUE(![bitmapRep isPlanar] && + // Not necessarily: on a HiDPI-capable system, we'll get + // a 2x bitmap + // (unsigned int)[bitmapRep bytesPerPlane] == + // desiredImageSize * desiredImageSize * 4 && + [bitmapRep bitsPerPixel] == 32 && + [bitmapRep samplesPerPixel] == 4 && + [bitmapRep hasAlpha] == YES, + NS_ERROR_UNEXPECTED); + + // check what size we actually got, and ensure it isn't too big to return + uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4; + NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED); + + // now we can validate the amount of data + NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] == + actualImageSize * actualImageSize * 4, + NS_ERROR_UNEXPECTED); + + // rgba, pre-multiplied data + uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData]; + + // create our buffer + int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane]; + AutoTArray iconBuffer; // initial size is for + // 16x16 + iconBuffer.SetLength(bufferCapacity); + + uint8_t* iconBufferPtr = iconBuffer.Elements(); + + // write header data into buffer + *iconBufferPtr++ = actualImageSize; + *iconBufferPtr++ = actualImageSize; + + uint32_t dataCount = [bitmapRep bytesPerPlane]; + uint32_t index = 0; + while (index < dataCount) { + // get data from the bitmap + uint8_t r = bitmapRepData[index++]; + uint8_t g = bitmapRepData[index++]; + uint8_t b = bitmapRepData[index++]; + uint8_t a = bitmapRepData[index++]; + + // write data out to our buffer + // non-cairo uses native image format, but the A channel is ignored. + // cairo uses ARGB (highest to lowest bits) +#if MOZ_LITTLE_ENDIAN + *iconBufferPtr++ = b; + *iconBufferPtr++ = g; + *iconBufferPtr++ = r; + *iconBufferPtr++ = a; +#else + *iconBufferPtr++ = a; + *iconBufferPtr++ = r; + *iconBufferPtr++ = g; + *iconBufferPtr++ = b; +#endif + } + + NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity, + "buffer size miscalculation"); + + // Now, create a pipe and stuff our data into it + nsCOMPtr inStream; + nsCOMPtr outStream; + rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), + bufferCapacity, bufferCapacity, aNonBlocking); + + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity, + &written); + if (NS_SUCCEEDED(rv)) { + NS_IF_ADDREF(*_retval = inStream); + } + } + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) +{ + return mPump->GetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) +{ + return mPump->SetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::GetContentType(nsACString& aContentType) +{ + aContentType.AssignLiteral(IMAGE_ICON_MS); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentType(const nsACString& aContentType) +{ + //It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentCharset(nsACString& aContentCharset) +{ + aContentCharset.AssignLiteral(IMAGE_ICON_MS); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentCharset(const nsACString& aContentCharset) +{ + //It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionFilename(nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + SetContentDispositionFilename(const nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionHeader(nsACString& aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentLength(int64_t* aContentLength) +{ + *aContentLength = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentLength(int64_t aContentLength) +{ + NS_NOTREACHED("nsIconChannel::SetContentLength"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) +{ + *aLoadGroup = mLoadGroup; + NS_IF_ADDREF(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetOwner(nsISupports** aOwner) +{ + *aOwner = mOwner.get(); + NS_IF_ADDREF(*aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOwner(nsISupports* aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks) +{ + *aNotificationCallbacks = mCallbacks.get(); + NS_IF_ADDREF(*aNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) +{ + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo) +{ + *aSecurityInfo = nullptr; + return NS_OK; +} + diff --git a/image/decoders/icon/moz.build b/image/decoders/icon/moz.build new file mode 100644 index 000000000..9c6106fa7 --- /dev/null +++ b/image/decoders/icon/moz.build @@ -0,0 +1,32 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nsIconModule.cpp', + 'nsIconProtocolHandler.cpp', + 'nsIconURI.cpp', +] + +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') + +platform = None + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + platform = 'gtk' + +if CONFIG['OS_ARCH'] == 'WINNT': + platform = 'win' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + platform = 'mac' + +if CONFIG['OS_TARGET'] == 'Android': + platform = 'android' + +if platform: + LOCAL_INCLUDES += [platform] diff --git a/image/decoders/icon/nsIconModule.cpp b/image/decoders/icon/nsIconModule.cpp new file mode 100644 index 000000000..36225374d --- /dev/null +++ b/image/decoders/icon/nsIconModule.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIconProtocolHandler.h" +#include "nsIconURI.h" +#include "nsIconChannel.h" + +// objects that just require generic constructors +//***************************************************************************** +// Protocol CIDs + +#define NS_ICONPROTOCOL_CID { 0xd0f9db12, 0x249c, 0x11d5, \ + { 0x99, 0x5, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } } + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsIconProtocolHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMozIconURI) + +NS_DEFINE_NAMED_CID(NS_ICONPROTOCOL_CID); +NS_DEFINE_NAMED_CID(NS_MOZICONURI_CID); + +static const mozilla::Module::CIDEntry kIconCIDs[] = { + { &kNS_ICONPROTOCOL_CID, false, nullptr, nsIconProtocolHandlerConstructor }, + { &kNS_MOZICONURI_CID, false, nullptr, nsMozIconURIConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kIconContracts[] = { + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon", &kNS_ICONPROTOCOL_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kIconCategories[] = { + { nullptr } +}; + +static void +IconDecoderModuleDtor() +{ +#if (MOZ_WIDGET_GTK == 2) + nsIconChannel::Shutdown(); +#endif +} + +static const mozilla::Module kIconModule = { + mozilla::Module::kVersion, + kIconCIDs, + kIconContracts, + kIconCategories, + nullptr, + nullptr, + IconDecoderModuleDtor +}; + +NSMODULE_DEFN(nsIconDecoderModule) = &kIconModule; diff --git a/image/decoders/icon/nsIconProtocolHandler.cpp b/image/decoders/icon/nsIconProtocolHandler.cpp new file mode 100644 index 000000000..03aaa2a14 --- /dev/null +++ b/image/decoders/icon/nsIconProtocolHandler.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconProtocolHandler.h" + +#include "nsIconChannel.h" +#include "nsIconURI.h" +#include "nsIURL.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsNetCID.h" + +/////////////////////////////////////////////////////////////////////////////// + +nsIconProtocolHandler::nsIconProtocolHandler() +{ } + +nsIconProtocolHandler::~nsIconProtocolHandler() +{ } + +NS_IMPL_ISUPPORTS(nsIconProtocolHandler, nsIProtocolHandler, + nsISupportsWeakReference) + + +/////////////////////////////////////////////////////////////////////////////// +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsIconProtocolHandler::GetScheme(nsACString& result) +{ + result = "moz-icon"; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::GetDefaultPort(int32_t* result) +{ + *result = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::AllowPort(int32_t port, + const char* scheme, + bool* _retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::GetProtocolFlags(uint32_t* result) +{ + *result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE | + URI_IS_LOCAL_RESOURCE; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewURI(const nsACString& aSpec, + const char* aOriginCharset, // ignored + nsIURI* aBaseURI, + nsIURI** result) +{ + nsCOMPtr uri = new nsMozIconURI(); + if (!uri) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = uri->SetSpec(aSpec); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr iconURL; + uri->GetIconURL(getter_AddRefs(iconURL)); + if (iconURL) { + uri = new nsNestedMozIconURI(); + rv = uri->SetSpec(aSpec); + if (NS_FAILED(rv)) { + return rv; + } + } + + NS_ADDREF(*result = uri); + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewChannel2(nsIURI* url, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + NS_ENSURE_ARG_POINTER(url); + nsIconChannel* channel = new nsIconChannel; + if (!channel) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(channel); + + nsresult rv = channel->Init(url); + if (NS_FAILED(rv)) { + NS_RELEASE(channel); + return rv; + } + + // set the loadInfo on the new channel + rv = channel->SetLoadInfo(aLoadInfo); + if (NS_FAILED(rv)) { + NS_RELEASE(channel); + return rv; + } + + *result = channel; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::NewChannel(nsIURI* url, nsIChannel** result) +{ + return NewChannel2(url, nullptr, result); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/image/decoders/icon/nsIconProtocolHandler.h b/image/decoders/icon/nsIconProtocolHandler.h new file mode 100644 index 000000000..9653d36f6 --- /dev/null +++ b/image/decoders/icon/nsIconProtocolHandler.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_nsIconProtocolHandler_h +#define mozilla_image_decoders_icon_nsIconProtocolHandler_h + +#include "nsWeakReference.h" +#include "nsIProtocolHandler.h" + +class nsIconProtocolHandler : public nsIProtocolHandler, + public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + + // nsIconProtocolHandler methods: + nsIconProtocolHandler(); + +protected: + virtual ~nsIconProtocolHandler(); +}; + +#endif // mozilla_image_decoders_icon_nsIconProtocolHandler_h diff --git a/image/decoders/icon/nsIconURI.cpp b/image/decoders/icon/nsIconURI.cpp new file mode 100644 index 000000000..2c2788c8f --- /dev/null +++ b/image/decoders/icon/nsIconURI.cpp @@ -0,0 +1,699 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set sw=2 sts=2 ts=2 et tw=80: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIconURI.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Sprintf.h" + +#include "nsIIOService.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "plstr.h" +#include + +using namespace mozilla; +using namespace mozilla::ipc; + +#define DEFAULT_IMAGE_SIZE 16 + +#if defined(MAX_PATH) +#define SANE_FILE_NAME_LEN MAX_PATH +#elif defined(PATH_MAX) +#define SANE_FILE_NAME_LEN PATH_MAX +#else +#define SANE_FILE_NAME_LEN 1024 +#endif + +// helper function for parsing out attributes like size, and contentType +// from the icon url. +static void extractAttributeValue(const char* aSearchString, + const char* aAttributeName, + nsCString& aResult); + +static const char* kSizeStrings[] = +{ + "button", + "toolbar", + "toolbarsmall", + "menu", + "dnd", + "dialog" +}; + +static const char* kStateStrings[] = +{ + "normal", + "disabled" +}; + +//////////////////////////////////////////////////////////////////////////////// + +nsMozIconURI::nsMozIconURI() + : mSize(DEFAULT_IMAGE_SIZE), + mIconSize(-1), + mIconState(-1) +{ } + +nsMozIconURI::~nsMozIconURI() +{ } + +NS_IMPL_ISUPPORTS(nsMozIconURI, nsIMozIconURI, nsIURI, nsIIPCSerializableURI) + +#define MOZICON_SCHEME "moz-icon:" +#define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1) + +//////////////////////////////////////////////////////////////////////////////// +// nsIURI methods: + +NS_IMETHODIMP +nsMozIconURI::GetSpec(nsACString& aSpec) +{ + aSpec = MOZICON_SCHEME; + + if (mIconURL) { + nsAutoCString fileIconSpec; + nsresult rv = mIconURL->GetSpec(fileIconSpec); + NS_ENSURE_SUCCESS(rv, rv); + aSpec += fileIconSpec; + } else if (!mStockIcon.IsEmpty()) { + aSpec += "//stock/"; + aSpec += mStockIcon; + } else { + aSpec += "//"; + aSpec += mFileName; + } + + aSpec += "?size="; + if (mIconSize >= 0) { + aSpec += kSizeStrings[mIconSize]; + } else { + char buf[20]; + SprintfLiteral(buf, "%d", mSize); + aSpec.Append(buf); + } + + if (mIconState >= 0) { + aSpec += "&state="; + aSpec += kStateStrings[mIconState]; + } + + if (!mContentType.IsEmpty()) { + aSpec += "&contentType="; + aSpec += mContentType.get(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetSpecIgnoringRef(nsACString& result) +{ + return GetSpec(result); +} + +NS_IMETHODIMP +nsMozIconURI::GetHasRef(bool* result) +{ + *result = false; + return NS_OK; +} + +// takes a string like ?size=32&contentType=text/html and returns a new string +// containing just the attribute value. i.e you could pass in this string with +// an attribute name of 'size=', this will return 32 +// Assumption: attribute pairs in the string are separated by '&'. +void +extractAttributeValue(const char* aSearchString, + const char* aAttributeName, + nsCString& aResult) +{ + //NS_ENSURE_ARG_POINTER(extractAttributeValue); + + aResult.Truncate(); + + if (aSearchString && aAttributeName) { + // search the string for attributeName + uint32_t attributeNameSize = strlen(aAttributeName); + const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName); + if (startOfAttribute && + ( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') ) { + startOfAttribute += attributeNameSize; // skip over the attributeName + // is there something after the attribute name + if (*startOfAttribute) { + const char* endofAttribute = strchr(startOfAttribute, '&'); + if (endofAttribute) { + aResult.Assign(Substring(startOfAttribute, endofAttribute)); + } else { + aResult.Assign(startOfAttribute); + } + } // if we have a attribute value + } // if we have a attribute name + } // if we got non-null search string and attribute name values +} + +NS_IMETHODIMP +nsMozIconURI::SetSpec(const nsACString& aSpec) +{ + // Reset everything to default values. + mIconURL = nullptr; + mSize = DEFAULT_IMAGE_SIZE; + mContentType.Truncate(); + mFileName.Truncate(); + mStockIcon.Truncate(); + mIconSize = -1; + mIconState = -1; + + nsAutoCString iconSpec(aSpec); + if (!Substring(iconSpec, 0, + MOZICON_SCHEME_LEN).EqualsLiteral(MOZICON_SCHEME)) { + return NS_ERROR_MALFORMED_URI; + } + + int32_t questionMarkPos = iconSpec.Find("?"); + if (questionMarkPos != -1 && + static_cast(iconSpec.Length()) > (questionMarkPos + 1)) { + extractAttributeValue(iconSpec.get(), "contentType=", mContentType); + + nsAutoCString sizeString; + extractAttributeValue(iconSpec.get(), "size=", sizeString); + if (!sizeString.IsEmpty()) { + const char* sizeStr = sizeString.get(); + for (uint32_t i = 0; i < ArrayLength(kSizeStrings); i++) { + if (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) { + mIconSize = i; + break; + } + } + + int32_t sizeValue = atoi(sizeString.get()); + if (sizeValue > 0) { + mSize = sizeValue; + } + } + + nsAutoCString stateString; + extractAttributeValue(iconSpec.get(), "state=", stateString); + if (!stateString.IsEmpty()) { + const char* stateStr = stateString.get(); + for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) { + if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) { + mIconState = i; + break; + } + } + } + } + + int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN; + if (questionMarkPos != -1) { + pathLength = questionMarkPos - MOZICON_SCHEME_LEN; + } + if (pathLength < 3) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength)); + + // Icon URI path can have three forms: + // (1) //stock/ + // (2) // + // (3) a valid URL + + if (!strncmp("//stock/", iconPath.get(), 8)) { + mStockIcon.Assign(Substring(iconPath, 8)); + // An icon identifier must always be specified. + if (mStockIcon.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + return NS_OK; + } + + if (StringBeginsWith(iconPath, NS_LITERAL_CSTRING("//"))) { + // Sanity check this supposed dummy file name. + if (iconPath.Length() > SANE_FILE_NAME_LEN) { + return NS_ERROR_MALFORMED_URI; + } + iconPath.Cut(0, 2); + mFileName.Assign(iconPath); + } + + nsresult rv; + nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr uri; + ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri)); + mIconURL = do_QueryInterface(uri); + if (mIconURL) { + mFileName.Truncate(); + } else if (mFileName.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetPrePath(nsACString& prePath) +{ + prePath = MOZICON_SCHEME; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetScheme(nsACString& aScheme) +{ + aScheme = "moz-icon"; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetScheme(const nsACString& aScheme) +{ + // doesn't make sense to set the scheme of a moz-icon URL + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetUsername(nsACString& aUsername) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetUsername(const nsACString& aUsername) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPassword(nsACString& aPassword) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetPassword(const nsACString& aPassword) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetUserPass(nsACString& aUserPass) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetUserPass(const nsACString& aUserPass) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetHostPort(nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHostPort(const nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHostAndPort(const nsACString& aHostPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetHost(nsACString& aHost) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetHost(const nsACString& aHost) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPort(int32_t* aPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::SetPort(int32_t aPort) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetPath(nsACString& aPath) +{ + aPath.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetPath(const nsACString& aPath) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::GetRef(nsACString& aRef) +{ + aRef.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetRef(const nsACString& aRef) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMozIconURI::Equals(nsIURI* other, bool* result) +{ + *result = false; + NS_ENSURE_ARG_POINTER(other); + NS_PRECONDITION(result, "null pointer"); + + nsAutoCString spec1; + nsAutoCString spec2; + + nsresult rv = GetSpec(spec1); + NS_ENSURE_SUCCESS(rv, rv); + rv = other->GetSpec(spec2); + NS_ENSURE_SUCCESS(rv, rv); + + if (!PL_strcasecmp(spec1.get(), spec2.get())) { + *result = true; + } else { + *result = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // EqualsExceptRef() is the same as Equals(). + return Equals(other, result); +} + +NS_IMETHODIMP +nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals) +{ + NS_ENSURE_ARG_POINTER(aEquals); + if (!aScheme) { + return NS_ERROR_INVALID_ARG; + } + + *aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::Clone(nsIURI** result) +{ + nsCOMPtr newIconURL; + if (mIconURL) { + nsCOMPtr newURI; + nsresult rv = mIconURL->Clone(getter_AddRefs(newURI)); + if (NS_FAILED(rv)) { + return rv; + } + newIconURL = do_QueryInterface(newURI, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsMozIconURI* uri = new nsMozIconURI(); + newIconURL.swap(uri->mIconURL); + uri->mSize = mSize; + uri->mContentType = mContentType; + uri->mFileName = mFileName; + uri->mStockIcon = mStockIcon; + uri->mIconSize = mIconSize; + uri->mIconState = mIconState; + NS_ADDREF(*result = uri); + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::CloneIgnoringRef(nsIURI** result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // CloneIgnoringRef() is the same as Clone(). + return Clone(result); +} + +NS_IMETHODIMP +nsMozIconURI::CloneWithNewRef(const nsACString& newRef, nsIURI** result) +{ + // GetRef/SetRef not supported by nsMozIconURI, so + // CloneWithNewRef() is the same as Clone(). + return Clone(result); +} + + +NS_IMETHODIMP +nsMozIconURI::Resolve(const nsACString& relativePath, nsACString& result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiSpec(nsACString& aSpecA) +{ + return GetSpec(aSpecA); +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiHostPort(nsACString& aHostPortA) +{ + return GetHostPort(aHostPortA); +} + +NS_IMETHODIMP +nsMozIconURI::GetAsciiHost(nsACString& aHostA) +{ + return GetHost(aHostA); +} + +NS_IMETHODIMP +nsMozIconURI::GetOriginCharset(nsACString& result) +{ + result.Truncate(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIIconUri methods: + +NS_IMETHODIMP +nsMozIconURI::GetIconURL(nsIURL** aFileUrl) +{ + *aFileUrl = mIconURL; + NS_IF_ADDREF(*aFileUrl); + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetIconURL(nsIURL* aFileUrl) +{ + // this isn't called anywhere, needs to go through SetSpec parsing + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMozIconURI::GetImageSize(uint32_t* aImageSize) + // measured by # of pixels in a row. defaults to 16. +{ + *aImageSize = mSize; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetImageSize(uint32_t aImageSize) + // measured by # of pixels in a row. defaults to 16. +{ + mSize = aImageSize; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetContentType(nsACString& aContentType) +{ + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::SetContentType(const nsACString& aContentType) +{ + mContentType = aContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetFileExtension(nsACString& aFileExtension) +{ + // First, try to get the extension from mIconURL if we have one + if (mIconURL) { + nsAutoCString fileExt; + if (NS_SUCCEEDED(mIconURL->GetFileExtension(fileExt))) { + if (!fileExt.IsEmpty()) { + // unfortunately, this code doesn't give us the required '.' in + // front of the extension so we have to do it ourselves. + aFileExtension.Assign('.'); + aFileExtension.Append(fileExt); + } + } + return NS_OK; + } + + if (!mFileName.IsEmpty()) { + // truncate the extension out of the file path... + const char* chFileName = mFileName.get(); // get the underlying buffer + const char* fileExt = strrchr(chFileName, '.'); + if (!fileExt) { + return NS_OK; + } + aFileExtension = fileExt; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetStockIcon(nsACString& aStockIcon) +{ + aStockIcon = mStockIcon; + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetIconSize(nsACString& aSize) +{ + if (mIconSize >= 0) { + aSize = kSizeStrings[mIconSize]; + } else { + aSize.Truncate(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::GetIconState(nsACString& aState) +{ + if (mIconState >= 0) { + aState = kStateStrings[mIconState]; + } else { + aState.Truncate(); + } + return NS_OK; +} +//////////////////////////////////////////////////////////////////////////////// +// nsIIPCSerializableURI methods: + +void +nsMozIconURI::Serialize(URIParams& aParams) +{ + IconURIParams params; + + if (mIconURL) { + URIParams iconURLParams; + SerializeURI(mIconURL, iconURLParams); + if (iconURLParams.type() == URIParams::T__None) { + // Serialization failed, bail. + return; + } + + params.uri() = iconURLParams; + } else { + params.uri() = void_t(); + } + + params.size() = mSize; + params.fileName() = mFileName; + params.stockIcon() = mStockIcon; + params.iconSize() = mIconSize; + params.iconState() = mIconState; + + aParams = params; +} + +bool +nsMozIconURI::Deserialize(const URIParams& aParams) +{ + if (aParams.type() != URIParams::TIconURIParams) { + MOZ_ASSERT_UNREACHABLE("Received unknown URI from other process!"); + return false; + } + + const IconURIParams& params = aParams.get_IconURIParams(); + if (params.uri().type() != OptionalURIParams::Tvoid_t) { + nsCOMPtr uri = DeserializeURI(params.uri().get_URIParams()); + mIconURL = do_QueryInterface(uri); + if (!mIconURL) { + MOZ_ASSERT_UNREACHABLE("bad nsIURI passed"); + return false; + } + } + + mSize = params.size(); + mContentType = params.contentType(); + mFileName = params.fileName(); + mStockIcon = params.stockIcon(); + mIconSize = params.iconSize(); + mIconState = params.iconState(); + + return true; +} + +//////////////////////////////////////////////////////////// +// Nested version of nsIconURI + +nsNestedMozIconURI::nsNestedMozIconURI() +{ } + +nsNestedMozIconURI::~nsNestedMozIconURI() +{ } + +NS_IMPL_ISUPPORTS_INHERITED(nsNestedMozIconURI, nsMozIconURI, nsINestedURI) + +NS_IMETHODIMP +nsNestedMozIconURI::GetInnerURI(nsIURI** aURI) +{ + nsCOMPtr iconURL = do_QueryInterface(mIconURL); + if (iconURL) { + iconURL.forget(aURI); + } else { + *aURI = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsNestedMozIconURI::GetInnermostURI(nsIURI** aURI) +{ + return NS_ImplGetInnermostURI(this, aURI); +} + diff --git a/image/decoders/icon/nsIconURI.h b/image/decoders/icon/nsIconURI.h new file mode 100644 index 000000000..1c0310bec --- /dev/null +++ b/image/decoders/icon/nsIconURI.h @@ -0,0 +1,63 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_decoders_icon_nsIconURI_h +#define mozilla_image_decoders_icon_nsIconURI_h + +#include "nsIIconURI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIIPCSerializableURI.h" +#include "nsINestedURI.h" + +class nsMozIconURI : public nsIMozIconURI + , public nsIIPCSerializableURI +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIMOZICONURI + NS_DECL_NSIIPCSERIALIZABLEURI + + // nsMozIconURI + nsMozIconURI(); + +protected: + virtual ~nsMozIconURI(); + nsCOMPtr mIconURL; // a URL that we want the icon for + uint32_t mSize; // the # of pixels in a row that we want for this image. + // Typically 16, 32, 128, etc. + nsCString mContentType; // optional field explicitly specifying the content + // type + nsCString mFileName; // for if we don't have an actual file path, we're just + // given a filename with an extension + nsCString mStockIcon; + int32_t mIconSize; // -1 if not specified, otherwise index into + // kSizeStrings + int32_t mIconState; // -1 if not specified, otherwise index into + // kStateStrings +}; + +// For moz-icon URIs that point to an actual file on disk and are +// therefore nested URIs +class nsNestedMozIconURI final : public nsMozIconURI + , public nsINestedURI +{ + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIURI(nsMozIconURI::) + NS_FORWARD_NSIMOZICONURI(nsMozIconURI::) + NS_FORWARD_NSIIPCSERIALIZABLEURI(nsMozIconURI::) + + NS_DECL_NSINESTEDURI + + nsNestedMozIconURI(); + +protected: + virtual ~nsNestedMozIconURI(); + +}; + +#endif // mozilla_image_decoders_icon_nsIconURI_h diff --git a/image/decoders/icon/win/moz.build b/image/decoders/icon/win/moz.build new file mode 100644 index 000000000..6f763d953 --- /dev/null +++ b/image/decoders/icon/win/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsIconChannel.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp new file mode 100644 index 000000000..9ddcbbc48 --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.cpp @@ -0,0 +1,841 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nsIconChannel.h" +#include "nsIIconURI.h" +#include "nsIServiceManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsMimeTypes.h" +#include "nsMemory.h" +#include "nsIStringStream.h" +#include "nsIURL.h" +#include "nsIOutputStream.h" +#include "nsIPipe.h" +#include "nsNetCID.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIMIMEService.h" +#include "nsCExternalHandlerService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsProxyRelease.h" +#include "nsContentSecurityManager.h" +#include "nsContentUtils.h" + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 + +// we need windows.h to read out registry information... +#include +#include +#include +#include +#include + +using namespace mozilla; + +struct ICONFILEHEADER { + uint16_t ifhReserved; + uint16_t ifhType; + uint16_t ifhCount; +}; + +struct ICONENTRY { + int8_t ieWidth; + int8_t ieHeight; + uint8_t ieColors; + uint8_t ieReserved; + uint16_t iePlanes; + uint16_t ieBitCount; + uint32_t ieSizeImage; + uint32_t ieFileOffset; +}; + +// Match stock icons with names +static SHSTOCKICONID +GetStockIconIDForName(const nsACString& aStockName) +{ + return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD : + SIID_INVALID; +} + +// nsIconChannel methods +nsIconChannel::nsIconChannel() +{ +} + +nsIconChannel::~nsIconChannel() +{ + if (mLoadInfo) { + NS_ReleaseOnMainThread(mLoadInfo.forget()); + } +} + +NS_IMPL_ISUPPORTS(nsIconChannel, + nsIChannel, + nsIRequest, + nsIRequestObserver, + nsIStreamListener) + +nsresult +nsIconChannel::Init(nsIURI* uri) +{ + NS_ASSERTION(uri, "no uri"); + mUrl = uri; + mOriginalURI = uri; + nsresult rv; + mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsIconChannel::GetName(nsACString& result) +{ + return mUrl->GetSpec(result); +} + +NS_IMETHODIMP +nsIconChannel::IsPending(bool* result) +{ + return mPump->IsPending(result); +} + +NS_IMETHODIMP +nsIconChannel::GetStatus(nsresult* status) +{ + return mPump->GetStatus(status); +} + +NS_IMETHODIMP +nsIconChannel::Cancel(nsresult status) +{ + return mPump->Cancel(status); +} + +NS_IMETHODIMP +nsIconChannel::Suspend(void) +{ + return mPump->Suspend(); +} + +NS_IMETHODIMP +nsIconChannel::Resume(void) +{ + return mPump->Resume(); +} +NS_IMETHODIMP +nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) +{ + *aLoadGroup = mLoadGroup; + NS_IF_ADDREF(*aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) +{ + return mPump->GetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) +{ + return mPump->SetLoadFlags(aLoadAttributes); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIChannel methods: + +NS_IMETHODIMP +nsIconChannel::GetOriginalURI(nsIURI** aURI) +{ + *aURI = mOriginalURI; + NS_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOriginalURI(nsIURI* aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + mOriginalURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetURI(nsIURI** aURI) +{ + *aURI = mUrl; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::Open(nsIInputStream** _retval) +{ + return MakeInputStream(_retval, false); +} + +NS_IMETHODIMP +nsIconChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +nsresult +nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, nsCString& aContentType, + nsCString& aFileExtension) +{ + nsresult rv = NS_OK; + nsCOMPtr iconURI (do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + iconURI->GetImageSize(aDesiredImageSize); + iconURI->GetContentType(aContentType); + iconURI->GetFileExtension(aFileExtension); + + nsCOMPtr url; + rv = iconURI->GetIconURL(getter_AddRefs(url)); + if (NS_FAILED(rv) || !url) return NS_OK; + + nsCOMPtr fileURL = do_QueryInterface(url, &rv); + if (NS_FAILED(rv) || !fileURL) return NS_OK; + + nsCOMPtr file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv) || !file) return NS_OK; + + return file->Clone(aLocalFile); +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen(nsIStreamListener* aListener, + nsISupports* ctxt) +{ + MOZ_ASSERT(!mLoadInfo || + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), + "security flags in loadInfo but asyncOpen2() not called"); + + nsCOMPtr inStream; + nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); + if (NS_FAILED(rv)) { + return rv; + } + + // Init our streampump + rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false); + if (NS_FAILED(rv)) { + return rv; + } + + rv = mPump->AsyncRead(this, ctxt); + if (NS_SUCCEEDED(rv)) { + // Store our real listener + mListener = aListener; + // Add ourself to the load group, if available + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + } + return rv; +} + +NS_IMETHODIMP +nsIconChannel::AsyncOpen2(nsIStreamListener* aListener) +{ + nsCOMPtr listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +static DWORD +GetSpecialFolderIcon(nsIFile* aFile, int aFolder, + SHFILEINFOW* aSFI, UINT aInfoFlags) +{ + DWORD shellResult = 0; + + if (!aFile) { + return shellResult; + } + + wchar_t fileNativePath[MAX_PATH]; + nsAutoString fileNativePathStr; + aFile->GetPath(fileNativePathStr); + ::GetShortPathNameW(fileNativePathStr.get(), fileNativePath, + ArrayLength(fileNativePath)); + + LPITEMIDLIST idList; + HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList); + if (SUCCEEDED(hr)) { + wchar_t specialNativePath[MAX_PATH]; + ::SHGetPathFromIDListW(idList, specialNativePath); + ::GetShortPathNameW(specialNativePath, specialNativePath, + ArrayLength(specialNativePath)); + + if (!wcsicmp(fileNativePath, specialNativePath)) { + aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX); + shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0, + aSFI, + sizeof(*aSFI), aInfoFlags); + } + } + CoTaskMemFree(idList); + return shellResult; +} + +static UINT +GetSizeInfoFlag(uint32_t aDesiredImageSize) +{ + return + (UINT) (aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON); +} + +nsresult +nsIconChannel::GetHIconFromFile(HICON* hIcon) +{ + nsXPIDLCString contentType; + nsCString fileExt; + nsCOMPtr localFile; // file we want an icon for + uint32_t desiredImageSize; + nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile), + &desiredImageSize, contentType, + fileExt); + NS_ENSURE_SUCCESS(rv, rv); + + // if the file exists, we are going to use it's real attributes... + // otherwise we only want to use it for it's extension... + SHFILEINFOW sfi; + UINT infoFlags = SHGFI_ICON; + + bool fileExists = false; + + nsAutoString filePath; + CopyASCIItoUTF16(fileExt, filePath); + if (localFile) { + rv = localFile->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + localFile->GetPath(filePath); + if (filePath.Length() < 2 || filePath[1] != ':') { + return NS_ERROR_MALFORMED_URI; // UNC + } + + if (filePath.Last() == ':') { + filePath.Append('\\'); + } else { + localFile->Exists(&fileExists); + if (!fileExists) { + localFile->GetLeafName(filePath); + } + } + } + + if (!fileExists) { + infoFlags |= SHGFI_USEFILEATTRIBUTES; + } + + infoFlags |= GetSizeInfoFlag(desiredImageSize); + + // if we have a content type... then use it! but for existing files, + // we want to show their real icon. + if (!fileExists && !contentType.IsEmpty()) { + nsCOMPtr mimeService + (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString defFileExt; + mimeService->GetPrimaryExtension(contentType, fileExt, defFileExt); + // If the mime service does not know about this mime type, we show + // the generic icon. + // In any case, we need to insert a '.' before the extension. + filePath = NS_LITERAL_STRING(".") + + NS_ConvertUTF8toUTF16(defFileExt); + } + + // Is this the "Desktop" folder? + DWORD shellResult = GetSpecialFolderIcon(localFile, CSIDL_DESKTOP, + &sfi, infoFlags); + if (!shellResult) { + // Is this the "My Documents" folder? + shellResult = GetSpecialFolderIcon(localFile, CSIDL_PERSONAL, + &sfi, infoFlags); + } + + // There are other "Special Folders" and Namespace entities that we + // are not fetching icons for, see: + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ + // shellcc/platform/shell/reference/enums/csidl.asp + // If we ever need to get them, code to do so would be inserted here. + + // Not a special folder, or something else failed above. + if (!shellResult) { + shellResult = ::SHGetFileInfoW(filePath.get(), + FILE_ATTRIBUTE_ARCHIVE, + &sfi, sizeof(sfi), infoFlags); + } + + if (shellResult && sfi.hIcon) { + *hIcon = sfi.hIcon; + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + return rv; +} + +nsresult +nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI, + HICON* hIcon) +{ + nsresult rv = NS_OK; + + // We can only do this on Vista or above + HMODULE hShellDLL = ::LoadLibraryW(L"shell32.dll"); + decltype(SHGetStockIconInfo)* pSHGetStockIconInfo = + (decltype(SHGetStockIconInfo)*) ::GetProcAddress(hShellDLL, + "SHGetStockIconInfo"); + + if (pSHGetStockIconInfo) { + uint32_t desiredImageSize; + aIconURI->GetImageSize(&desiredImageSize); + nsAutoCString stockIcon; + aIconURI->GetStockIcon(stockIcon); + + SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon); + if (stockIconID == SIID_INVALID) { + return NS_ERROR_NOT_AVAILABLE; + } + + UINT infoFlags = SHGSI_ICON; + infoFlags |= GetSizeInfoFlag(desiredImageSize); + + SHSTOCKICONINFO sii = {0}; + sii.cbSize = sizeof(sii); + HRESULT hr = pSHGetStockIconInfo(stockIconID, infoFlags, &sii); + + if (SUCCEEDED(hr)) { + *hIcon = sii.hIcon; + } else { + rv = NS_ERROR_FAILURE; + } + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + + if (hShellDLL) { + ::FreeLibrary(hShellDLL); + } + + return rv; +} + +// Given a BITMAPINFOHEADER, returns the size of the color table. +static int +GetColorTableSize(BITMAPINFOHEADER* aHeader) +{ + int colorTableSize = -1; + + // http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx + switch (aHeader->biBitCount) { + case 0: + colorTableSize = 0; + break; + case 1: + colorTableSize = 2 * sizeof(RGBQUAD); + break; + case 4: + case 8: { + // The maximum possible size for the color table is 2**bpp, so check for + // that and fail if we're not in those bounds + unsigned int maxEntries = 1 << (aHeader->biBitCount); + if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) { + colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD); + } else if (aHeader->biClrUsed == 0) { + colorTableSize = maxEntries * sizeof(RGBQUAD); + } + break; + } + case 16: + case 32: + // If we have BI_BITFIELDS compression, we would normally need 3 DWORDS for + // the bitfields mask which would be stored in the color table; However, + // we instead force the bitmap to request data of type BI_RGB so the color + // table should be of size 0. + // Setting aHeader->biCompression = BI_RGB forces the later call to + // GetDIBits to return to us BI_RGB data. + if (aHeader->biCompression == BI_BITFIELDS) { + aHeader->biCompression = BI_RGB; + } + colorTableSize = 0; + break; + case 24: + colorTableSize = 0; + break; + } + + if (colorTableSize < 0) { + NS_WARNING("Unable to figure out the color table size for this bitmap"); + } + + return colorTableSize; +} + +// Given a header and a size, creates a freshly allocated BITMAPINFO structure. +// It is the caller's responsibility to null-check and delete the structure. +static BITMAPINFO* +CreateBitmapInfo(BITMAPINFOHEADER* aHeader, size_t aColorTableSize) +{ + BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) + + aColorTableSize, + mozilla::fallible); + if (bmi) { + memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER)); + memset(bmi->bmiColors, 0, aColorTableSize); + } + return bmi; +} + +nsresult +nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking) +{ + // Check whether the icon requested's a file icon or a stock icon + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + // GetDIBits does not exist on windows mobile. + HICON hIcon = nullptr; + + nsCOMPtr iconURI(do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (!stockIcon.IsEmpty()) { + rv = GetStockHIcon(iconURI, &hIcon); + } else { + rv = GetHIconFromFile(&hIcon); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (hIcon) { + // we got a handle to an icon. Now we want to get a bitmap for the icon + // using GetIconInfo.... + ICONINFO iconInfo; + if (GetIconInfo(hIcon, &iconInfo)) { + // we got the bitmaps, first find out their size + HDC hDC = CreateCompatibleDC(nullptr); // get a device context for + // the screen. + BITMAPINFOHEADER maskHeader = {sizeof(BITMAPINFOHEADER)}; + BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)}; + int colorTableSize, maskTableSize; + if (GetDIBits(hDC, iconInfo.hbmMask, 0, 0, nullptr, + (BITMAPINFO*)&maskHeader, DIB_RGB_COLORS) && + GetDIBits(hDC, iconInfo.hbmColor, 0, 0, nullptr, + (BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) && + maskHeader.biHeight == colorHeader.biHeight && + maskHeader.biWidth == colorHeader.biWidth && + colorHeader.biBitCount > 8 && + colorHeader.biSizeImage > 0 && + colorHeader.biWidth >= 0 && colorHeader.biWidth <= 255 && + colorHeader.biHeight >= 0 && colorHeader.biHeight <= 255 && + maskHeader.biSizeImage > 0 && + (colorTableSize = GetColorTableSize(&colorHeader)) >= 0 && + (maskTableSize = GetColorTableSize(&maskHeader)) >= 0) { + uint32_t iconSize = sizeof(ICONFILEHEADER) + + sizeof(ICONENTRY) + + sizeof(BITMAPINFOHEADER) + + colorHeader.biSizeImage + + maskHeader.biSizeImage; + + UniquePtr buffer = MakeUnique(iconSize); + if (!buffer) { + rv = NS_ERROR_OUT_OF_MEMORY; + } else { + char* whereTo = buffer.get(); + int howMuch; + + // the data starts with an icon file header + ICONFILEHEADER iconHeader; + iconHeader.ifhReserved = 0; + iconHeader.ifhType = 1; + iconHeader.ifhCount = 1; + howMuch = sizeof(ICONFILEHEADER); + memcpy(whereTo, &iconHeader, howMuch); + whereTo += howMuch; + + // followed by the single icon entry + ICONENTRY iconEntry; + iconEntry.ieWidth = static_cast(colorHeader.biWidth); + iconEntry.ieHeight = static_cast(colorHeader.biHeight); + iconEntry.ieColors = 0; + iconEntry.ieReserved = 0; + iconEntry.iePlanes = 1; + iconEntry.ieBitCount = colorHeader.biBitCount; + iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) + + colorHeader.biSizeImage + + maskHeader.biSizeImage; + iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY); + howMuch = sizeof(ICONENTRY); + memcpy(whereTo, &iconEntry, howMuch); + whereTo += howMuch; + + // followed by the bitmap info header + // (doubling the height because icons have two bitmaps) + colorHeader.biHeight *= 2; + colorHeader.biSizeImage += maskHeader.biSizeImage; + howMuch = sizeof(BITMAPINFOHEADER); + memcpy(whereTo, &colorHeader, howMuch); + whereTo += howMuch; + colorHeader.biHeight /= 2; + colorHeader.biSizeImage -= maskHeader.biSizeImage; + + // followed by the XOR bitmap data (colorHeader) + // (you'd expect the color table to come here, but it apparently + // doesn't) + BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader, + colorTableSize); + if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0, + colorHeader.biHeight, whereTo, colorInfo, + DIB_RGB_COLORS)) { + whereTo += colorHeader.biSizeImage; + + // and finally the AND bitmap data (maskHeader) + BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize); + if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0, + maskHeader.biHeight, whereTo, maskInfo, + DIB_RGB_COLORS)) { + // Now, create a pipe and stuff our data into it + nsCOMPtr inStream; + nsCOMPtr outStream; + rv = NS_NewPipe(getter_AddRefs(inStream), + getter_AddRefs(outStream), + iconSize, iconSize, aNonBlocking); + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = outStream->Write(buffer.get(), iconSize, &written); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*_retval = inStream); + } + } + + } // if we got bitmap bits + delete maskInfo; + } // if we got mask bits + delete colorInfo; + } // if we allocated the buffer + } // if we got mask size + + DeleteDC(hDC); + DeleteObject(iconInfo.hbmColor); + DeleteObject(iconInfo.hbmMask); + } // if we got icon info + DestroyIcon(hIcon); + } // if we got an hIcon + + // If we didn't make a stream, then fail. + if (!*_retval && NS_SUCCEEDED(rv)) { + rv = NS_ERROR_NOT_AVAILABLE; + } + return rv; +} + +NS_IMETHODIMP +nsIconChannel::GetContentType(nsACString& aContentType) +{ + aContentType.AssignLiteral(IMAGE_ICO); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentType(const nsACString& aContentType) +{ + // It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset) +{ + aContentCharset.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentCharset(const nsACString& aContentCharset) +{ + // It doesn't make sense to set the content-charset on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionFilename(nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + SetContentDispositionFilename(const nsAString& aContentDispositionFilename) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel:: + GetContentDispositionHeader(nsACString& aContentDispositionHeader) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentLength(int64_t* aContentLength) +{ + *aContentLength = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentLength(int64_t aContentLength) +{ + NS_NOTREACHED("nsIconChannel::SetContentLength"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsIconChannel::GetOwner(nsISupports** aOwner) +{ + *aOwner = mOwner.get(); + NS_IF_ADDREF(*aOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetOwner(nsISupports* aOwner) +{ + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) +{ + NS_IF_ADDREF(*aLoadInfo = mLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) +{ + mLoadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks) +{ + *aNotificationCallbacks = mCallbacks.get(); + NS_IF_ADDREF(*aNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel:: + SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) +{ + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo) +{ + *aSecurityInfo = nullptr; + return NS_OK; +} + +// nsIRequestObserver methods +NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + if (mListener) { + return mListener->OnStartRequest(this, aContext); + } + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatus) +{ + if (mListener) { + mListener->OnStopRequest(this, aContext, aStatus); + mListener = nullptr; + } + + // Remove from load group + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + } + + // Drop notification callbacks to prevent cycles. + mCallbacks = nullptr; + + return NS_OK; +} + +// nsIStreamListener methods +NS_IMETHODIMP +nsIconChannel::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + if (mListener) { + return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount); + } + return NS_OK; +} diff --git a/image/decoders/icon/win/nsIconChannel.h b/image/decoders/icon/win/nsIconChannel.h new file mode 100644 index 000000000..94df5a52e --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_image_encoders_icon_win_nsIconChannel_h +#define mozilla_image_encoders_icon_win_nsIconChannel_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIChannel.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIURI.h" +#include "nsIInputStreamPump.h" +#include "nsIStreamListener.h" +#include "nsIIconURI.h" + +#include + +class nsIFile; + +class nsIconChannel final : public nsIChannel, public nsIStreamListener +{ + ~nsIconChannel(); + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsIconChannel(); + + nsresult Init(nsIURI* uri); + +protected: + nsCOMPtr mUrl; + nsCOMPtr mOriginalURI; + nsCOMPtr mLoadGroup; + nsCOMPtr mCallbacks; + nsCOMPtr mOwner; + nsCOMPtr mLoadInfo; + + nsCOMPtr mPump; + nsCOMPtr mListener; + + nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsCString& aContentType, + nsCString& aFileExtension); + nsresult GetHIconFromFile(HICON* hIcon); + nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking); + + // Functions specific to Vista and above +protected: + nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon); +}; + +#endif // mozilla_image_encoders_icon_win_nsIconChannel_h -- cgit v1.2.3