nsIconChannel.cpp (12683B)
1 /* vim:set ts=2 sw=2 sts=2 cin et: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsIconChannel.h" 7 8 #include <stdlib.h> 9 #include <unistd.h> 10 11 #include "ErrorList.h" 12 #include "gdk/gdk.h" 13 #include "mozilla/AlreadyAddRefed.h" 14 #include "nsIIconURI.h" 15 #include "mozilla/gfx/DataSurfaceHelpers.h" 16 #include "mozilla/NullPrincipal.h" 17 #include "mozilla/LookAndFeel.h" 18 #include "mozilla/CheckedInt.h" 19 #include "mozilla/dom/ContentChild.h" 20 #include "mozilla/gfx/Swizzle.h" 21 #include "mozilla/ipc/ByteBuf.h" 22 #include "mozilla/GUniquePtr.h" 23 #include "mozilla/GRefPtr.h" 24 #include <algorithm> 25 26 #include <gio/gio.h> 27 #include <gio/gdesktopappinfo.h> 28 29 #include <gtk/gtk.h> 30 31 #include "nsMimeTypes.h" 32 #include "nsIMIMEService.h" 33 34 #include "nsServiceManagerUtils.h" 35 36 #include "nsNetUtil.h" 37 #include "nsComponentManagerUtils.h" 38 #include "nsIStringStream.h" 39 #include "nsServiceManagerUtils.h" 40 #include "nsIURL.h" 41 #include "nsIPipe.h" 42 #include "nsIAsyncInputStream.h" 43 #include "nsIAsyncOutputStream.h" 44 #include "prlink.h" 45 #include "gfxUtils.h" 46 #include "gfxPlatform.h" 47 48 using namespace mozilla; 49 using mozilla::ipc::ByteBuf; 50 51 NS_IMPL_ISUPPORTS(nsIconChannel, nsIRequest, nsIChannel) 52 53 static bool IsValidRGBAPixbuf(GdkPixbuf* aPixbuf) { 54 return gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB && 55 gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 && 56 gdk_pixbuf_get_has_alpha(aPixbuf) && 57 gdk_pixbuf_get_n_channels(aPixbuf) == 4; 58 } 59 60 static already_AddRefed<gfx::DataSourceSurface> MozGdkPixbufToDataSurface( 61 GdkPixbuf* aPixbuf) { 62 if (NS_WARN_IF(!IsValidRGBAPixbuf(aPixbuf))) { 63 return nullptr; 64 } 65 int width = gdk_pixbuf_get_width(aPixbuf); 66 int height = gdk_pixbuf_get_height(aPixbuf); 67 if (!width || !height) { 68 return nullptr; 69 } 70 guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf); 71 const int stride = gdk_pixbuf_get_rowstride(aPixbuf); 72 RefPtr wrapper = gfx::Factory::CreateWrappingDataSourceSurface( 73 pixels, stride, gfx::IntSize(width, height), 74 gfx::SurfaceFormat::R8G8B8A8); 75 if (NS_WARN_IF(!wrapper)) { 76 return nullptr; 77 } 78 return gfxUtils::CreatePremultipliedDataSurface(wrapper); 79 } 80 81 static nsresult MozGdkPixbufToByteBuf(GdkPixbuf* aPixbuf, ByteBuf* aByteBuf) { 82 int width = gdk_pixbuf_get_width(aPixbuf); 83 int height = gdk_pixbuf_get_height(aPixbuf); 84 NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 && 85 IsValidRGBAPixbuf(aPixbuf), 86 NS_ERROR_UNEXPECTED); 87 const int n_channels = 4; 88 CheckedInt32 buf_size = 89 4 + n_channels * CheckedInt32(height) * CheckedInt32(width); 90 if (!buf_size.isValid()) { 91 return NS_ERROR_OUT_OF_MEMORY; 92 } 93 uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size.value()); 94 uint8_t* out = buf; 95 96 *(out++) = width; 97 *(out++) = height; 98 *(out++) = uint8_t(mozilla::gfx::SurfaceFormat::OS_RGBA); 99 100 // Set all bits to ensure in nsIconDecoder we color manage and premultiply. 101 *(out++) = 0xFF; 102 103 const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf); 104 int instride = gdk_pixbuf_get_rowstride(aPixbuf); 105 int outstride = width * n_channels; 106 107 // encode the RGB data and the A data and adjust the stride as necessary. 108 mozilla::gfx::SwizzleData(pixels, instride, 109 mozilla::gfx::SurfaceFormat::R8G8B8A8, out, 110 outstride, mozilla::gfx::SurfaceFormat::OS_RGBA, 111 mozilla::gfx::IntSize(width, height)); 112 113 *aByteBuf = ByteBuf(buf, buf_size.value(), buf_size.value()); 114 return NS_OK; 115 } 116 117 static nsresult ByteBufToStream(ByteBuf&& aBuf, nsIInputStream** aStream) { 118 nsresult rv; 119 nsCOMPtr<nsIStringInputStream> stream = 120 do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); 121 122 if (NS_WARN_IF(NS_FAILED(rv))) { 123 return rv; 124 } 125 126 // stream takes ownership of buf and will free it on destruction. 127 // This function cannot fail. 128 rv = stream->AdoptData(reinterpret_cast<char*>(aBuf.mData), aBuf.mLen); 129 MOZ_ASSERT(CheckedInt32(aBuf.mLen).isValid(), 130 "aBuf.mLen should fit in int32_t"); 131 aBuf.mData = nullptr; 132 133 // If this no longer holds then re-examine buf's lifetime. 134 MOZ_ASSERT(NS_SUCCEEDED(rv)); 135 NS_ENSURE_SUCCESS(rv, rv); 136 137 stream.forget(aStream); 138 return NS_OK; 139 } 140 141 static GdkRGBA GeckoColorToGdk(nscolor aColor) { 142 auto ToGdk = [](uint8_t aGecko) { return aGecko / 255.0; }; 143 return GdkRGBA{ 144 .red = ToGdk(NS_GET_R(aColor)), 145 .green = ToGdk(NS_GET_G(aColor)), 146 .blue = ToGdk(NS_GET_B(aColor)), 147 .alpha = ToGdk(NS_GET_A(aColor)), 148 }; 149 } 150 151 static nscolor GetForegroundColor(nsIMozIconURI* aIconURI) { 152 auto scheme = [&] { 153 bool dark = false; 154 if (NS_FAILED(aIconURI->GetImageDark(&dark))) { 155 return mozilla::LookAndFeel::SystemColorScheme(); 156 } 157 return dark ? mozilla::ColorScheme::Dark : mozilla::ColorScheme::Light; 158 }(); 159 return mozilla::LookAndFeel::Color(mozilla::LookAndFeel::ColorID::Windowtext, 160 scheme, 161 mozilla::LookAndFeel::UseStandins::No); 162 } 163 164 static nsresult GetIconWithGIO(nsIMozIconURI* aIconURI, ByteBuf* aDataOut) { 165 RefPtr<GIcon> icon; 166 nsCOMPtr<nsIURL> fileURI; 167 168 // Read icon content 169 aIconURI->GetIconURL(getter_AddRefs(fileURI)); 170 171 // Get icon for file specified by URI 172 if (fileURI) { 173 nsAutoCString spec; 174 fileURI->GetAsciiSpec(spec); 175 if (fileURI->SchemeIs("file")) { 176 GFile* file = g_file_new_for_uri(spec.get()); 177 GFileInfo* fileInfo = 178 g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, 179 G_FILE_QUERY_INFO_NONE, nullptr, nullptr); 180 g_object_unref(file); 181 if (fileInfo) { 182 icon = g_file_info_get_icon(fileInfo); 183 g_object_unref(fileInfo); 184 } 185 } 186 } else { 187 // From the moz-icon://appId?size=... get the appId 188 nsAutoCString appId; 189 aIconURI->GetAsciiSpec(appId); 190 191 if (appId.Find("?size=")) { 192 appId.Truncate(appId.Find("?size=")); 193 } 194 195 appId = Substring(appId, sizeof("moz-icon:/")); 196 197 RefPtr<GDesktopAppInfo> app_info = 198 dont_AddRef(g_desktop_app_info_new(appId.get())); 199 if (app_info) { 200 icon = g_app_info_get_icon((GAppInfo*)app_info.get()); 201 } 202 } 203 204 // Try to get icon by using MIME type 205 if (!icon) { 206 nsAutoCString type; 207 aIconURI->GetContentType(type); 208 // Try to get MIME type from file extension by using nsIMIMEService 209 if (type.IsEmpty()) { 210 nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1")); 211 if (ms) { 212 nsAutoCString fileExt; 213 aIconURI->GetFileExtension(fileExt); 214 ms->GetTypeFromExtension(fileExt, type); 215 } 216 } 217 mozilla::GUniquePtr<gchar> ctype; 218 if (!type.IsEmpty()) { 219 ctype.reset(g_content_type_from_mime_type(type.get())); 220 } 221 if (ctype) { 222 icon = dont_AddRef(g_content_type_get_icon(ctype.get())); 223 } 224 } 225 226 // Get default icon theme 227 GtkIconTheme* iconTheme = gtk_icon_theme_get_default(); 228 // Get icon size and scale. 229 int32_t iconSize = aIconURI->GetImageSize(); 230 int32_t scale = aIconURI->GetImageScale(); 231 232 RefPtr<GtkIconInfo> iconInfo; 233 if (icon) { 234 iconInfo = dont_AddRef(gtk_icon_theme_lookup_by_gicon_for_scale( 235 iconTheme, icon, iconSize, scale, GtkIconLookupFlags(0))); 236 } 237 238 if (!iconInfo) { 239 // Mozilla's mimetype lookup failed. Try the "unknown" icon. 240 iconInfo = dont_AddRef(gtk_icon_theme_lookup_icon_for_scale( 241 iconTheme, "unknown", iconSize, scale, GtkIconLookupFlags(0))); 242 if (!iconInfo) { 243 return NS_ERROR_NOT_AVAILABLE; 244 } 245 } 246 247 // Create a GdkPixbuf buffer containing icon and scale it 248 const auto fg = GeckoColorToGdk(GetForegroundColor(aIconURI)); 249 RefPtr<GdkPixbuf> pixbuf = dont_AddRef(gtk_icon_info_load_symbolic( 250 iconInfo, &fg, nullptr, nullptr, nullptr, nullptr, nullptr)); 251 252 if (!pixbuf) { 253 return NS_ERROR_UNEXPECTED; 254 } 255 return MozGdkPixbufToByteBuf(pixbuf, aDataOut); 256 } 257 258 static already_AddRefed<GdkPixbuf> GetSymbolicIconPixbuf(const nsCString& aName, 259 int aIconSize, 260 int aScale, 261 nscolor aFgColor) { 262 GtkIconTheme* theme = gtk_icon_theme_get_default(); 263 RefPtr<GtkIconInfo> iconInfo = 264 dont_AddRef(gtk_icon_theme_lookup_icon_for_scale( 265 theme, aName.get(), aIconSize, aScale, GtkIconLookupFlags(0))); 266 if (!iconInfo) { 267 return nullptr; 268 } 269 const auto fg = GeckoColorToGdk(aFgColor); 270 RefPtr<GdkPixbuf> pixbuf = dont_AddRef(gtk_icon_info_load_symbolic( 271 iconInfo, &fg, nullptr, nullptr, nullptr, nullptr, nullptr)); 272 return pixbuf.forget(); 273 } 274 275 /* static */ 276 nsresult nsIconChannel::GetIcon(nsIURI* aURI, ByteBuf* aDataOut) { 277 nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI); 278 NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI"); 279 280 if (!iconURI) { 281 return NS_ERROR_NOT_AVAILABLE; 282 } 283 284 if (gfxPlatform::IsHeadless()) { 285 return NS_ERROR_NOT_AVAILABLE; 286 } 287 288 nsAutoCString stockIcon; 289 iconURI->GetStockIcon(stockIcon); 290 if (stockIcon.IsEmpty()) { 291 return GetIconWithGIO(iconURI, aDataOut); 292 } 293 294 const gint iconSize = iconURI->GetImageSize(); 295 const gint scale = iconURI->GetImageScale(); 296 const nscolor fg = GetForegroundColor(iconURI); 297 RefPtr pixbuf = GetSymbolicIconPixbuf(stockIcon, iconSize, scale, fg); 298 if (!pixbuf) { 299 return NS_ERROR_NOT_AVAILABLE; 300 } 301 return MozGdkPixbufToByteBuf(pixbuf, aDataOut); 302 } 303 304 already_AddRefed<gfx::DataSourceSurface> nsIconChannel::GetSymbolicIcon( 305 const nsCString& aName, int aIconSize, int aScale, nscolor aFgColor) { 306 RefPtr pixbuf = GetSymbolicIconPixbuf(aName, aIconSize, aScale, aFgColor); 307 if (!pixbuf) { 308 return nullptr; 309 } 310 return MozGdkPixbufToDataSurface(pixbuf); 311 } 312 313 nsresult nsIconChannel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { 314 nsCOMPtr<nsIInputStream> stream; 315 316 using ContentChild = mozilla::dom::ContentChild; 317 if (auto* contentChild = ContentChild::GetSingleton()) { 318 // Get the icon via IPC and translate the promise of a ByteBuf 319 // into an actually-existing channel. 320 RefPtr<ContentChild::GetSystemIconPromise> icon = 321 contentChild->SendGetSystemIcon(aURI); 322 if (!icon) { 323 return NS_ERROR_UNEXPECTED; 324 } 325 326 nsCOMPtr<nsIAsyncInputStream> inputStream; 327 nsCOMPtr<nsIAsyncOutputStream> outputStream; 328 NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream), true, 329 false, 0, UINT32_MAX); 330 331 // FIXME: Bug 1718324 332 // The GetSystemIcon() call will end up on the parent doing GetIcon() 333 // and by using ByteBuf we might not be immune to some deadlock, at least 334 // on paper. From analysis in 335 // https://phabricator.services.mozilla.com/D118596#3865440 we should be 336 // safe in practice, but it would be nicer to just write that differently. 337 338 icon->Then( 339 mozilla::GetCurrentSerialEventTarget(), __func__, 340 [outputStream](std::tuple<nsresult, mozilla::Maybe<ByteBuf>>&& aArg) { 341 nsresult rv = std::get<0>(aArg); 342 mozilla::Maybe<ByteBuf> bytes = std::move(std::get<1>(aArg)); 343 344 if (NS_SUCCEEDED(rv)) { 345 MOZ_RELEASE_ASSERT(bytes); 346 uint32_t written; 347 rv = outputStream->Write(reinterpret_cast<char*>(bytes->mData), 348 static_cast<uint32_t>(bytes->mLen), 349 &written); 350 if (NS_SUCCEEDED(rv)) { 351 const bool wroteAll = static_cast<size_t>(written) == bytes->mLen; 352 MOZ_ASSERT(wroteAll); 353 if (!wroteAll) { 354 rv = NS_ERROR_UNEXPECTED; 355 } 356 } 357 } else { 358 MOZ_ASSERT(!bytes); 359 } 360 361 if (NS_FAILED(rv)) { 362 outputStream->CloseWithStatus(rv); 363 } 364 }, 365 [outputStream](mozilla::ipc::ResponseRejectReason) { 366 outputStream->CloseWithStatus(NS_ERROR_FAILURE); 367 }); 368 369 stream = inputStream.forget(); 370 } else { 371 // Get the icon directly. 372 ByteBuf bytebuf; 373 nsresult rv = GetIcon(aURI, &bytebuf); 374 NS_ENSURE_SUCCESS(rv, rv); 375 376 rv = ByteBufToStream(std::move(bytebuf), getter_AddRefs(stream)); 377 NS_ENSURE_SUCCESS(rv, rv); 378 } 379 380 return NS_NewInputStreamChannelInternal( 381 getter_AddRefs(mRealChannel), aURI, stream.forget(), 382 nsLiteralCString(IMAGE_ICON_MS), /* aContentCharset */ ""_ns, aLoadInfo); 383 } 384 385 void nsIconChannel::Shutdown() {}