summaryrefslogtreecommitdiffstats
path: root/image/decoders/icon
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /image/decoders/icon
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/decoders/icon')
-rw-r--r--image/decoders/icon/android/moz.build13
-rw-r--r--image/decoders/icon/android/nsIconChannel.cpp125
-rw-r--r--image/decoders/icon/android/nsIconChannel.h45
-rw-r--r--image/decoders/icon/components.conf29
-rw-r--r--image/decoders/icon/gtk/moz.build20
-rw-r--r--image/decoders/icon/gtk/nsIconChannel.cpp483
-rw-r--r--image/decoders/icon/gtk/nsIconChannel.h53
-rw-r--r--image/decoders/icon/mac/moz.build13
-rw-r--r--image/decoders/icon/mac/nsIconChannel.h61
-rw-r--r--image/decoders/icon/mac/nsIconChannelCocoa.mm526
-rw-r--r--image/decoders/icon/moz.build39
-rw-r--r--image/decoders/icon/nsIconProtocolHandler.cpp68
-rw-r--r--image/decoders/icon/nsIconProtocolHandler.h25
-rw-r--r--image/decoders/icon/nsIconURI.cpp666
-rw-r--r--image/decoders/icon/nsIconURI.h118
-rw-r--r--image/decoders/icon/win/moz.build21
-rw-r--r--image/decoders/icon/win/nsIconChannel.cpp997
-rw-r--r--image/decoders/icon/win/nsIconChannel.h65
18 files changed, 3367 insertions, 0 deletions
diff --git a/image/decoders/icon/android/moz.build b/image/decoders/icon/android/moz.build
new file mode 100644
index 0000000000..a99ae228d4
--- /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 0000000000..7599cf2bb7
--- /dev/null
+++ b/image/decoders/icon/android/nsIconChannel.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 20; 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 <stdlib.h>
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsMimeTypes.h"
+#include "nsXULAppAPI.h"
+#include "AndroidBridge.h"
+#include "nsIconChannel.h"
+#include "nsIIconURI.h"
+#include "nsIStringStream.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.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
+ nsTArray<uint8_t> 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;
+ CheckedInt32 buf_size =
+ 4 + channels * CheckedInt32(height) * CheckedInt32(width);
+ if (!buf_size.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size.value());
+ uint8_t* out = buf;
+
+ *(out++) = width;
+ *(out++) = height;
+ *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::R8G8B8A8);
+
+ // Set all bits to ensure in nsIconDecoder we color manage and premultiply.
+ *(out++) = 0xFF;
+
+ nsresult rv;
+ if (XRE_IsParentProcess()) {
+ rv = GetIconForExtension(aFileExt, aIconSize, out);
+ } else {
+ rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->AdoptData((char*)buf, buf_size.value());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIconProtocolHandler::NewChannel 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 opened.
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ return NS_NewInputStreamChannel(
+ aChannel, aURI, stream.forget(), nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE, nsLiteralCString(IMAGE_ICON_MS));
+}
+
+nsresult nsIconChannel::Init(nsIURI* aURI) {
+ nsCOMPtr<nsIMozIconURI> 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 0000000000..e25196c6ee
--- /dev/null
+++ b/image/decoders/icon/android/nsIconChannel.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#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 "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<nsIChannel> mRealChannel;
+};
+
+#endif // mozilla_image_decoders_icon_android_nsIconChannel_h
diff --git a/image/decoders/icon/components.conf b/image/decoders/icon/components.conf
new file mode 100644
index 0000000000..68bff8e231
--- /dev/null
+++ b/image/decoders/icon/components.conf
@@ -0,0 +1,29 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{d0f9db12-249c-11d5-9905-001083010e9b}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=moz-icon'],
+ 'type': 'nsIconProtocolHandler',
+ 'headers': ['/image/decoders/icon/nsIconProtocolHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'moz-icon',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_IS_UI_RESOURCE',
+ 'URI_IS_LOCAL_RESOURCE',
+ ],
+ 'default_port': 0,
+ },
+ },
+ {
+ 'cid': '{1460df3b-774c-4205-8349-838e507c3ef9}',
+ 'type': 'nsMozIconURI::Mutator',
+ 'headers': ['/image/decoders/icon/nsIconURI.h'],
+ },
+]
diff --git a/image/decoders/icon/gtk/moz.build b/image/decoders/icon/gtk/moz.build
new file mode 100644
index 0000000000..3eac86b6ba
--- /dev/null
+++ b/image/decoders/icon/gtk/moz.build
@@ -0,0 +1,20 @@
+# -*- 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",
+]
+
+EXPORTS += [
+ "nsIconChannel.h",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp
new file mode 100644
index 0000000000..2b8b958b9e
--- /dev/null
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -0,0 +1,483 @@
+/* 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 <stdlib.h>
+#include <unistd.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/ipc/ByteBuf.h"
+#include <algorithm>
+
+#include <gio/gio.h>
+
+#include <gtk/gtk.h>
+
+#include "nsMimeTypes.h"
+#include "nsIMIMEService.h"
+
+#include "nsServiceManagerUtils.h"
+
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIStringStream.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "nsIPipe.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "prlink.h"
+#include "gfxPlatform.h"
+
+using mozilla::CheckedInt32;
+using mozilla::ipc::ByteBuf;
+
+NS_IMPL_ISUPPORTS(nsIconChannel, nsIRequest, nsIChannel)
+
+static nsresult MozGdkPixbufToByteBuf(GdkPixbuf* aPixbuf, ByteBuf* aByteBuf) {
+ 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;
+ CheckedInt32 buf_size =
+ 4 + n_channels * CheckedInt32(height) * CheckedInt32(width);
+ if (!buf_size.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size.value());
+ uint8_t* out = buf;
+
+ *(out++) = width;
+ *(out++) = height;
+ *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::OS_RGBA);
+
+ // Set all bits to ensure in nsIconDecoder we color manage and premultiply.
+ *(out++) = 0xFF;
+
+ const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
+ int instride = gdk_pixbuf_get_rowstride(aPixbuf);
+ int outstride = width * n_channels;
+
+ // encode the RGB data and the A data and adjust the stride as necessary.
+ mozilla::gfx::SwizzleData(pixels, instride,
+ mozilla::gfx::SurfaceFormat::R8G8B8A8, out,
+ outstride, mozilla::gfx::SurfaceFormat::OS_RGBA,
+ mozilla::gfx::IntSize(width, height));
+
+ *aByteBuf = ByteBuf(buf, buf_size.value(), buf_size.value());
+ return NS_OK;
+}
+
+static nsresult ByteBufToStream(ByteBuf&& aBuf, nsIInputStream** aStream) {
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // stream takes ownership of buf and will free it on destruction.
+ // This function cannot fail.
+ rv = stream->AdoptData(reinterpret_cast<char*>(aBuf.mData), aBuf.mLen);
+ MOZ_ASSERT(CheckedInt32(aBuf.mLen).isValid(),
+ "aBuf.mLen should fit in int32_t");
+ aBuf.mData = nullptr;
+
+ // If this no longer holds then re-examine buf's lifetime.
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+static nsresult StreamToChannel(already_AddRefed<nsIInputStream> aStream,
+ nsIURI* aURI, nsIChannel** aChannel) {
+ // nsIconProtocolHandler::NewChannel 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 opened.
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ mozilla::NullPrincipal::CreateWithoutOriginAttributes();
+ return NS_NewInputStreamChannel(
+ aChannel, aURI, std::move(aStream), nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE, nsLiteralCString(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;
+}
+
+static int32_t GetIconSize(nsIMozIconURI* aIconURI) {
+ nsAutoCString iconSizeString;
+
+ aIconURI->GetIconSize(iconSizeString);
+ if (iconSizeString.IsEmpty()) {
+ uint32_t size;
+ mozilla::DebugOnly<nsresult> rv = aIconURI->GetImageSize(&size);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed");
+ return size;
+ }
+ 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;
+}
+
+/* static */
+nsresult nsIconChannel::GetIconWithGIO(nsIMozIconURI* aIconURI,
+ ByteBuf* aDataOut) {
+ GIcon* icon = nullptr;
+ nsCOMPtr<nsIURL> fileURI;
+
+ // Read icon content
+ aIconURI->GetIconURL(getter_AddRefs(fileURI));
+
+ // Get icon for file specified by URI
+ if (fileURI) {
+ nsAutoCString spec;
+ fileURI->GetAsciiSpec(spec);
+ if (fileURI->SchemeIs("file")) {
+ 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<nsIMIMEService> 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 = MozGdkPixbufToByteBuf(buf, aDataOut);
+ g_object_unref(buf);
+ return rv;
+}
+
+/* static */
+nsresult nsIconChannel::GetIcon(nsIURI* aURI, ByteBuf* aDataOut) {
+ nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
+ NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
+
+ if (!iconURI) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (gfxPlatform::IsHeadless()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString stockIcon;
+ iconURI->GetStockIcon(stockIcon);
+ if (stockIcon.IsEmpty()) {
+ return GetIconWithGIO(iconURI, aDataOut);
+ }
+
+ // 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, "-ltr"_ns)) {
+ direction = GTK_TEXT_DIR_LTR;
+ } else if (StringEndsWith(stockIcon, "-rtl"_ns)) {
+ 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 paths 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 chosen 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 = MozGdkPixbufToByteBuf(icon, aDataOut);
+
+ g_object_unref(icon);
+
+ return rv;
+}
+
+nsresult nsIconChannel::Init(nsIURI* aURI) {
+ nsCOMPtr<nsIInputStream> stream;
+
+ using ContentChild = mozilla::dom::ContentChild;
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ // Get the icon via IPC and translate the promise of a ByteBuf
+ // into an actually-existing channel.
+ RefPtr<ContentChild::GetSystemIconPromise> icon =
+ contentChild->SendGetSystemIcon(aURI);
+ if (!icon) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream), true,
+ false, 0, UINT32_MAX);
+
+ // FIXME: Bug 1718324
+ // The GetSystemIcon() call will end up on the parent doing GetIcon()
+ // and by using ByteBuf we might not be immune to some deadlock, at least
+ // on paper. From analysis in
+ // https://phabricator.services.mozilla.com/D118596#3865440 we should be
+ // safe in practice, but it would be nicer to just write that differently.
+
+ icon->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [outputStream](std::tuple<nsresult, mozilla::Maybe<ByteBuf>>&& aArg) {
+ nsresult rv = std::get<0>(aArg);
+ mozilla::Maybe<ByteBuf> bytes = std::move(std::get<1>(aArg));
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_RELEASE_ASSERT(bytes);
+ uint32_t written;
+ rv = outputStream->Write(reinterpret_cast<char*>(bytes->mData),
+ static_cast<uint32_t>(bytes->mLen),
+ &written);
+ if (NS_SUCCEEDED(rv)) {
+ const bool wroteAll = static_cast<size_t>(written) == bytes->mLen;
+ MOZ_ASSERT(wroteAll);
+ if (!wroteAll) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ } else {
+ MOZ_ASSERT(!bytes);
+ }
+
+ if (NS_FAILED(rv)) {
+ outputStream->CloseWithStatus(rv);
+ }
+ },
+ [outputStream](mozilla::ipc::ResponseRejectReason) {
+ outputStream->CloseWithStatus(NS_ERROR_FAILURE);
+ });
+
+ stream = inputStream.forget();
+ } else {
+ // Get the icon directly.
+ ByteBuf bytebuf;
+ nsresult rv = GetIcon(aURI, &bytebuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ByteBufToStream(std::move(bytebuf), getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return StreamToChannel(stream.forget(), aURI, getter_AddRefs(mRealChannel));
+}
+
+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 0000000000..6ad26602d0
--- /dev/null
+++ b/image/decoders/icon/gtk/nsIconChannel.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; 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/. */
+
+#ifndef mozilla_image_decoders_icon_gtk_nsIconChannel_h
+#define mozilla_image_decoders_icon_gtk_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsIIconURI.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::ipc {
+class ByteBuf;
+}
+
+/// This class is the GTK implementation of nsIconChannel. It asks
+/// GTK for the icon, translates the pixel data in-memory into
+/// nsIconDecoder format, and proxies the nsChannel interface to a new
+/// channel returning that image.
+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);
+
+ /// Obtains an icon, in nsIconDecoder format, as a ByteBuf instead
+ /// of a channel. For use with IPC.
+ static nsresult GetIcon(nsIURI* aURI, mozilla::ipc::ByteBuf* aDataOut);
+
+ private:
+ ~nsIconChannel() {}
+ /// The input stream channel which will yield the image.
+ /// Will always be non-null after a successful Init.
+ nsCOMPtr<nsIChannel> mRealChannel;
+
+ static nsresult GetIconWithGIO(nsIMozIconURI* aIconURI,
+ mozilla::ipc::ByteBuf* aDataOut);
+};
+
+#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 0000000000..3467659a8f
--- /dev/null
+++ b/image/decoders/icon/mac/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 += [
+ "nsIconChannelCocoa.mm",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h
new file mode 100644
index 0000000000..dca2a3c51a
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannel.h
@@ -0,0 +1,61 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h
+#define mozilla_image_encoders_icon_mac_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.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"
+#include "nsNetUtil.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<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIStreamListener> mListener;
+ bool mCanceled = false;
+
+ [[nodiscard]] 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 0000000000..108fe2f315
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -0,0 +1,526 @@
+/* -*- 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 "nsContentUtils.h"
+#include "nsIconChannel.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/EndianUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIIconURI.h"
+#include "nsIInputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsString.h"
+#include "nsMimeTypes.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsCExternalHandlerService.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+#include "nsTArray.h"
+#include "nsObjCExceptions.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+#include "nsNetUtil.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include <Cocoa/Cocoa.h>
+
+using namespace mozilla;
+
+// nsIconChannel methods
+nsIconChannel::nsIconChannel() {}
+
+nsIconChannel::~nsIconChannel() {
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread("nsIconChannel::mLoadInfo", 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::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIconChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIconChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Cancel(nsresult status) {
+ mCanceled = true;
+ return mPump->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetCanceled(bool* result) {
+ *result = mCanceled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::Suspend(void) { return mPump->Suspend(); }
+
+NS_IMETHODIMP
+nsIconChannel::Resume(void) { return mPump->Resume(); }
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsIconChannel::OnStartRequest(nsIRequest* aRequest) {
+ if (mListener) {
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ if (mListener) {
+ mListener->OnStopRequest(this, 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, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (mListener) {
+ return mListener->OnDataAvailable(this, 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) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return MakeInputStream(_retval, false);
+}
+
+nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsACString& aContentType,
+ nsACString& aFileExtension) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ iconURI->GetImageSize(aDesiredImageSize);
+ iconURI->GetContentType(aContentType);
+ iconURI->GetFileExtension(aFileExtension);
+
+ nsCOMPtr<nsIURL> url;
+ rv = iconURI->GetIconURL(getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return NS_OK;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
+ if (NS_FAILED(rv) || !fileURL) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv) || !file) return NS_OK;
+
+ nsCOMPtr<nsILocalFileMac> 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) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = MakeInputStream(getter_AddRefs(inStream), true);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // Init our stream pump
+ nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
+ rv = mPump->Init(inStream, 0, 0, false, target);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ rv = mPump->AsyncRead(this);
+ if (NS_SUCCEEDED(rv)) {
+ // Store our real listener
+ mListener = aListener;
+ // Add ourself to the load group, if available
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ } else {
+ mCallbacks = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval,
+ bool aNonBlocking) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ nsCString contentType;
+ nsAutoCString fileExt;
+ nsCOMPtr<nsIFile> 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) {
+ fileloc->Exists(&fileExists);
+ }
+
+ NSImage* iconImage = nil;
+
+ // first try to get the icon from the file if it exists
+ if (fileExists) {
+ nsCOMPtr<nsILocalFileMac> 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;
+ }
+
+ if (desiredImageSize > 255) {
+ // The Icon image format represents width and height as u8, so it does not
+ // allow for images sized 256 or more.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the actual size to *twice* the requested size.
+ // We do this because our UI doesn't take the window's device pixel ratio into
+ // account when it requests these icons; e.g. it will request an icon with
+ // size 16, place it in a 16x16 CSS pixel sized image, and then display it in
+ // a window on a HiDPI screen where the icon now covers 32x32 physical screen
+ // pixels. So we just always double the size here in order to prevent
+ // blurriness.
+ uint32_t size =
+ (desiredImageSize < 128) ? desiredImageSize * 2 : desiredImageSize;
+ uint32_t width = size;
+ uint32_t height = size;
+
+ // The "image format" we're outputting here (and which gets decoded by
+ // nsIconDecoder) has the following format:
+ // - 1 byte for the image width, as u8
+ // - 1 byte for the image height, as u8
+ // - the raw image data as BGRA, width * height * 4 bytes.
+ size_t bufferCapacity = 4 + width * height * 4;
+ UniquePtr<uint8_t[]> fileBuf = MakeUniqueFallible<uint8_t[]>(bufferCapacity);
+ if (NS_WARN_IF(!fileBuf)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ fileBuf[0] = uint8_t(width);
+ fileBuf[1] = uint8_t(height);
+ fileBuf[2] = uint8_t(mozilla::gfx::SurfaceFormat::B8G8R8A8);
+
+ // Clear all bits to ensure in nsIconDecoder we assume we are already color
+ // managed and premultiplied.
+ fileBuf[3] = 0;
+
+ uint8_t* imageBuf = &fileBuf[4];
+
+ // Create a CGBitmapContext around imageBuf and draw iconImage to it.
+ // This gives us the image data in the format we want: BGRA, four bytes per
+ // pixel, in host endianness, with premultiplied alpha.
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx = CGBitmapContextCreate(
+ imageBuf, width, height, 8 /* bitsPerComponent */, width * 4, cs,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(cs);
+
+ NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext
+ setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx
+ flipped:NO]];
+
+ [iconImage drawInRect:NSMakeRect(0, 0, width, height)];
+
+ [NSGraphicsContext setCurrentContext:oldContext];
+
+ CGContextRelease(ctx);
+
+ // Now, create a pipe and stuff our data into it
+ nsCOMPtr<nsIInputStream> inStream;
+ nsCOMPtr<nsIOutputStream> outStream;
+ NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
+ bufferCapacity, bufferCapacity, aNonBlocking);
+
+ uint32_t written;
+ rv = outStream->Write((char*)fileBuf.get(), 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_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+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::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+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) {
+ MOZ_ASSERT_UNREACHABLE("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) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ 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(nsITransportSecurityInfo** 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 0000000000..96cf951b3b
--- /dev/null
+++ b/image/decoders/icon/moz.build
@@ -0,0 +1,39 @@
+# -*- 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 += [
+ "nsIconProtocolHandler.cpp",
+ "nsIconURI.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+EXPORTS += [
+ "nsIconURI.h",
+]
+
+platform = None
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ 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/nsIconProtocolHandler.cpp b/image/decoders/icon/nsIconProtocolHandler.cpp
new file mode 100644
index 0000000000..9334f908db
--- /dev/null
+++ b/image/decoders/icon/nsIconProtocolHandler.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "nsIconProtocolHandler.h"
+
+#include "nsIconChannel.h"
+#include "nsIconURI.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.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::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconProtocolHandler::NewChannel(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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/image/decoders/icon/nsIconProtocolHandler.h b/image/decoders/icon/nsIconProtocolHandler.h
new file mode 100644
index 0000000000..63843eaa4b
--- /dev/null
+++ b/image/decoders/icon/nsIconProtocolHandler.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#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 0000000000..49e5302082
--- /dev/null
+++ b/image/decoders/icon/nsIconURI.cpp
@@ -0,0 +1,666 @@
+/* -*- 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 "nsIClassInfoImpl.h"
+#include "nsIIOService.h"
+#include "nsISerializable.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "plstr.h"
+#include "nsCRT.h"
+#include <stdlib.h>
+
+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
+
+static NS_DEFINE_CID(kThisIconURIImplementationCID,
+ NS_THIS_ICONURI_IMPLEMENTATION_CID);
+
+static const char* const kSizeStrings[] = {"button", "toolbar", "toolbarsmall",
+ "menu", "dnd", "dialog"};
+
+static const char* const kStateStrings[] = {"normal", "disabled"};
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_CLASSINFO(nsMozIconURI, nullptr, nsIClassInfo::THREADSAFE,
+ NS_ICONURI_CID)
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(nsMozIconURI)
+
+nsMozIconURI::nsMozIconURI()
+ : mSize(DEFAULT_IMAGE_SIZE), mIconSize(-1), mIconState(-1) {}
+
+nsMozIconURI::~nsMozIconURI() {}
+
+NS_IMPL_ADDREF(nsMozIconURI)
+NS_IMPL_RELEASE(nsMozIconURI)
+
+NS_INTERFACE_MAP_BEGIN(nsMozIconURI)
+ if (aIID.Equals(kThisIconURIImplementationCID)) {
+ foundInterface = static_cast<nsIURI*>(this);
+ } else
+ NS_INTERFACE_MAP_ENTRY(nsIMozIconURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINestedURI, mIconURL)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_IMPL_QUERY_CLASSINFO(nsMozIconURI)
+NS_INTERFACE_MAP_END
+
+#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::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return GetSpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ return GetHostPort(aUnicodeHostPort);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetDisplayHost(nsACString& aUnicodeHost) {
+ return GetHost(aUnicodeHost);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetDisplayPrePath(nsACString& aPrePath) {
+ return GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHasRef(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHasUserPass(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHasQuery(bool* result) {
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsMozIconURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable)
+
+NS_IMETHODIMP
+nsMozIconURI::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsMozIconURI::Mutator> mutator = new nsMozIconURI::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+// helper function for parsing out attributes like size, and contentType
+// from the icon url.
+// 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 '&'.
+static void extractAttributeValue(const char* aSearchString,
+ const char* aAttributeName,
+ nsCString& aResult) {
+ 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
+}
+
+nsresult nsMozIconURI::SetSpecInternal(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) ||
+ (!Substring(iconSpec, MOZICON_SCHEME_LEN, 7).EqualsLiteral("file://") &&
+ // Checking for the leading '//' will match both the '//stock/' and
+ // '//.foo' cases:
+ !Substring(iconSpec, MOZICON_SCHEME_LEN, 2).EqualsLiteral("//"))) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ int32_t questionMarkPos = iconSpec.Find("?");
+ if (questionMarkPos != -1 &&
+ static_cast<int32_t>(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 (nsCRT::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 (nsCRT::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/<icon-identifier>
+ // (2) //<some dummy file with an extension>
+ // (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)) {
+ // 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<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri));
+ mIconURL = do_QueryInterface(uri);
+ if (mIconURL) {
+ // The inner URI should be a 'file:' one. If not, bail.
+ if (!mIconURL->SchemeIs("file")) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ 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;
+}
+
+nsresult 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; }
+
+nsresult nsMozIconURI::SetUsername(const nsACString& aUsername) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPassword(nsACString& aPassword) { return NS_ERROR_FAILURE; }
+
+nsresult nsMozIconURI::SetPassword(const nsACString& aPassword) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetUserPass(nsACString& aUserPass) { return NS_ERROR_FAILURE; }
+
+nsresult nsMozIconURI::SetUserPass(const nsACString& aUserPass) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHostPort(nsACString& aHostPort) { return NS_ERROR_FAILURE; }
+
+nsresult nsMozIconURI::SetHostPort(const nsACString& aHostPort) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetHost(nsACString& aHost) { return NS_ERROR_FAILURE; }
+
+nsresult nsMozIconURI::SetHost(const nsACString& aHost) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetPort(int32_t* aPort) { return NS_ERROR_FAILURE; }
+
+nsresult nsMozIconURI::SetPort(int32_t aPort) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+nsMozIconURI::GetPathQueryRef(nsACString& aPath) {
+ aPath.Truncate();
+ return NS_OK;
+}
+
+nsresult nsMozIconURI::SetPathQueryRef(const nsACString& aPath) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetFilePath(nsACString& aFilePath) {
+ aFilePath.Truncate();
+ return NS_OK;
+}
+
+nsresult nsMozIconURI::SetFilePath(const nsACString& aFilePath) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetQuery(nsACString& aQuery) {
+ aQuery.Truncate();
+ return NS_OK;
+}
+
+nsresult nsMozIconURI::SetQuery(const nsACString& aQuery) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsMozIconURI::SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* aEncoding) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetRef(nsACString& aRef) {
+ aRef.Truncate();
+ return NS_OK;
+}
+
+nsresult nsMozIconURI::SetRef(const nsACString& aRef) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Equals(nsIURI* other, bool* result) {
+ *result = false;
+ NS_ENSURE_ARG_POINTER(other);
+ MOZ_ASSERT(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 (!nsCRT::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) {
+ MOZ_ASSERT(aEquals, "null pointer");
+ if (!aScheme) {
+ *aEquals = false;
+ return NS_OK;
+ }
+
+ *aEquals = nsCRT::strcasecmp("moz-icon", aScheme) == 0;
+ return NS_OK;
+}
+
+nsresult nsMozIconURI::Clone(nsIURI** result) {
+ nsCOMPtr<nsIURL> newIconURL;
+ if (mIconURL) {
+ newIconURL = mIconURL;
+ }
+
+ RefPtr<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;
+ uri.forget(result);
+
+ return NS_OK;
+}
+
+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); }
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIconUri methods:
+
+NS_IMETHODIMP
+nsMozIconURI::GetIconURL(nsIURL** aFileUrl) {
+ *aFileUrl = mIconURL;
+ NS_IF_ADDREF(*aFileUrl);
+ return NS_OK;
+}
+
+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::GetContentType(nsACString& aContentType) {
+ aContentType = mContentType;
+ 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;
+}
+
+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() = Some(std::move(iconURLParams));
+ } else {
+ params.uri() = Nothing();
+ }
+
+ 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().isSome()) {
+ nsCOMPtr<nsIURI> uri = DeserializeURI(params.uri().ref());
+ 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();
+
+ if (params.iconSize() < -1 ||
+ params.iconSize() >= (int32_t)ArrayLength(kSizeStrings)) {
+ return false;
+ }
+ mIconSize = params.iconSize();
+
+ if (params.iconState() < -1 ||
+ params.iconState() >= (int32_t)ArrayLength(kStateStrings)) {
+ return false;
+ }
+ mIconState = params.iconState();
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetInnerURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> iconURL = mIconURL;
+ if (!iconURL) {
+ *aURI = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ iconURL.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetInnermostURI(nsIURI** aURI) {
+ return NS_ImplGetInnermostURI(this, aURI);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMozIconURI::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsAutoCString spec;
+ nsresult rv = aStream->ReadCString(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetSpecInternal(spec);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Write(nsIObjectOutputStream* aStream) {
+ nsAutoCString spec;
+ nsresult rv = GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aStream->WriteStringZ(spec.get());
+}
diff --git a/image/decoders/icon/nsIconURI.h b/image/decoders/icon/nsIconURI.h
new file mode 100644
index 0000000000..1f55bc686c
--- /dev/null
+++ b/image/decoders/icon/nsIconURI.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#ifndef mozilla_image_decoders_icon_nsIconURI_h
+#define mozilla_image_decoders_icon_nsIconURI_h
+
+#include "nsIIconURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsINestedURI.h"
+#include "nsIURIMutator.h"
+#include "nsISerializable.h"
+
+#define NS_THIS_ICONURI_IMPLEMENTATION_CID \
+ { /* 0b9bb0c2-fee6-470b-b9b9-9fd9462b5e19 */ \
+ 0x5c3e417f, 0xb686, 0x4105, { \
+ 0x86, 0xe7, 0xf9, 0x1b, 0xac, 0x97, 0x4d, 0x5c \
+ } \
+ }
+
+namespace mozilla {
+class Encoding;
+}
+
+class nsMozIconURI final : public nsIMozIconURI,
+ public nsINestedURI,
+ public nsISerializable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIMOZICONURI
+ NS_DECL_NSINESTEDURI
+ NS_DECL_NSISERIALIZABLE
+
+ protected:
+ nsMozIconURI();
+ virtual ~nsMozIconURI();
+ nsCOMPtr<nsIURL> 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
+
+ private:
+ nsresult Clone(nsIURI** aURI);
+ nsresult SetSpecInternal(const nsACString& input);
+ nsresult SetScheme(const nsACString& input);
+ nsresult SetUserPass(const nsACString& input);
+ nsresult SetUsername(const nsACString& input);
+ nsresult SetPassword(const nsACString& input);
+ nsresult SetHostPort(const nsACString& aValue);
+ nsresult SetHost(const nsACString& input);
+ nsresult SetPort(int32_t port);
+ nsresult SetPathQueryRef(const nsACString& input);
+ nsresult SetRef(const nsACString& input);
+ nsresult SetFilePath(const nsACString& input);
+ nsresult SetQuery(const nsACString& input);
+ nsresult SetQueryWithEncoding(const nsACString& input,
+ const mozilla::Encoding* encoding);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+ bool Deserialize(const mozilla::ipc::URIParams&);
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsMozIconURI>,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override {
+ return InitFromIPCParams(aParams);
+ }
+
+ NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) {
+ nsCOMPtr<nsIURIMutator> mutator = this;
+ mutator.forget(aMutator);
+ }
+ return InitFromSpec(aSpec);
+ }
+
+ explicit Mutator() {}
+
+ private:
+ virtual ~Mutator() {}
+
+ friend class nsMozIconURI;
+ };
+
+ friend BaseURIMutator<nsMozIconURI>;
+};
+
+#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 0000000000..c4472f89ac
--- /dev/null
+++ b/image/decoders/icon/win/moz.build
@@ -0,0 +1,21 @@
+# -*- 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",
+]
+
+EXPORTS += [
+ "nsIconChannel.h",
+]
+
+LOCAL_INCLUDES += [
+ "/image",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp
new file mode 100644
index 0000000000..6c93dc3bf2
--- /dev/null
+++ b/image/decoders/icon/win/nsIconChannel.cpp
@@ -0,0 +1,997 @@
+/* -*- 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/ArrayUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/ByteBuf.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIconChannel.h"
+#include "nsIIconURI.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIURL.h"
+#include "nsIPipe.h"
+#include "nsNetCID.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "nsIIconURI.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+
+#include "Decoder.h"
+#include "DecodePool.h"
+
+// we need windows.h to read out registry information...
+#include <windows.h>
+#include <shellapi.h>
+#include <shlobj.h>
+#include <objbase.h>
+#include <wchar.h>
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+using mozilla::ipc::ByteBuf;
+
+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;
+};
+
+struct IconPathInfo {
+ nsCOMPtr<nsIFile> localFile;
+ nsAutoString filePath;
+ UINT infoFlags = 0;
+};
+
+using HIconPromise = MozPromise<HICON, nsresult, true>;
+
+static UINT GetSizeInfoFlag(uint32_t aDesiredImageSize) {
+ return aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON;
+}
+
+static nsresult ExtractIconInfoFromUrl(nsIURI* aUrl, nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsCString& aContentType,
+ nsCString& aFileExtension) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(aUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ iconURI->GetImageSize(aDesiredImageSize);
+ iconURI->GetContentType(aContentType);
+ iconURI->GetFileExtension(aFileExtension);
+
+ nsCOMPtr<nsIURL> url;
+ rv = iconURI->GetIconURL(getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return NS_OK;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
+ if (NS_FAILED(rv) || !fileURL) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv) || !file) return NS_OK;
+
+ return file->Clone(aLocalFile);
+}
+
+static nsresult ExtractIconPathInfoFromUrl(nsIURI* aUrl,
+ IconPathInfo* aIconPathInfo) {
+ nsCString contentType;
+ nsCString fileExt;
+ nsCOMPtr<nsIFile> localFile; // file we want an icon for
+ uint32_t desiredImageSize;
+ nsresult rv = ExtractIconInfoFromUrl(aUrl, 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...
+ 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<nsIMIMEService> 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 = u"."_ns + NS_ConvertUTF8toUTF16(defFileExt);
+ }
+
+ if (!localFile && !fileExists &&
+ ((filePath.Length() == 1 && filePath.Last() == '.') ||
+ filePath.Length() == 0)) {
+ filePath = u".MozBogusExtensionMoz"_ns;
+ }
+
+ aIconPathInfo->localFile = std::move(localFile);
+ aIconPathInfo->filePath = std::move(filePath);
+ aIconPathInfo->infoFlags = infoFlags;
+
+ return NS_OK;
+}
+
+static bool GetSpecialFolderIcon(nsIFile* aFile, int aFolder, UINT aInfoFlags,
+ HICON* aIcon) {
+ if (!aFile) {
+ return false;
+ }
+
+ wchar_t fileNativePath[MAX_PATH];
+ nsAutoString fileNativePathStr;
+ aFile->GetPath(fileNativePathStr);
+ ::GetShortPathNameW(fileNativePathStr.get(), fileNativePath,
+ ArrayLength(fileNativePath));
+
+ struct IdListDeleter {
+ void operator()(ITEMIDLIST* ptr) { ::CoTaskMemFree(ptr); }
+ };
+
+ UniquePtr<ITEMIDLIST, IdListDeleter> idList;
+ HRESULT hr =
+ ::SHGetSpecialFolderLocation(nullptr, aFolder, getter_Transfers(idList));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ wchar_t specialNativePath[MAX_PATH];
+ ::SHGetPathFromIDListW(idList.get(), specialNativePath);
+ ::GetShortPathNameW(specialNativePath, specialNativePath,
+ ArrayLength(specialNativePath));
+
+ if (wcsicmp(fileNativePath, specialNativePath) != 0) {
+ return false;
+ }
+
+ SHFILEINFOW sfi = {};
+ aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX);
+ if (::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList.get(), 0, &sfi,
+ sizeof(sfi), aInfoFlags) == 0) {
+ return false;
+ }
+
+ *aIcon = sfi.hIcon;
+ return true;
+}
+
+static nsresult GetIconHandleFromPathInfo(const IconPathInfo& aPathInfo,
+ HICON* aIcon) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
+
+ // Is this the "Desktop" folder?
+ if (GetSpecialFolderIcon(aPathInfo.localFile, CSIDL_DESKTOP,
+ aPathInfo.infoFlags, aIcon)) {
+ return NS_OK;
+ }
+
+ // Is this the "My Documents" folder?
+ if (GetSpecialFolderIcon(aPathInfo.localFile, CSIDL_PERSONAL,
+ aPathInfo.infoFlags, aIcon)) {
+ return NS_OK;
+ }
+
+ // 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.
+ SHFILEINFOW sfi = {};
+ if (::SHGetFileInfoW(aPathInfo.filePath.get(), FILE_ATTRIBUTE_ARCHIVE, &sfi,
+ sizeof(sfi), aPathInfo.infoFlags) != 0) {
+ *aIcon = sfi.hIcon;
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// Match stock icons with names
+static mozilla::Maybe<SHSTOCKICONID> GetStockIconIDForName(
+ const nsACString& aStockName) {
+ return aStockName.EqualsLiteral("uac-shield") ? Some(SIID_SHIELD) : Nothing();
+}
+
+// Specific to Vista and above
+static nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* aIcon) {
+ uint32_t desiredImageSize;
+ aIconURI->GetImageSize(&desiredImageSize);
+ nsAutoCString stockIcon;
+ aIconURI->GetStockIcon(stockIcon);
+
+ Maybe<SHSTOCKICONID> stockIconID = GetStockIconIDForName(stockIcon);
+ if (stockIconID.isNothing()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UINT infoFlags = SHGSI_ICON;
+ infoFlags |= GetSizeInfoFlag(desiredImageSize);
+
+ SHSTOCKICONINFO sii = {0};
+ sii.cbSize = sizeof(sii);
+ HRESULT hr = SHGetStockIconInfo(*stockIconID, infoFlags, &sii);
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aIcon = sii.hIcon;
+
+ return NS_OK;
+}
+
+// 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;
+}
+
+static nsresult MakeIconBuffer(HICON aIcon, ByteBuf* aOutBuffer) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aIcon) {
+ // we got a handle to an icon. Now we want to get a bitmap for the icon
+ // using GetIconInfo....
+ ICONINFO iconInfo;
+ if (GetIconInfo(aIcon, &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;
+
+ if (!aOutBuffer->Allocate(iconSize)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ uint8_t* whereTo = aOutBuffer->mData;
+ 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<int8_t>(colorHeader.biWidth);
+ iconEntry.ieHeight = static_cast<int8_t>(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)) {
+ rv = NS_OK;
+ } // 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(aIcon);
+ } // if we got an hIcon
+
+ return rv;
+}
+
+static nsresult GetIconHandleFromURLBlocking(nsIMozIconURI* aUrl,
+ HICON* aIcon) {
+ nsAutoCString stockIcon;
+ aUrl->GetStockIcon(stockIcon);
+ if (!stockIcon.IsEmpty()) {
+ return GetStockHIcon(aUrl, aIcon);
+ }
+
+ IconPathInfo iconPathInfo;
+ nsresult rv = ExtractIconPathInfoFromUrl(aUrl, &iconPathInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "GetIconHandleFromURLBlocking",
+ [&] { rv = GetIconHandleFromPathInfo(iconPathInfo, aIcon); });
+
+ RefPtr<nsIEventTarget> target = DecodePool::Singleton()->GetIOEventTarget();
+
+ nsresult dispatchResult = SyncRunnable::DispatchToThread(target, task);
+ NS_ENSURE_SUCCESS(dispatchResult, dispatchResult);
+
+ return rv;
+}
+
+static RefPtr<HIconPromise> GetIconHandleFromURLAsync(nsIMozIconURI* aUrl) {
+ RefPtr<HIconPromise::Private> promise = new HIconPromise::Private(__func__);
+
+ nsAutoCString stockIcon;
+ aUrl->GetStockIcon(stockIcon);
+ if (!stockIcon.IsEmpty()) {
+ HICON hIcon = nullptr;
+ nsresult rv = GetStockHIcon(aUrl, &hIcon);
+ if (NS_SUCCEEDED(rv)) {
+ promise->Resolve(hIcon, __func__);
+ } else {
+ promise->Reject(rv, __func__);
+ }
+ return promise;
+ }
+
+ IconPathInfo iconPathInfo;
+ nsresult rv = ExtractIconPathInfoFromUrl(aUrl, &iconPathInfo);
+ if (NS_FAILED(rv)) {
+ promise->Reject(rv, __func__);
+ return promise;
+ }
+
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "GetIconHandleFromURLAsync", [iconPathInfo, promise] {
+ HICON hIcon = nullptr;
+ nsresult rv = GetIconHandleFromPathInfo(iconPathInfo, &hIcon);
+ if (NS_SUCCEEDED(rv)) {
+ promise->Resolve(hIcon, __func__);
+ } else {
+ promise->Reject(rv, __func__);
+ }
+ });
+
+ RefPtr<nsIEventTarget> target = DecodePool::Singleton()->GetIOEventTarget();
+
+ rv = target->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ promise->Reject(rv, __func__);
+ }
+
+ return promise;
+}
+
+static RefPtr<nsIconChannel::ByteBufPromise> GetIconBufferFromURLAsync(
+ nsIMozIconURI* aUrl) {
+ RefPtr<nsIconChannel::ByteBufPromise::Private> promise =
+ new nsIconChannel::ByteBufPromise::Private(__func__);
+
+ GetIconHandleFromURLAsync(aUrl)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](HICON aIcon) {
+ ByteBuf iconBuffer;
+ nsresult rv = MakeIconBuffer(aIcon, &iconBuffer);
+ if (NS_SUCCEEDED(rv)) {
+ promise->Resolve(std::move(iconBuffer), __func__);
+ } else {
+ promise->Reject(rv, __func__);
+ }
+ },
+ [promise](nsresult rv) { promise->Reject(rv, __func__); });
+
+ return promise;
+}
+
+static nsresult WriteByteBufToOutputStream(const ByteBuf& aBuffer,
+ nsIAsyncOutputStream* aStream) {
+ uint32_t written = 0;
+ nsresult rv = aStream->Write(reinterpret_cast<const char*>(aBuffer.mData),
+ aBuffer.mLen, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (written == aBuffer.mLen) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+NS_IMPL_ISUPPORTS(nsIconChannel, nsIChannel, nsIRequest, nsIRequestObserver,
+ nsIStreamListener)
+
+// nsIconChannel methods
+nsIconChannel::nsIconChannel() {}
+
+nsIconChannel::~nsIconChannel() {
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread("nsIconChannel::mLoadInfo", mLoadInfo.forget());
+ }
+ if (mLoadGroup) {
+ NS_ReleaseOnMainThread("nsIconChannel::mLoadGroup", mLoadGroup.forget());
+ }
+}
+
+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::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIconChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsIconChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Cancel(nsresult status) {
+ mCanceled = true;
+ return mPump->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetCanceled(bool* result) {
+ *result = mCanceled;
+ return NS_OK;
+}
+
+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);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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;
+}
+
+// static
+RefPtr<nsIconChannel::ByteBufPromise> nsIconChannel::GetIconAsync(
+ nsIURI* aURI) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(aURI, &rv));
+ if (NS_FAILED(rv)) {
+ return ByteBufPromise::CreateAndReject(rv, __func__);
+ }
+
+ return GetIconBufferFromURLAsync(iconURI);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ // Double-check that we are actually an icon URL
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the handle for the given icon URI. This may involve the decode I/O
+ // thread, as we can only call SHGetFileInfo() from that thread
+ //
+ // Since this API is synchronous, this call will not return until the decode
+ // I/O thread returns with the icon handle
+ //
+ // Once we have the handle, we create a Windows ICO buffer with it and
+ // dump the buffer into the output end of the pipe. The input end will
+ // be returned to the caller
+ HICON hIcon = nullptr;
+ rv = GetIconHandleFromURLBlocking(iconURI, &hIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ByteBuf iconBuffer;
+ rv = MakeIconBuffer(hIcon, &iconBuffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the asynchronous pipe with a blocking read end
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
+ false /*nonBlockingInput*/, false /*nonBlockingOutput*/,
+ iconBuffer.mLen /*segmentSize*/, 1 /*segmentCount*/);
+
+ rv = WriteByteBufToOutputStream(iconBuffer, outputStream);
+
+ if (NS_SUCCEEDED(rv)) {
+ inputStream.forget(aStream);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_FAILED(rv)) {
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL &&
+ mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()),
+ "security flags in loadInfo but doContentSecurityCheck() not called");
+
+ mListener = listener;
+
+ rv = StartAsyncOpen();
+ if (NS_FAILED(rv)) {
+ mListener = nullptr;
+ mCallbacks = nullptr;
+ return rv;
+ }
+
+ // Add ourself to the load group, if available
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIconChannel::StartAsyncOpen() {
+ // Double-check that we are actually an icon URL
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the asynchronous pipe with a non-blocking read end
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
+ true /*nonBlockingInput*/, false /*nonBlockingOutput*/,
+ 0 /*segmentSize*/, UINT32_MAX /*segmentCount*/);
+
+ // If we are in content, we asynchronously request the ICO buffer from
+ // the parent process because the APIs to load icons don't work with
+ // Win32k Lockdown
+ using ContentChild = mozilla::dom::ContentChild;
+ if (auto* contentChild = ContentChild::GetSingleton()) {
+ RefPtr<ContentChild::GetSystemIconPromise> iconPromise =
+ contentChild->SendGetSystemIcon(mUrl);
+ if (!iconPromise) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ iconPromise->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [outputStream](std::tuple<nsresult, mozilla::Maybe<ByteBuf>>&& aArg) {
+ nsresult rv = std::get<0>(aArg);
+ mozilla::Maybe<ByteBuf> iconBuffer = std::move(std::get<1>(aArg));
+
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_RELEASE_ASSERT(iconBuffer);
+ rv = WriteByteBufToOutputStream(*iconBuffer, outputStream);
+ }
+
+ outputStream->CloseWithStatus(rv);
+ },
+ [outputStream](mozilla::ipc::ResponseRejectReason) {
+ outputStream->CloseWithStatus(NS_ERROR_FAILURE);
+ });
+ } else {
+ // Get the handle for the given icon URI. This may involve the decode I/O
+ // thread, as we can only call SHGetFileInfo() from that thread
+ //
+ // Once we have the handle, we create a Windows ICO buffer with it and
+ // dump the buffer into the output end of the pipe. The input end will be
+ // pumped to our attached nsIStreamListener
+ GetIconBufferFromURLAsync(iconURI)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [outputStream](ByteBuf aIconBuffer) {
+ nsresult rv =
+ WriteByteBufToOutputStream(std::move(aIconBuffer), outputStream);
+ outputStream->CloseWithStatus(rv);
+ },
+ [outputStream](nsresult rv) { outputStream->CloseWithStatus(rv); });
+ }
+
+ rv = mPump->Init(inputStream.get(), 0 /*segmentSize*/, 0 /*segmentCount*/,
+ false /*closeWhenDone*/, GetMainThreadSerialEventTarget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mPump->AsyncRead(this);
+}
+
+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) {
+ MOZ_ASSERT_UNREACHABLE("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) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ 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(nsITransportSecurityInfo** aSecurityInfo) {
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest) {
+ if (mListener) {
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ if (mListener) {
+ mListener->OnStopRequest(this, 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, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (mListener) {
+ return mListener->OnDataAvailable(this, 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 0000000000..4065be76e3
--- /dev/null
+++ b/image/decoders/icon/win/nsIconChannel.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_encoders_icon_win_nsIconChannel_h
+#define mozilla_image_encoders_icon_win_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MozPromise.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.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"
+
+namespace mozilla::ipc {
+class ByteBuf;
+}
+
+class nsIconChannel final : public nsIChannel, public nsIStreamListener {
+ public:
+ using ByteBufPromise =
+ mozilla::MozPromise<mozilla::ipc::ByteBuf, nsresult, true>;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsIconChannel();
+
+ nsresult Init(nsIURI* uri);
+
+ /// Obtains an icon in Windows ICO format as a ByteBuf instead
+ /// of a channel. For use with IPC.
+ static RefPtr<ByteBufPromise> GetIconAsync(nsIURI* aURI);
+
+ private:
+ ~nsIconChannel();
+
+ nsresult StartAsyncOpen();
+
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIStreamListener> mListener;
+
+ bool mCanceled = false;
+};
+
+#endif // mozilla_image_encoders_icon_win_nsIconChannel_h