diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /image/decoders/icon | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/decoders/icon')
-rw-r--r-- | image/decoders/icon/android/moz.build | 13 | ||||
-rw-r--r-- | image/decoders/icon/android/nsIconChannel.cpp | 125 | ||||
-rw-r--r-- | image/decoders/icon/android/nsIconChannel.h | 45 | ||||
-rw-r--r-- | image/decoders/icon/components.conf | 29 | ||||
-rw-r--r-- | image/decoders/icon/gtk/moz.build | 20 | ||||
-rw-r--r-- | image/decoders/icon/gtk/nsIconChannel.cpp | 483 | ||||
-rw-r--r-- | image/decoders/icon/gtk/nsIconChannel.h | 53 | ||||
-rw-r--r-- | image/decoders/icon/mac/moz.build | 13 | ||||
-rw-r--r-- | image/decoders/icon/mac/nsIconChannel.h | 61 | ||||
-rw-r--r-- | image/decoders/icon/mac/nsIconChannelCocoa.mm | 505 | ||||
-rw-r--r-- | image/decoders/icon/moz.build | 39 | ||||
-rw-r--r-- | image/decoders/icon/nsIconProtocolHandler.cpp | 68 | ||||
-rw-r--r-- | image/decoders/icon/nsIconProtocolHandler.h | 25 | ||||
-rw-r--r-- | image/decoders/icon/nsIconURI.cpp | 654 | ||||
-rw-r--r-- | image/decoders/icon/nsIconURI.h | 118 | ||||
-rw-r--r-- | image/decoders/icon/win/moz.build | 21 | ||||
-rw-r--r-- | image/decoders/icon/win/nsIconChannel.cpp | 1006 | ||||
-rw-r--r-- | image/decoders/icon/win/nsIconChannel.h | 65 |
18 files changed, 3343 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..368ecdda20 --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm @@ -0,0 +1,505 @@ +/* -*- 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 = + nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, mozilla::TaskCategory::Other); + 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..d917337bf9 --- /dev/null +++ b/image/decoders/icon/nsIconURI.cpp @@ -0,0 +1,654 @@ +/* -*- 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_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..fe76afe9b1 --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.cpp @@ -0,0 +1,1006 @@ +/* -*- 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); }); + } + + // Use the main thread for the pumped events unless the load info + // specifies otherwise + nsCOMPtr<nsISerialEventTarget> listenerTarget = + nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, + mozilla::TaskCategory::Other); + if (!listenerTarget) { + listenerTarget = do_GetMainThread(); + } + + rv = mPump->Init(inputStream.get(), 0 /*segmentSize*/, 0 /*segmentCount*/, + false /*closeWhenDone*/, listenerTarget); + 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 |