diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /image/decoders/icon | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-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 | 19 | ||||
-rw-r--r-- | image/decoders/icon/gtk/moz.build | 13 | ||||
-rw-r--r-- | image/decoders/icon/gtk/nsIconChannel.cpp | 390 | ||||
-rw-r--r-- | image/decoders/icon/gtk/nsIconChannel.h | 42 | ||||
-rw-r--r-- | image/decoders/icon/mac/moz.build | 11 | ||||
-rw-r--r-- | image/decoders/icon/mac/nsIconChannel.h | 61 | ||||
-rw-r--r-- | image/decoders/icon/mac/nsIconChannelCocoa.mm | 491 | ||||
-rw-r--r-- | image/decoders/icon/moz.build | 39 | ||||
-rw-r--r-- | image/decoders/icon/nsIconProtocolHandler.cpp | 81 | ||||
-rw-r--r-- | image/decoders/icon/nsIconProtocolHandler.h | 25 | ||||
-rw-r--r-- | image/decoders/icon/nsIconURI.cpp | 659 | ||||
-rw-r--r-- | image/decoders/icon/nsIconURI.h | 118 | ||||
-rw-r--r-- | image/decoders/icon/win/moz.build | 15 | ||||
-rw-r--r-- | image/decoders/icon/win/nsIconChannel.cpp | 950 | ||||
-rw-r--r-- | image/decoders/icon/win/nsIconChannel.h | 83 |
18 files changed, 3180 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..e374e7b60c --- /dev/null +++ b/image/decoders/icon/components.conf @@ -0,0 +1,19 @@ +# -*- 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'], + }, + { + '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..09acc05231 --- /dev/null +++ b/image/decoders/icon/gtk/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", +] + +FINAL_LIBRARY = "xul" + +CXXFLAGS += CONFIG["TK_CFLAGS"] diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp new file mode 100644 index 0000000000..954bd7beb1 --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.cpp @@ -0,0 +1,390 @@ +/* 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/gfx/Swizzle.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 "prlink.h" +#include "gfxPlatform.h" + +using mozilla::CheckedInt32; + +NS_IMPL_ISUPPORTS(nsIconChannel, nsIRequest, nsIChannel) + +static nsresult moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI, + nsIChannel** aChannel) { + int width = gdk_pixbuf_get_width(aPixbuf); + int height = gdk_pixbuf_get_height(aPixbuf); + NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 && + gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && + gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && + gdk_pixbuf_get_has_alpha(aPixbuf) && + gdk_pixbuf_get_n_channels(aPixbuf) == 4, + NS_ERROR_UNEXPECTED); + + const int n_channels = 4; + 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)); + + nsresult rv; + nsCOMPtr<nsIStringInputStream> stream = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + + // Prevent the leaking of buf + if (NS_WARN_IF(NS_FAILED(rv))) { + free(buf); + return rv; + } + + // stream takes ownership of buf and will free it on destruction. + // This function cannot fail. + rv = stream->AdoptData((char*)buf, buf_size.value()); + + // If this no longer holds then re-examine buf's lifetime. + MOZ_ASSERT(NS_SUCCEEDED(rv)); + 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 = + mozilla::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)); +} + +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; +} + +nsresult nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI) { + 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 = moz_gdk_pixbuf_to_channel(buf, aIconURI, getter_AddRefs(mRealChannel)); + g_object_unref(buf); + return rv; +} + +nsresult nsIconChannel::Init(nsIURI* aURI) { + nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI); + NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); + + if (gfxPlatform::IsHeadless()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (stockIcon.IsEmpty()) { + return InitWithGIO(iconURI); + } + + // 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 = + moz_gdk_pixbuf_to_channel(icon, iconURI, getter_AddRefs(mRealChannel)); + + g_object_unref(icon); + + return rv; +} + +void nsIconChannel::Shutdown() { + if (gProtoWindow) { + gtk_widget_destroy(gProtoWindow); + gProtoWindow = nullptr; + gStockImageWidget = nullptr; + } +} diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h new file mode 100644 index 0000000000..5a8bae263a --- /dev/null +++ b/image/decoders/icon/gtk/nsIconChannel.h @@ -0,0 +1,42 @@ +/* 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" + +/// This class is the gnome implementation of nsIconChannel. It basically asks +/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for +/// that file to which all calls will be proxied. +class nsIconChannel final : public nsIChannel { + public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIREQUEST(mRealChannel->) + NS_FORWARD_NSICHANNEL(mRealChannel->) + + nsIconChannel() {} + + static void Shutdown(); + + /// Called by nsIconProtocolHandler after it creates this channel. + /// Must be called before calling any other function on this object. + /// If this method fails, no other function must be called on this object. + nsresult Init(nsIURI* aURI); + + private: + ~nsIconChannel() {} + /// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html). + /// Will always be non-null after a successful Init. + nsCOMPtr<nsIChannel> mRealChannel; + + nsresult InitWithGIO(nsIMozIconURI* aIconURI); +}; + +#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h diff --git a/image/decoders/icon/mac/moz.build b/image/decoders/icon/mac/moz.build new file mode 100644 index 0000000000..fee884a473 --- /dev/null +++ b/image/decoders/icon/mac/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + "nsIconChannelCocoa.mm", +] + +FINAL_LIBRARY = "xul" diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h new file mode 100644 index 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..82ced3becc --- /dev/null +++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm @@ -0,0 +1,491 @@ +/* -*- 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 "nsMemory.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 <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::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<nsIEventTarget> 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_ABORT_BLOCK_NSRESULT; + + 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 = MakeUnique<uint8_t[]>(bufferCapacity); + 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 graphicsContextWithGraphicsPort: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; + rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), bufferCapacity, + bufferCapacity, aNonBlocking); + + if (NS_SUCCEEDED(rv)) { + 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_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) { + return mPump->GetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) { + return mPump->SetLoadFlags(aLoadAttributes); +} + +NS_IMETHODIMP +nsIconChannel::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(nsISupports** aSecurityInfo) { + *aSecurityInfo = nullptr; + return NS_OK; +} diff --git a/image/decoders/icon/moz.build b/image/decoders/icon/moz.build new file mode 100644 index 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..035dcd6238 --- /dev/null +++ b/image/decoders/icon/nsIconProtocolHandler.cpp @@ -0,0 +1,81 @@ +/* -*- 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::GetDefaultPort(int32_t* result) { + *result = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::GetProtocolFlags(uint32_t* result) { + *result = + URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE; + return NS_OK; +} + +NS_IMETHODIMP +nsIconProtocolHandler::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..29f9c78064 --- /dev/null +++ b/image/decoders/icon/nsIconURI.cpp @@ -0,0 +1,659 @@ +/* -*- 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 <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 NS_DEFINE_CID(kIconURICID, NS_ICONURI_CID); + +// helper function for parsing out attributes like size, and contentType +// from the icon url. +static void extractAttributeValue(const char* aSearchString, + const char* aAttributeName, + nsCString& aResult); + +static const char* kSizeStrings[] = {"button", "toolbar", "toolbarsmall", + "menu", "dnd", "dialog"}; + +static const char* kStateStrings[] = {"normal", "disabled"}; + +//////////////////////////////////////////////////////////////////////////////// + +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; +} + +// takes a string like ?size=32&contentType=text/html and returns a new string +// containing just the attribute value. i.e you could pass in this string with +// an attribute name of 'size=', this will return 32 +// Assumption: attribute pairs in the string are separated by '&'. +void extractAttributeValue(const char* aSearchString, + const char* aAttributeName, nsCString& aResult) { + // NS_ENSURE_ARG_POINTER(extractAttributeValue); + + aResult.Truncate(); + + if (aSearchString && aAttributeName) { + // search the string for attributeName + uint32_t attributeNameSize = strlen(aAttributeName); + const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName); + if (startOfAttribute && + (*(startOfAttribute - 1) == '?' || *(startOfAttribute - 1) == '&')) { + startOfAttribute += attributeNameSize; // skip over the attributeName + // is there something after the attribute name + if (*startOfAttribute) { + const char* endofAttribute = strchr(startOfAttribute, '&'); + if (endofAttribute) { + aResult.Assign(Substring(startOfAttribute, endofAttribute)); + } else { + aResult.Assign(startOfAttribute); + } + } // if we have a attribute value + } // if we have a attribute name + } // if we got non-null search string and attribute name values +} + +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 (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) { + mIconSize = i; + break; + } + } + + int32_t sizeValue = atoi(sizeString.get()); + if (sizeValue > 0) { + mSize = sizeValue; + } + } + + nsAutoCString stateString; + extractAttributeValue(iconSpec.get(), "state=", stateString); + if (!stateString.IsEmpty()) { + const char* stateStr = stateString.get(); + for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) { + if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) { + mIconState = i; + break; + } + } + } + } + + int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN; + if (questionMarkPos != -1) { + pathLength = questionMarkPos - MOZICON_SCHEME_LEN; + } + if (pathLength < 3) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength)); + + // Icon URI path can have three forms: + // (1) //stock/<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 (!PL_strcasecmp(spec1.get(), spec2.get())) { + *result = true; + } else { + *result = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result) { + // GetRef/SetRef not supported by nsMozIconURI, so + // EqualsExceptRef() is the same as Equals(). + return Equals(other, result); +} + +NS_IMETHODIMP +nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals) { + MOZ_ASSERT(aEquals, "null pointer"); + if (!aScheme) { + *aEquals = false; + return NS_OK; + } + + *aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true; + 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..2a06f68fec --- /dev/null +++ b/image/decoders/icon/win/moz.build @@ -0,0 +1,15 @@ +# -*- 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", +] + +LOCAL_INCLUDES += [ + "/image", +] + +FINAL_LIBRARY = "xul" diff --git a/image/decoders/icon/win/nsIconChannel.cpp b/image/decoders/icon/win/nsIconChannel.cpp new file mode 100644 index 0000000000..28d2ccc873 --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.cpp @@ -0,0 +1,950 @@ +/* -*- 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 "nsComponentManagerUtils.h" +#include "nsIconChannel.h" +#include "nsIIconURI.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsMimeTypes.h" +#include "nsMemory.h" +#include "nsIURL.h" +#include "nsIPipe.h" +#include "nsNetCID.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIInputStream.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; + +struct ICONFILEHEADER { + uint16_t ifhReserved; + uint16_t ifhType; + uint16_t ifhCount; +}; + +struct ICONENTRY { + int8_t ieWidth; + int8_t ieHeight; + uint8_t ieColors; + uint8_t ieReserved; + uint16_t iePlanes; + uint16_t ieBitCount; + uint32_t ieSizeImage; + uint32_t ieFileOffset; +}; + +// Match stock icons with names +static SHSTOCKICONID GetStockIconIDForName(const nsACString& aStockName) { + return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD : SIID_INVALID; +} + +class nsIconChannel::IconAsyncOpenTask final : public Runnable { + public: + IconAsyncOpenTask(nsIconChannel* aChannel, nsIEventTarget* aTarget, + nsCOMPtr<nsIFile>&& aLocalFile, nsAutoString& aPath, + UINT aInfoFlags) + : Runnable("IconAsyncOpenTask"), + mChannel(aChannel), + + mTarget(aTarget), + mLocalFile(std::move(aLocalFile)), + mPath(aPath), + mInfoFlags(aInfoFlags) {} + + NS_IMETHOD Run() override; + + private: + RefPtr<nsIconChannel> mChannel; + nsCOMPtr<nsIEventTarget> mTarget; + nsCOMPtr<nsIFile> mLocalFile; + nsAutoString mPath; + UINT mInfoFlags; +}; + +NS_IMETHODIMP nsIconChannel::IconAsyncOpenTask::Run() { + HICON hIcon = nullptr; + nsresult rv = + mChannel->GetHIconFromFile(mLocalFile, mPath, mInfoFlags, &hIcon); + // Effectively give ownership of mChannel to the runnable so it get released + // on the main thread. + RefPtr<nsIconChannel> channel = mChannel.forget(); + nsCOMPtr<nsIRunnable> task = NewRunnableMethod<HICON, nsresult>( + "nsIconChannel::FinishAsyncOpen", channel, + &nsIconChannel::FinishAsyncOpen, hIcon, rv); + mTarget->Dispatch(task.forget(), NS_DISPATCH_NORMAL); + return NS_OK; +} + +class nsIconChannel::IconSyncOpenTask final : public Runnable { + public: + IconSyncOpenTask(nsIconChannel* aChannel, nsIEventTarget* aTarget, + nsCOMPtr<nsIFile>&& aLocalFile, nsAutoString& aPath, + UINT aInfoFlags) + : Runnable("IconSyncOpenTask"), + mMonitor("IconSyncOpenTask"), + mDone(false), + mChannel(aChannel), + mTarget(aTarget), + mLocalFile(std::move(aLocalFile)), + mPath(aPath), + mInfoFlags(aInfoFlags), + mHIcon(nullptr), + mRv(NS_OK) {} + + NS_IMETHOD Run() override; + + Monitor& GetMonitor() { return mMonitor; } + bool Done() const { + mMonitor.AssertCurrentThreadOwns(); + return mDone; + } + HICON GetHIcon() const { + mMonitor.AssertCurrentThreadOwns(); + return mHIcon; + } + nsresult GetRv() const { + mMonitor.AssertCurrentThreadOwns(); + return mRv; + } + + private: + Monitor mMonitor; + bool mDone; + // Parameters in + RefPtr<nsIconChannel> mChannel; + nsCOMPtr<nsIEventTarget> mTarget; + nsCOMPtr<nsIFile> mLocalFile; + nsAutoString mPath; + UINT mInfoFlags; + // Return values + HICON mHIcon; + nsresult mRv; +}; + +NS_IMETHODIMP +nsIconChannel::IconSyncOpenTask::Run() { + MonitorAutoLock lock(mMonitor); + mRv = mChannel->GetHIconFromFile(mLocalFile, mPath, mInfoFlags, &mHIcon); + mDone = true; + mMonitor.NotifyAll(); + // Do this little dance because nsIconChannel multiple inherits from + // nsISupports. + nsCOMPtr<nsIChannel> channel = mChannel.forget(); + NS_ProxyRelease("IconSyncOpenTask::mChannel", mTarget, channel.forget()); + return NS_OK; +} + +// nsIconChannel methods +nsIconChannel::nsIconChannel() {} + +nsIconChannel::~nsIconChannel() { + if (mLoadInfo) { + NS_ReleaseOnMainThread("nsIconChannel::mLoadInfo", mLoadInfo.forget()); + } + if (mLoadGroup) { + NS_ReleaseOnMainThread("nsIconChannel::mLoadGroup", mLoadGroup.forget()); + } +} + +NS_IMPL_ISUPPORTS(nsIconChannel, nsIChannel, nsIRequest, nsIRequestObserver, + nsIStreamListener) + +nsresult nsIconChannel::Init(nsIURI* uri) { + NS_ASSERTION(uri, "no uri"); + mUrl = uri; + mOriginalURI = uri; + nsresult rv; + mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +NS_IMETHODIMP +nsIconChannel::GetName(nsACString& result) { return mUrl->GetSpec(result); } + +NS_IMETHODIMP +nsIconChannel::IsPending(bool* result) { return mPump->IsPending(result); } + +NS_IMETHODIMP +nsIconChannel::GetStatus(nsresult* status) { return mPump->GetStatus(status); } + +NS_IMETHODIMP +nsIconChannel::Cancel(nsresult status) { + 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; +} + +NS_IMETHODIMP +nsIconChannel::Open(nsIInputStream** aStream) { + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + + HICON hIcon = nullptr; + rv = GetHIcon(/* aNonBlocking */ false, &hIcon); + if (NS_FAILED(rv)) { + return rv; + } + + return MakeInputStream(aStream, /* aNonBlocking */ false, hIcon); +} + +nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsCString& aContentType, + nsCString& 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; + + return file->Clone(aLocalFile); +} + +void nsIconChannel::OnAsyncError(nsresult aStatus) { + OnStartRequest(this); + OnStopRequest(this, aStatus); +} + +void nsIconChannel::FinishAsyncOpen(HICON aIcon, nsresult aStatus) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_FAILED(aStatus)) { + OnAsyncError(aStatus); + return; + } + + nsCOMPtr<nsIInputStream> inStream; + nsresult rv = MakeInputStream(getter_AddRefs(inStream), + /* aNonBlocking */ true, aIcon); + if (NS_FAILED(rv)) { + OnAsyncError(rv); + return; + } + + rv = mPump->Init(inStream, 0, 0, false, mListenerTarget); + if (NS_FAILED(rv)) { + OnAsyncError(rv); + return; + } + + rv = mPump->AsyncRead(this); + if (NS_FAILED(rv)) { + OnAsyncError(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"); + + nsCOMPtr<nsIInputStream> inStream; + rv = EnsurePipeCreated(/* aIconSize */ 0, /* aNonBlocking */ true); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + return rv; + } + + mListenerTarget = nsContentUtils::GetEventTargetByLoadInfo( + mLoadInfo, mozilla::TaskCategory::Other); + if (!mListenerTarget) { + mListenerTarget = do_GetMainThread(); + } + + // If we pass aNonBlocking as true, GetHIcon will always have dispatched + // upon success. + HICON hIcon = nullptr; + rv = GetHIcon(/* aNonBlocking */ true, &hIcon); + if (NS_FAILED(rv)) { + mCallbacks = nullptr; + mInputStream = nullptr; + mOutputStream = nullptr; + mListenerTarget = nullptr; + return rv; + } + + // We shouldn't have the icon yet if it is non-blocking. + MOZ_ASSERT(!hIcon); + + // Add ourself to the load group, if available + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + + // Store our real listener + mListener = aListener; + return NS_OK; +} + +static DWORD GetSpecialFolderIcon(nsIFile* aFile, int aFolder, + SHFILEINFOW* aSFI, UINT aInfoFlags) { + DWORD shellResult = 0; + + if (!aFile) { + return shellResult; + } + + wchar_t fileNativePath[MAX_PATH]; + nsAutoString fileNativePathStr; + aFile->GetPath(fileNativePathStr); + ::GetShortPathNameW(fileNativePathStr.get(), fileNativePath, + ArrayLength(fileNativePath)); + + LPITEMIDLIST idList; + HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList); + if (SUCCEEDED(hr)) { + wchar_t specialNativePath[MAX_PATH]; + ::SHGetPathFromIDListW(idList, specialNativePath); + ::GetShortPathNameW(specialNativePath, specialNativePath, + ArrayLength(specialNativePath)); + + if (!wcsicmp(fileNativePath, specialNativePath)) { + aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX); + shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0, aSFI, + sizeof(*aSFI), aInfoFlags); + } + } + CoTaskMemFree(idList); + return shellResult; +} + +static UINT GetSizeInfoFlag(uint32_t aDesiredImageSize) { + return (UINT)(aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON); +} + +nsresult nsIconChannel::GetHIconFromFile(bool aNonBlocking, HICON* hIcon) { + nsCString contentType; + nsCString fileExt; + nsCOMPtr<nsIFile> localFile; // file we want an icon for + uint32_t desiredImageSize; + nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile), + &desiredImageSize, contentType, fileExt); + NS_ENSURE_SUCCESS(rv, rv); + + // if the file exists, we are going to use it's real attributes... + // otherwise we only want to use it for it's extension... + 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 (aNonBlocking) { + RefPtr<nsIEventTarget> target = DecodePool::Singleton()->GetIOEventTarget(); + RefPtr<IconAsyncOpenTask> task = new IconAsyncOpenTask( + this, mListenerTarget, std::move(localFile), filePath, infoFlags); + target->Dispatch(task.forget(), NS_DISPATCH_NORMAL); + return NS_OK; + } + + // We cannot call SHGetFileInfo on more than one thread (at a time), so it + // must be called on the same thread every time, even now when we need sync + // behaviour. So we synchronously wait on the other thread to finish. + RefPtr<nsIEventTarget> target = DecodePool::Singleton()->GetIOEventTarget(); + RefPtr<IconSyncOpenTask> task = new IconSyncOpenTask( + this, mListenerTarget, std::move(localFile), filePath, infoFlags); + MonitorAutoLock lock(task->GetMonitor()); + target->Dispatch(task, NS_DISPATCH_NORMAL); + do { + task->GetMonitor().Wait(); + } while (!task->Done()); + *hIcon = task->GetHIcon(); + return task->GetRv(); +} + +nsresult nsIconChannel::GetHIconFromFile(nsIFile* aLocalFile, + const nsAutoString& aPath, + UINT aInfoFlags, HICON* hIcon) { + SHFILEINFOW sfi; + + // Is this the "Desktop" folder? + DWORD shellResult = + GetSpecialFolderIcon(aLocalFile, CSIDL_DESKTOP, &sfi, aInfoFlags); + if (!shellResult) { + // Is this the "My Documents" folder? + shellResult = + GetSpecialFolderIcon(aLocalFile, CSIDL_PERSONAL, &sfi, aInfoFlags); + } + + // There are other "Special Folders" and Namespace entities that we + // are not fetching icons for, see: + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ + // shellcc/platform/shell/reference/enums/csidl.asp + // If we ever need to get them, code to do so would be inserted here. + + // Not a special folder, or something else failed above. + if (!shellResult) { + shellResult = ::SHGetFileInfoW(aPath.get(), FILE_ATTRIBUTE_ARCHIVE, &sfi, + sizeof(sfi), aInfoFlags); + } + + if (!shellResult || !sfi.hIcon) { + return NS_ERROR_NOT_AVAILABLE; + } + + *hIcon = sfi.hIcon; + return NS_OK; +} + +nsresult nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon) { + nsresult rv = NS_OK; + + uint32_t desiredImageSize; + aIconURI->GetImageSize(&desiredImageSize); + nsAutoCString stockIcon; + aIconURI->GetStockIcon(stockIcon); + + SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon); + if (stockIconID == SIID_INVALID) { + return NS_ERROR_NOT_AVAILABLE; + } + + UINT infoFlags = SHGSI_ICON; + infoFlags |= GetSizeInfoFlag(desiredImageSize); + + SHSTOCKICONINFO sii = {0}; + sii.cbSize = sizeof(sii); + HRESULT hr = SHGetStockIconInfo(stockIconID, infoFlags, &sii); + + if (SUCCEEDED(hr)) { + *hIcon = sii.hIcon; + } else { + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +// Given a BITMAPINFOHEADER, returns the size of the color table. +static int GetColorTableSize(BITMAPINFOHEADER* aHeader) { + int colorTableSize = -1; + + // http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx + switch (aHeader->biBitCount) { + case 0: + colorTableSize = 0; + break; + case 1: + colorTableSize = 2 * sizeof(RGBQUAD); + break; + case 4: + case 8: { + // The maximum possible size for the color table is 2**bpp, so check for + // that and fail if we're not in those bounds + unsigned int maxEntries = 1 << (aHeader->biBitCount); + if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) { + colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD); + } else if (aHeader->biClrUsed == 0) { + colorTableSize = maxEntries * sizeof(RGBQUAD); + } + break; + } + case 16: + case 32: + // If we have BI_BITFIELDS compression, we would normally need 3 DWORDS + // for the bitfields mask which would be stored in the color table; + // However, we instead force the bitmap to request data of type BI_RGB so + // the color table should be of size 0. Setting aHeader->biCompression = + // BI_RGB forces the later call to GetDIBits to return to us BI_RGB data. + if (aHeader->biCompression == BI_BITFIELDS) { + aHeader->biCompression = BI_RGB; + } + colorTableSize = 0; + break; + case 24: + colorTableSize = 0; + break; + } + + if (colorTableSize < 0) { + NS_WARNING("Unable to figure out the color table size for this bitmap"); + } + + return colorTableSize; +} + +// Given a header and a size, creates a freshly allocated BITMAPINFO structure. +// It is the caller's responsibility to null-check and delete the structure. +static BITMAPINFO* CreateBitmapInfo(BITMAPINFOHEADER* aHeader, + size_t aColorTableSize) { + BITMAPINFO* bmi = (BITMAPINFO*)::operator new( + sizeof(BITMAPINFOHEADER) + aColorTableSize, mozilla::fallible); + if (bmi) { + memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER)); + memset(bmi->bmiColors, 0, aColorTableSize); + } + return bmi; +} + +nsresult nsIconChannel::EnsurePipeCreated(uint32_t aIconSize, + bool aNonBlocking) { + if (mInputStream || mOutputStream) { + return NS_OK; + } + + return NS_NewPipe(getter_AddRefs(mInputStream), getter_AddRefs(mOutputStream), + aIconSize, aIconSize > 0 ? aIconSize : UINT32_MAX, + aNonBlocking); +} + +nsresult nsIconChannel::GetHIcon(bool aNonBlocking, HICON* aIcon) { + // Check whether the icon requested's a file icon or a stock icon + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + // GetDIBits does not exist on windows mobile. + nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString stockIcon; + iconURI->GetStockIcon(stockIcon); + if (!stockIcon.IsEmpty()) { + return GetStockHIcon(iconURI, aIcon); + } + + return GetHIconFromFile(aNonBlocking, aIcon); +} + +nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, + bool aNonBlocking, HICON aIcon) { + 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; + + UniquePtr<char[]> buffer = MakeUnique<char[]>(iconSize); + if (!buffer) { + rv = NS_ERROR_OUT_OF_MEMORY; + } else { + char* whereTo = buffer.get(); + int howMuch; + + // the data starts with an icon file header + ICONFILEHEADER iconHeader; + iconHeader.ifhReserved = 0; + iconHeader.ifhType = 1; + iconHeader.ifhCount = 1; + howMuch = sizeof(ICONFILEHEADER); + memcpy(whereTo, &iconHeader, howMuch); + whereTo += howMuch; + + // followed by the single icon entry + ICONENTRY iconEntry; + iconEntry.ieWidth = static_cast<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)) { + // Now, create a pipe and stuff our data into it + rv = EnsurePipeCreated(iconSize, aNonBlocking); + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = mOutputStream->Write(buffer.get(), iconSize, &written); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(*_retval = mInputStream); + } + } + + } // 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 + + // If we didn't make a stream, then fail. + if (!*_retval && NS_SUCCEEDED(rv)) { + rv = NS_ERROR_NOT_AVAILABLE; + } + + mInputStream = nullptr; + mOutputStream = nullptr; + return rv; +} + +NS_IMETHODIMP +nsIconChannel::GetContentType(nsACString& aContentType) { + aContentType.AssignLiteral(IMAGE_ICO); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentType(const nsACString& aContentType) { + // It doesn't make sense to set the content-type on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset) { + aContentCharset.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +nsIconChannel::SetContentCharset(const nsACString& aContentCharset) { + // It doesn't make sense to set the content-charset on this type + // of channel... + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsIconChannel::GetContentLength(int64_t* aContentLength) { + *aContentLength = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsIconChannel::SetContentLength(int64_t aContentLength) { + 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(nsISupports** 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; + mListenerTarget = 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..f3bb93f348 --- /dev/null +++ b/image/decoders/icon/win/nsIconChannel.h @@ -0,0 +1,83 @@ +/* -*- 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 "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 "nsIOutputStream.h" +#include "nsIStreamListener.h" +#include "nsIIconURI.h" + +#include <windows.h> + +class nsIFile; + +class nsIconChannel final : public nsIChannel, public nsIStreamListener { + ~nsIconChannel(); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsIconChannel(); + + nsresult Init(nsIURI* uri); + + protected: + class IconAsyncOpenTask; + class IconSyncOpenTask; + + void OnAsyncError(nsresult aStatus); + void FinishAsyncOpen(HICON aIcon, nsresult aStatus); + nsresult EnsurePipeCreated(uint32_t aIconSize, bool aNonBlocking); + + nsCOMPtr<nsIURI> mUrl; + nsCOMPtr<nsIURI> mOriginalURI; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsISupports> mOwner; + nsCOMPtr<nsILoadInfo> mLoadInfo; + + nsCOMPtr<nsIInputStreamPump> mPump; + nsCOMPtr<nsIInputStream> mInputStream; + nsCOMPtr<nsIOutputStream> mOutputStream; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsIEventTarget> mListenerTarget; + + nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile, + uint32_t* aDesiredImageSize, + nsCString& aContentType, + nsCString& aFileExtension); + nsresult GetHIcon(bool aNonBlocking, HICON* hIcon); + nsresult GetHIconFromFile(bool aNonBlocking, HICON* hIcon); + nsresult GetHIconFromFile(nsIFile* aLocalFile, const nsAutoString& aPath, + UINT aInfoFlags, HICON* hIcon); + [[nodiscard]] nsresult MakeInputStream(nsIInputStream** _retval, + bool aNonBlocking, HICON aIcon); + + // Functions specific to Vista and above + protected: + nsresult GetStockHIcon(nsIMozIconURI* aIconURI, HICON* hIcon); + + private: + bool mCanceled = false; +}; + +#endif // mozilla_image_encoders_icon_win_nsIconChannel_h |