nsIconChannelCocoa.mm (14803B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsContentUtils.h" 8 #include "nsIconChannel.h" 9 #include "mozilla/BasePrincipal.h" 10 #include "nsComponentManagerUtils.h" 11 #include "nsIIconURI.h" 12 #include "nsIInputStream.h" 13 #include "nsIInterfaceRequestor.h" 14 #include "nsIInterfaceRequestorUtils.h" 15 #include "nsString.h" 16 #include "nsMimeTypes.h" 17 #include "nsIURL.h" 18 #include "nsNetCID.h" 19 #include "nsIPipe.h" 20 #include "nsIOutputStream.h" 21 #include "nsCExternalHandlerService.h" 22 #include "nsILocalFileMac.h" 23 #include "nsIFileURL.h" 24 #include "nsTArray.h" 25 #include "nsObjCExceptions.h" 26 #include "nsProxyRelease.h" 27 #include "nsContentSecurityManager.h" 28 #include "nsNetUtil.h" 29 #include "mozilla/RefPtr.h" 30 #include "mozilla/UniquePtrExtensions.h" 31 32 #include <Cocoa/Cocoa.h> 33 34 using namespace mozilla; 35 36 // nsIconChannel methods 37 nsIconChannel::nsIconChannel() {} 38 39 nsIconChannel::~nsIconChannel() { 40 if (mLoadInfo) { 41 NS_ReleaseOnMainThread("nsIconChannel::mLoadInfo", mLoadInfo.forget()); 42 } 43 } 44 45 NS_IMPL_ISUPPORTS(nsIconChannel, nsIChannel, nsIRequest, nsIRequestObserver, 46 nsIStreamListener) 47 48 nsresult nsIconChannel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) { 49 NS_ASSERTION(uri, "no uri"); 50 mUrl = uri; 51 mOriginalURI = uri; 52 mLoadInfo = aLoadInfo; 53 nsresult rv; 54 mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); 55 return rv; 56 } 57 58 //////////////////////////////////////////////////////////////////////////////// 59 // nsIRequest methods: 60 61 NS_IMETHODIMP 62 nsIconChannel::GetName(nsACString& result) { return mUrl->GetSpec(result); } 63 64 NS_IMETHODIMP 65 nsIconChannel::IsPending(bool* result) { return mPump->IsPending(result); } 66 67 NS_IMETHODIMP 68 nsIconChannel::GetStatus(nsresult* status) { return mPump->GetStatus(status); } 69 70 NS_IMETHODIMP nsIconChannel::SetCanceledReason(const nsACString& aReason) { 71 return SetCanceledReasonImpl(aReason); 72 } 73 74 NS_IMETHODIMP nsIconChannel::GetCanceledReason(nsACString& aReason) { 75 return GetCanceledReasonImpl(aReason); 76 } 77 78 NS_IMETHODIMP nsIconChannel::CancelWithReason(nsresult aStatus, 79 const nsACString& aReason) { 80 return CancelWithReasonImpl(aStatus, aReason); 81 } 82 83 NS_IMETHODIMP 84 nsIconChannel::Cancel(nsresult status) { 85 mCanceled = true; 86 return mPump->Cancel(status); 87 } 88 89 NS_IMETHODIMP 90 nsIconChannel::GetCanceled(bool* result) { 91 *result = mCanceled; 92 return NS_OK; 93 } 94 95 NS_IMETHODIMP 96 nsIconChannel::Suspend(void) { return mPump->Suspend(); } 97 98 NS_IMETHODIMP 99 nsIconChannel::Resume(void) { return mPump->Resume(); } 100 101 // nsIRequestObserver methods 102 NS_IMETHODIMP 103 nsIconChannel::OnStartRequest(nsIRequest* aRequest) { 104 if (mListener) { 105 return mListener->OnStartRequest(this); 106 } 107 return NS_OK; 108 } 109 110 NS_IMETHODIMP 111 nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { 112 if (mListener) { 113 mListener->OnStopRequest(this, aStatus); 114 mListener = nullptr; 115 } 116 117 // Remove from load group 118 if (mLoadGroup) { 119 mLoadGroup->RemoveRequest(this, nullptr, aStatus); 120 } 121 122 return NS_OK; 123 } 124 125 // nsIStreamListener methods 126 NS_IMETHODIMP 127 nsIconChannel::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream, 128 uint64_t aOffset, uint32_t aCount) { 129 if (mListener) { 130 return mListener->OnDataAvailable(this, aStream, aOffset, aCount); 131 } 132 return NS_OK; 133 } 134 135 //////////////////////////////////////////////////////////////////////////////// 136 // nsIChannel methods: 137 138 NS_IMETHODIMP 139 nsIconChannel::GetOriginalURI(nsIURI** aURI) { 140 *aURI = mOriginalURI; 141 NS_ADDREF(*aURI); 142 return NS_OK; 143 } 144 145 NS_IMETHODIMP 146 nsIconChannel::SetOriginalURI(nsIURI* aURI) { 147 NS_ENSURE_ARG_POINTER(aURI); 148 mOriginalURI = aURI; 149 return NS_OK; 150 } 151 152 NS_IMETHODIMP 153 nsIconChannel::GetURI(nsIURI** aURI) { 154 *aURI = mUrl; 155 NS_IF_ADDREF(*aURI); 156 return NS_OK; 157 } 158 159 NS_IMETHODIMP 160 nsIconChannel::Open(nsIInputStream** _retval) { 161 nsCOMPtr<nsIStreamListener> listener; 162 nsresult rv = 163 nsContentSecurityManager::doContentSecurityCheck(this, listener); 164 NS_ENSURE_SUCCESS(rv, rv); 165 166 return MakeInputStream(_retval, false); 167 } 168 169 nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile, 170 uint32_t* aDesiredImageSize, 171 nsACString& aContentType, 172 nsACString& aFileExtension) { 173 nsresult rv = NS_OK; 174 nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv)); 175 NS_ENSURE_SUCCESS(rv, rv); 176 177 iconURI->GetImageSize(aDesiredImageSize); 178 iconURI->GetContentType(aContentType); 179 iconURI->GetFileExtension(aFileExtension); 180 181 nsCOMPtr<nsIURL> url; 182 rv = iconURI->GetIconURL(getter_AddRefs(url)); 183 if (NS_FAILED(rv) || !url) return NS_OK; 184 185 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv); 186 if (NS_FAILED(rv) || !fileURL) return NS_OK; 187 188 nsCOMPtr<nsIFile> file; 189 rv = fileURL->GetFile(getter_AddRefs(file)); 190 if (NS_FAILED(rv) || !file) return NS_OK; 191 192 nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(file, &rv)); 193 if (NS_FAILED(rv) || !localFileMac) return NS_OK; 194 195 *aLocalFile = file; 196 NS_IF_ADDREF(*aLocalFile); 197 198 return NS_OK; 199 } 200 201 NS_IMETHODIMP 202 nsIconChannel::AsyncOpen(nsIStreamListener* aListener) { 203 nsCOMPtr<nsIStreamListener> listener = aListener; 204 nsresult rv = 205 nsContentSecurityManager::doContentSecurityCheck(this, listener); 206 if (NS_FAILED(rv)) { 207 mCallbacks = nullptr; 208 return rv; 209 } 210 211 MOZ_ASSERT( 212 mLoadInfo->GetSecurityMode() == 0 || 213 mLoadInfo->GetInitialSecurityCheckDone() || 214 (mLoadInfo->GetSecurityMode() == 215 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && 216 mLoadInfo->GetLoadingPrincipal() && 217 mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), 218 "security flags in loadInfo but doContentSecurityCheck() not called"); 219 220 nsCOMPtr<nsIInputStream> inStream; 221 rv = MakeInputStream(getter_AddRefs(inStream), true); 222 if (NS_FAILED(rv)) { 223 mCallbacks = nullptr; 224 return rv; 225 } 226 227 // Init our stream pump 228 nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget(); 229 rv = mPump->Init(inStream, 0, 0, false, target); 230 if (NS_FAILED(rv)) { 231 mCallbacks = nullptr; 232 return rv; 233 } 234 235 rv = mPump->AsyncRead(this); 236 if (NS_SUCCEEDED(rv)) { 237 // Store our real listener 238 mListener = aListener; 239 // Add ourself to the load group, if available 240 if (mLoadGroup) { 241 mLoadGroup->AddRequest(this, nullptr); 242 } 243 } else { 244 mCallbacks = nullptr; 245 } 246 247 return rv; 248 } 249 250 nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, 251 bool aNonBlocking) { 252 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 253 254 nsCString contentType; 255 nsAutoCString fileExt; 256 nsCOMPtr<nsIFile> fileloc; // file we want an icon for 257 uint32_t desiredImageSize; 258 nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc), 259 &desiredImageSize, contentType, fileExt); 260 NS_ENSURE_SUCCESS(rv, rv); 261 262 bool fileExists = false; 263 if (fileloc) { 264 fileloc->Exists(&fileExists); 265 } 266 267 NSImage* iconImage = nil; 268 269 // first try to get the icon from the file if it exists 270 if (fileExists) { 271 nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv)); 272 NS_ENSURE_SUCCESS(rv, rv); 273 274 CFURLRef macURL; 275 if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) { 276 iconImage = 277 [[NSWorkspace sharedWorkspace] iconForFile:[(NSURL*)macURL path]]; 278 ::CFRelease(macURL); 279 } 280 } 281 282 // if we don't have an icon yet try to get one by extension 283 if (!iconImage && !fileExt.IsEmpty()) { 284 NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()]; 285 iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension]; 286 } 287 288 // If we still don't have an icon, get the generic document icon. 289 if (!iconImage) { 290 iconImage = 291 [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeUnknown]; 292 } 293 294 if (!iconImage) { 295 return NS_ERROR_FAILURE; 296 } 297 298 if (desiredImageSize > 255) { 299 // The Icon image format represents width and height as u8, so it does not 300 // allow for images sized 256 or more. 301 return NS_ERROR_FAILURE; 302 } 303 304 // Set the actual size to *twice* the requested size. 305 // We do this because our UI doesn't take the window's device pixel ratio into 306 // account when it requests these icons; e.g. it will request an icon with 307 // size 16, place it in a 16x16 CSS pixel sized image, and then display it in 308 // a window on a HiDPI screen where the icon now covers 32x32 physical screen 309 // pixels. So we just always double the size here in order to prevent 310 // blurriness. 311 uint32_t size = 312 (desiredImageSize < 128) ? desiredImageSize * 2 : desiredImageSize; 313 uint32_t width = size; 314 uint32_t height = size; 315 316 // The "image format" we're outputting here (and which gets decoded by 317 // nsIconDecoder) has the following format: 318 // - 1 byte for the image width, as u8 319 // - 1 byte for the image height, as u8 320 // - 1 byte for format 321 // - 1 byte for color transform 322 // - the raw image data as BGRA, width * height * 4 bytes. 323 size_t bufferCapacity = 4 + width * height * 4; 324 UniquePtr<uint8_t[]> fileBuf = MakeUniqueFallible<uint8_t[]>(bufferCapacity); 325 if (NS_WARN_IF(!fileBuf)) { 326 return NS_ERROR_OUT_OF_MEMORY; 327 } 328 329 fileBuf[0] = uint8_t(width); 330 fileBuf[1] = uint8_t(height); 331 fileBuf[2] = uint8_t(mozilla::gfx::SurfaceFormat::B8G8R8A8); 332 333 // Clear all bits to ensure in nsIconDecoder we assume we are already color 334 // managed and premultiplied. 335 fileBuf[3] = 0; 336 337 uint8_t* imageBuf = &fileBuf[4]; 338 339 // Create a CGBitmapContext around imageBuf and draw iconImage to it. 340 // This gives us the image data in the format we want: BGRA, four bytes per 341 // pixel, in host endianness, with premultiplied alpha. 342 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); 343 CGContextRef ctx = CGBitmapContextCreate( 344 imageBuf, width, height, 8 /* bitsPerComponent */, width * 4, cs, 345 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); 346 CGColorSpaceRelease(cs); 347 348 NSGraphicsContext* oldContext = [NSGraphicsContext currentContext]; 349 [NSGraphicsContext 350 setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:ctx 351 flipped:NO]]; 352 353 [iconImage drawInRect:NSMakeRect(0, 0, width, height)]; 354 355 [NSGraphicsContext setCurrentContext:oldContext]; 356 357 CGContextRelease(ctx); 358 359 // Now, create a pipe and stuff our data into it 360 nsCOMPtr<nsIInputStream> inStream; 361 nsCOMPtr<nsIOutputStream> outStream; 362 NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), 363 bufferCapacity, bufferCapacity, aNonBlocking); 364 365 uint32_t written; 366 rv = outStream->Write((char*)fileBuf.get(), bufferCapacity, &written); 367 if (NS_SUCCEEDED(rv)) { 368 NS_IF_ADDREF(*_retval = inStream); 369 } 370 371 // Drop notification callbacks to prevent cycles. 372 mCallbacks = nullptr; 373 374 return NS_OK; 375 376 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); 377 } 378 379 NS_IMETHODIMP 380 nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes) { 381 return mPump->GetLoadFlags(aLoadAttributes); 382 } 383 384 NS_IMETHODIMP 385 nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) { 386 return mPump->SetLoadFlags(aLoadAttributes); 387 } 388 389 NS_IMETHODIMP 390 nsIconChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { 391 return GetTRRModeImpl(aTRRMode); 392 } 393 394 NS_IMETHODIMP 395 nsIconChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { 396 return SetTRRModeImpl(aTRRMode); 397 } 398 399 NS_IMETHODIMP 400 nsIconChannel::GetIsDocument(bool* aIsDocument) { 401 return NS_GetIsDocumentChannel(this, aIsDocument); 402 } 403 404 NS_IMETHODIMP 405 nsIconChannel::GetContentType(nsACString& aContentType) { 406 aContentType.AssignLiteral(IMAGE_ICON_MS); 407 return NS_OK; 408 } 409 410 NS_IMETHODIMP 411 nsIconChannel::SetContentType(const nsACString& aContentType) { 412 // It doesn't make sense to set the content-type on this type 413 // of channel... 414 return NS_ERROR_FAILURE; 415 } 416 417 NS_IMETHODIMP 418 nsIconChannel::GetContentCharset(nsACString& aContentCharset) { 419 aContentCharset.AssignLiteral(IMAGE_ICON_MS); 420 return NS_OK; 421 } 422 423 NS_IMETHODIMP 424 nsIconChannel::SetContentCharset(const nsACString& aContentCharset) { 425 // It doesn't make sense to set the content-type on this type 426 // of channel... 427 return NS_ERROR_FAILURE; 428 } 429 430 NS_IMETHODIMP 431 nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition) { 432 return NS_ERROR_NOT_AVAILABLE; 433 } 434 435 NS_IMETHODIMP 436 nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) { 437 return NS_ERROR_NOT_AVAILABLE; 438 } 439 440 NS_IMETHODIMP 441 nsIconChannel::GetContentDispositionFilename( 442 nsAString& aContentDispositionFilename) { 443 return NS_ERROR_NOT_AVAILABLE; 444 } 445 446 NS_IMETHODIMP 447 nsIconChannel::SetContentDispositionFilename( 448 const nsAString& aContentDispositionFilename) { 449 return NS_ERROR_NOT_AVAILABLE; 450 } 451 452 NS_IMETHODIMP 453 nsIconChannel::GetContentDispositionHeader( 454 nsACString& aContentDispositionHeader) { 455 return NS_ERROR_NOT_AVAILABLE; 456 } 457 458 NS_IMETHODIMP 459 nsIconChannel::GetContentLength(int64_t* aContentLength) { 460 *aContentLength = 0; 461 return NS_ERROR_FAILURE; 462 } 463 464 NS_IMETHODIMP 465 nsIconChannel::SetContentLength(int64_t aContentLength) { 466 MOZ_ASSERT_UNREACHABLE("nsIconChannel::SetContentLength"); 467 return NS_ERROR_NOT_IMPLEMENTED; 468 } 469 470 NS_IMETHODIMP 471 nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { 472 *aLoadGroup = mLoadGroup; 473 NS_IF_ADDREF(*aLoadGroup); 474 return NS_OK; 475 } 476 477 NS_IMETHODIMP 478 nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { 479 mLoadGroup = aLoadGroup; 480 return NS_OK; 481 } 482 483 NS_IMETHODIMP 484 nsIconChannel::GetOwner(nsISupports** aOwner) { 485 *aOwner = mOwner.get(); 486 NS_IF_ADDREF(*aOwner); 487 return NS_OK; 488 } 489 490 NS_IMETHODIMP 491 nsIconChannel::SetOwner(nsISupports* aOwner) { 492 mOwner = aOwner; 493 return NS_OK; 494 } 495 496 NS_IMETHODIMP 497 nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { 498 NS_IF_ADDREF(*aLoadInfo = mLoadInfo); 499 return NS_OK; 500 } 501 502 NS_IMETHODIMP 503 nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { 504 MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null"); 505 mLoadInfo = aLoadInfo; 506 return NS_OK; 507 } 508 509 NS_IMETHODIMP 510 nsIconChannel::GetNotificationCallbacks( 511 nsIInterfaceRequestor** aNotificationCallbacks) { 512 *aNotificationCallbacks = mCallbacks.get(); 513 NS_IF_ADDREF(*aNotificationCallbacks); 514 return NS_OK; 515 } 516 517 NS_IMETHODIMP 518 nsIconChannel::SetNotificationCallbacks( 519 nsIInterfaceRequestor* aNotificationCallbacks) { 520 mCallbacks = aNotificationCallbacks; 521 return NS_OK; 522 } 523 524 NS_IMETHODIMP 525 nsIconChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { 526 *aSecurityInfo = nullptr; 527 return NS_OK; 528 }