imgRequestProxy.cpp (39068B)
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 "imgRequestProxy.h" 8 9 #include <utility> 10 11 #include "Image.h" 12 #include "ImageLogging.h" 13 #include "ImageOps.h" 14 #include "ImageTypes.h" 15 #include "imgINotificationObserver.h" 16 #include "imgLoader.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/DocGroup.h" // for DocGroup 19 #include "nsCRTGlue.h" 20 #include "nsError.h" 21 22 using namespace mozilla; 23 using namespace mozilla::image; 24 25 // The split of imgRequestProxy and imgRequestProxyStatic means that 26 // certain overridden functions need to be usable in the destructor. 27 // Since virtual functions can't be used in that way, this class 28 // provides a behavioural trait for each class to use instead. 29 class ProxyBehaviour { 30 public: 31 virtual ~ProxyBehaviour() = default; 32 33 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0; 34 virtual bool HasImage() const = 0; 35 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0; 36 virtual imgRequest* GetOwner() const = 0; 37 virtual void SetOwner(imgRequest* aOwner) = 0; 38 }; 39 40 class RequestBehaviour : public ProxyBehaviour { 41 public: 42 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {} 43 44 already_AddRefed<mozilla::image::Image> GetImage() const override; 45 bool HasImage() const override; 46 already_AddRefed<ProgressTracker> GetProgressTracker() const override; 47 48 imgRequest* GetOwner() const override { return mOwner; } 49 50 void SetOwner(imgRequest* aOwner) override { 51 mOwner = aOwner; 52 53 if (mOwner) { 54 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker(); 55 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage(); 56 } else { 57 mOwnerHasImage = false; 58 } 59 } 60 61 private: 62 // We maintain the following invariant: 63 // The proxy is registered at most with a single imgRequest as an observer, 64 // and whenever it is, mOwner points to that object. This helps ensure that 65 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer 66 // from whatever request it was registered with (if any). This, in turn, 67 // means that imgRequest::mObservers will not have any stale pointers in it. 68 RefPtr<imgRequest> mOwner; 69 70 bool mOwnerHasImage; 71 }; 72 73 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const { 74 if (!mOwnerHasImage) { 75 return nullptr; 76 } 77 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 78 return progressTracker->GetImage(); 79 } 80 81 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const { 82 // NOTE: It's possible that our mOwner has an Image that it didn't notify 83 // us about, if we were Canceled before its Image was constructed. 84 // (Canceling removes us as an observer, so mOwner has no way to notify us). 85 // That's why this method uses mOwner->GetProgressTracker() instead of just 86 // mOwner->mProgressTracker -- we might have a null mImage and yet have an 87 // mOwner with a non-null mImage (and a null mProgressTracker pointer). 88 return mOwner->GetProgressTracker(); 89 } 90 91 NS_IMPL_ADDREF(imgRequestProxy) 92 NS_IMPL_RELEASE(imgRequestProxy) 93 94 NS_INTERFACE_MAP_BEGIN(imgRequestProxy) 95 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase) 96 NS_INTERFACE_MAP_ENTRY(imgIRequest) 97 NS_INTERFACE_MAP_ENTRY(nsIRequest) 98 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) 99 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy) 100 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr) 101 NS_INTERFACE_MAP_END 102 103 imgRequestProxy::imgRequestProxy() 104 : mBehaviour(new RequestBehaviour), 105 mURI(nullptr), 106 mListener(nullptr), 107 mLoadFlags(nsIRequest::LOAD_NORMAL), 108 mLockCount(0), 109 mAnimationConsumers(0), 110 mCancelable(true), 111 mCanceled(false), 112 mIsInLoadGroup(false), 113 mForceDispatchLoadGroup(false), 114 mListenerIsStrongRef(false), 115 mDecodeRequested(false), 116 mPendingNotify(false), 117 mValidating(false) { 118 /* member initializers and constructor code */ 119 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy"); 120 } 121 122 imgRequestProxy::~imgRequestProxy() { 123 /* destructor code */ 124 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!"); 125 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?"); 126 127 ClearAnimationConsumers(); 128 129 // Explicitly set mListener to null to ensure that the RemoveProxy 130 // call below can't send |this| to an arbitrary listener while |this| 131 // is being destroyed. This is all belt-and-suspenders in view of the 132 // above assert. 133 NullOutListener(); 134 135 /* Call RemoveProxy with a successful status. This will keep the 136 channel, if still downloading data, from being canceled if 'this' is 137 the last observer. This allows the image to continue to download and 138 be cached even if no one is using it currently. 139 */ 140 mCanceled = true; 141 RemoveFromOwner(NS_OK); 142 143 RemoveFromLoadGroup(); 144 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy"); 145 } 146 147 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup, 148 nsIURI* aURI, 149 imgINotificationObserver* aObserver) { 150 MOZ_ASSERT(!GetOwner() && !mListener, 151 "imgRequestProxy is already initialized"); 152 153 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner); 154 155 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init"); 156 157 mBehaviour->SetOwner(aOwner); 158 mListener = aObserver; 159 // Make sure to addref mListener before the AddToOwner call below, since 160 // that call might well want to release it if the imgRequest has 161 // already seen OnStopRequest. 162 if (mListener) { 163 mListenerIsStrongRef = true; 164 NS_ADDREF(mListener); 165 } 166 mLoadGroup = aLoadGroup; 167 mURI = aURI; 168 169 // Note: AddToOwner won't send all the On* notifications immediately 170 AddToOwner(); 171 172 return NS_OK; 173 } 174 175 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) { 176 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!"); 177 178 if (mCanceled) { 179 // Ensure that this proxy has received all notifications to date 180 // before we clean it up when removing it from the old owner below. 181 SyncNotifyListener(); 182 } 183 184 // If we're holding locks, unlock the old image. 185 // Note that UnlockImage decrements mLockCount each time it's called. 186 uint32_t oldLockCount = mLockCount; 187 while (mLockCount) { 188 UnlockImage(); 189 } 190 191 // If we're holding animation requests, undo them. 192 uint32_t oldAnimationConsumers = mAnimationConsumers; 193 ClearAnimationConsumers(); 194 195 GetOwner()->RemoveProxy(this, NS_OK); 196 197 mBehaviour->SetOwner(aNewOwner); 198 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!"); 199 200 // If we were locked, apply the locks here 201 for (uint32_t i = 0; i < oldLockCount; i++) { 202 LockImage(); 203 } 204 205 // If we had animation requests, restore them here. Note that we 206 // do this *after* RemoveProxy, which clears out animation consumers 207 // (see bug 601723). 208 for (uint32_t i = 0; i < oldAnimationConsumers; i++) { 209 IncrementAnimationConsumers(); 210 } 211 212 AddToOwner(); 213 return NS_OK; 214 } 215 216 NS_IMETHODIMP imgRequestProxy::GetTriggeringPrincipal( 217 nsIPrincipal** aTriggeringPrincipal) { 218 MOZ_ASSERT(GetOwner()); 219 nsCOMPtr<nsIPrincipal> triggeringPrincipal = 220 GetOwner()->GetTriggeringPrincipal(); 221 triggeringPrincipal.forget(aTriggeringPrincipal); 222 return NS_OK; 223 } 224 225 void imgRequestProxy::MarkValidating() { 226 MOZ_ASSERT(GetValidator()); 227 mValidating = true; 228 } 229 230 void imgRequestProxy::ClearValidating() { 231 MOZ_ASSERT(mValidating); 232 MOZ_ASSERT(!GetValidator()); 233 mValidating = false; 234 235 // If we'd previously requested a synchronous decode, request a decode on the 236 // new image. 237 if (mDecodeRequested) { 238 mDecodeRequested = false; 239 StartDecoding(imgIContainer::FLAG_NONE); 240 } 241 } 242 243 bool imgRequestProxy::HasDecodedPixels() { 244 if (IsValidating()) { 245 return false; 246 } 247 248 RefPtr<Image> image = GetImage(); 249 if (image) { 250 return image->HasDecodedPixels(); 251 } 252 253 return false; 254 } 255 256 nsresult imgRequestProxy::DispatchWithTargetIfAvailable( 257 already_AddRefed<nsIRunnable> aEvent) { 258 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable"); 259 return NS_DispatchToMainThread( 260 CreateRenderBlockingRunnable(std::move(aEvent))); 261 } 262 263 void imgRequestProxy::AddToOwner() { 264 imgRequest* owner = GetOwner(); 265 if (!owner) { 266 return; 267 } 268 269 owner->AddProxy(this); 270 } 271 272 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) { 273 imgRequest* owner = GetOwner(); 274 if (owner) { 275 if (mValidating) { 276 imgCacheValidator* validator = owner->GetValidator(); 277 MOZ_ASSERT(validator); 278 validator->RemoveProxy(this); 279 mValidating = false; 280 } 281 282 owner->RemoveProxy(this, aStatus); 283 } 284 } 285 286 void imgRequestProxy::AddToLoadGroup() { 287 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!"); 288 MOZ_ASSERT(!mForceDispatchLoadGroup); 289 290 /* While in theory there could be a dispatch outstanding to remove this 291 request from the load group, in practice we only add to the load group 292 (when previously not in a load group) at initialization. */ 293 if (!mIsInLoadGroup && mLoadGroup) { 294 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup"); 295 mLoadGroup->AddRequest(this, nullptr); 296 mIsInLoadGroup = true; 297 } 298 } 299 300 void imgRequestProxy::RemoveFromLoadGroup() { 301 if (!mIsInLoadGroup || !mLoadGroup) { 302 return; 303 } 304 305 /* Sometimes we may not be able to remove ourselves from the load group in 306 the current context. This is because our listeners are not re-entrant (e.g. 307 we are in the middle of CancelAndForgetObserver or SyncClone). */ 308 if (mForceDispatchLoadGroup) { 309 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch"); 310 311 /* We take away the load group from the request temporarily; this prevents 312 additional dispatches via RemoveFromLoadGroup occurring, as well as 313 MoveToBackgroundInLoadGroup from removing and readding. This is safe 314 because we know that once we get here, blocking the load group at all is 315 unnecessary. */ 316 mIsInLoadGroup = false; 317 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup); 318 RefPtr<imgRequestProxy> self(this); 319 DispatchWithTargetIfAvailable(NS_NewRunnableFunction( 320 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void { 321 loadGroup->RemoveRequest(self, nullptr, NS_OK); 322 })); 323 return; 324 } 325 326 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup"); 327 328 /* calling RemoveFromLoadGroup may cause the document to finish 329 loading, which could result in our death. We need to make sure 330 that we stay alive long enough to fight another battle... at 331 least until we exit this function. */ 332 nsCOMPtr<imgIRequest> kungFuDeathGrip(this); 333 mLoadGroup->RemoveRequest(this, nullptr, NS_OK); 334 mLoadGroup = nullptr; 335 mIsInLoadGroup = false; 336 } 337 338 void imgRequestProxy::MoveToBackgroundInLoadGroup() { 339 /* Even if we are still in the load group, we may have taken away the load 340 group reference itself because we are in the process of leaving the group. 341 In that case, there is no need to background the request. */ 342 if (!mLoadGroup) { 343 return; 344 } 345 346 /* There is no need to dispatch if we only need to add ourselves to the load 347 group without removal. It is the removal which causes the problematic 348 callbacks (see RemoveFromLoadGroup). */ 349 if (mIsInLoadGroup && mForceDispatchLoadGroup) { 350 LOG_FUNC(gImgLog, 351 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch"); 352 353 RefPtr<imgRequestProxy> self(this); 354 DispatchWithTargetIfAvailable(NS_NewRunnableFunction( 355 "imgRequestProxy::MoveToBackgroundInLoadGroup", 356 [self]() -> void { self->MoveToBackgroundInLoadGroup(); })); 357 return; 358 } 359 360 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup"); 361 nsCOMPtr<imgIRequest> kungFuDeathGrip(this); 362 if (mIsInLoadGroup) { 363 mLoadGroup->RemoveRequest(this, nullptr, NS_OK); 364 } 365 366 mLoadFlags |= nsIRequest::LOAD_BACKGROUND; 367 mLoadGroup->AddRequest(this, nullptr); 368 } 369 370 /** nsIRequest / imgIRequest methods **/ 371 372 NS_IMETHODIMP 373 imgRequestProxy::GetName(nsACString& aName) { 374 aName.Truncate(); 375 376 if (mURI) { 377 mURI->GetSpec(aName); 378 } 379 380 return NS_OK; 381 } 382 383 NS_IMETHODIMP 384 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; } 385 386 NS_IMETHODIMP 387 imgRequestProxy::GetStatus(nsresult* aStatus) { 388 return NS_ERROR_NOT_IMPLEMENTED; 389 } 390 391 NS_IMETHODIMP imgRequestProxy::SetCanceledReason(const nsACString& aReason) { 392 return SetCanceledReasonImpl(aReason); 393 } 394 395 NS_IMETHODIMP imgRequestProxy::GetCanceledReason(nsACString& aReason) { 396 return GetCanceledReasonImpl(aReason); 397 } 398 399 NS_IMETHODIMP imgRequestProxy::CancelWithReason(nsresult aStatus, 400 const nsACString& aReason) { 401 return CancelWithReasonImpl(aStatus, aReason); 402 } 403 404 void imgRequestProxy::SetCancelable(bool aCancelable) { 405 MOZ_ASSERT(NS_IsMainThread()); 406 mCancelable = aCancelable; 407 } 408 409 NS_IMETHODIMP 410 imgRequestProxy::Cancel(nsresult status) { 411 if (mCanceled) { 412 return NS_ERROR_FAILURE; 413 } 414 415 if (NS_WARN_IF(!mCancelable)) { 416 return NS_ERROR_FAILURE; 417 } 418 419 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel"); 420 421 mCanceled = true; 422 423 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status); 424 return DispatchWithTargetIfAvailable(ev.forget()); 425 } 426 427 void imgRequestProxy::DoCancel(nsresult status) { 428 RemoveFromOwner(status); 429 RemoveFromLoadGroup(); 430 NullOutListener(); 431 } 432 433 NS_IMETHODIMP 434 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) { 435 // If mCanceled is true but mListener is non-null, that means 436 // someone called Cancel() on us but the imgCancelRunnable is still 437 // pending. We still need to null out mListener before returning 438 // from this function in this case. That means we want to do the 439 // RemoveProxy call right now, because we need to deliver the 440 // onStopRequest. 441 if (mCanceled && !mListener) { 442 return NS_ERROR_FAILURE; 443 } 444 445 if (NS_WARN_IF(!mCancelable)) { 446 MOZ_ASSERT(mCancelable, 447 "Shouldn't try to cancel non-cancelable requests via " 448 "CancelAndForgetObserver"); 449 return NS_ERROR_FAILURE; 450 } 451 452 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver"); 453 454 mCanceled = true; 455 mForceDispatchLoadGroup = true; 456 RemoveFromOwner(aStatus); 457 RemoveFromLoadGroup(); 458 mForceDispatchLoadGroup = false; 459 460 NullOutListener(); 461 462 return NS_OK; 463 } 464 465 NS_IMETHODIMP 466 imgRequestProxy::StartDecoding(uint32_t aFlags) { 467 // Flag this, so we know to request after validation if pending. 468 if (IsValidating()) { 469 mDecodeRequested = true; 470 return NS_OK; 471 } 472 473 RefPtr<Image> image = GetImage(); 474 if (image) { 475 return image->StartDecoding(aFlags); 476 } 477 478 if (GetOwner()) { 479 GetOwner()->StartDecoding(); 480 } 481 482 return NS_OK; 483 } 484 485 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) { 486 // Flag this, so we know to request after validation if pending. 487 if (IsValidating()) { 488 mDecodeRequested = true; 489 return false; 490 } 491 492 RefPtr<Image> image = GetImage(); 493 if (image) { 494 return image->StartDecodingWithResult(aFlags); 495 } 496 497 if (GetOwner()) { 498 GetOwner()->StartDecoding(); 499 } 500 501 return false; 502 } 503 504 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult( 505 uint32_t aFlags) { 506 if (IsValidating()) { 507 mDecodeRequested = true; 508 return imgIContainer::DECODE_REQUESTED; 509 } 510 511 RefPtr<Image> image = GetImage(); 512 if (image) { 513 return image->RequestDecodeWithResult(aFlags); 514 } 515 516 if (GetOwner()) { 517 GetOwner()->StartDecoding(); 518 } 519 520 return imgIContainer::DECODE_REQUESTED; 521 } 522 523 NS_IMETHODIMP 524 imgRequestProxy::GetHasAnimationConsumers(bool* aIsLocked) { 525 *aIsLocked = !!mAnimationConsumers; 526 return NS_OK; 527 } 528 529 NS_IMETHODIMP 530 imgRequestProxy::LockImage() { 531 mLockCount++; 532 RefPtr<Image> image = 533 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr; 534 if (image) { 535 return image->LockImage(); 536 } 537 return NS_OK; 538 } 539 540 NS_IMETHODIMP 541 imgRequestProxy::UnlockImage() { 542 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!"); 543 544 mLockCount--; 545 RefPtr<Image> image = 546 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr; 547 if (image) { 548 return image->UnlockImage(); 549 } 550 return NS_OK; 551 } 552 553 NS_IMETHODIMP 554 imgRequestProxy::RequestDiscard() { 555 RefPtr<Image> image = GetImage(); 556 if (image) { 557 return image->RequestDiscard(); 558 } 559 return NS_OK; 560 } 561 562 NS_IMETHODIMP 563 imgRequestProxy::IncrementAnimationConsumers() { 564 mAnimationConsumers++; 565 RefPtr<Image> image = 566 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr; 567 if (image) { 568 image->IncrementAnimationConsumers(); 569 } 570 return NS_OK; 571 } 572 573 NS_IMETHODIMP 574 imgRequestProxy::DecrementAnimationConsumers() { 575 // We may get here if some responsible code called Increment, 576 // then called us, but we have meanwhile called ClearAnimationConsumers 577 // because we needed to get rid of them earlier (see 578 // imgRequest::RemoveProxy), and hence have nothing left to 579 // decrement. (In such a case we got rid of the animation consumers 580 // early, but not the observer.) 581 if (mAnimationConsumers > 0) { 582 mAnimationConsumers--; 583 RefPtr<Image> image = 584 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr; 585 if (image) { 586 image->DecrementAnimationConsumers(); 587 } 588 } 589 return NS_OK; 590 } 591 592 void imgRequestProxy::ClearAnimationConsumers() { 593 while (mAnimationConsumers > 0) { 594 DecrementAnimationConsumers(); 595 } 596 } 597 598 NS_IMETHODIMP 599 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; } 600 601 NS_IMETHODIMP 602 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; } 603 604 NS_IMETHODIMP 605 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) { 606 NS_IF_ADDREF(*loadGroup = mLoadGroup.get()); 607 return NS_OK; 608 } 609 NS_IMETHODIMP 610 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) { 611 if (loadGroup != mLoadGroup) { 612 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!"); 613 return NS_ERROR_NOT_IMPLEMENTED; 614 } 615 return NS_OK; 616 } 617 618 NS_IMETHODIMP 619 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) { 620 *flags = mLoadFlags; 621 return NS_OK; 622 } 623 NS_IMETHODIMP 624 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) { 625 mLoadFlags = flags; 626 return NS_OK; 627 } 628 629 NS_IMETHODIMP 630 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { 631 return GetTRRModeImpl(aTRRMode); 632 } 633 634 NS_IMETHODIMP 635 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) { 636 return SetTRRModeImpl(aTRRMode); 637 } 638 639 /** imgIRequest methods **/ 640 641 NS_IMETHODIMP 642 imgRequestProxy::GetImage(imgIContainer** aImage) { 643 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER); 644 // It's possible that our owner has an image but hasn't notified us of it - 645 // that'll happen if we get Canceled before the owner instantiates its image 646 // (because Canceling unregisters us as a listener on mOwner). If we're 647 // in that situation, just grab the image off of mOwner. 648 RefPtr<Image> image = GetImage(); 649 nsCOMPtr<imgIContainer> imageToReturn; 650 if (image) { 651 imageToReturn = image; 652 } 653 if (!imageToReturn && GetOwner()) { 654 imageToReturn = GetOwner()->GetImage(); 655 } 656 if (!imageToReturn) { 657 return NS_ERROR_FAILURE; 658 } 659 660 imageToReturn.swap(*aImage); 661 662 return NS_OK; 663 } 664 665 NS_IMETHODIMP 666 imgRequestProxy::GetProviderId(uint32_t* aId) { 667 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER); 668 669 nsCOMPtr<imgIContainer> image; 670 nsresult rv = GetImage(getter_AddRefs(image)); 671 if (NS_SUCCEEDED(rv)) { 672 *aId = image->GetProviderId(); 673 } else { 674 *aId = 0; 675 } 676 677 return NS_OK; 678 } 679 680 NS_IMETHODIMP 681 imgRequestProxy::GetImageStatus(uint32_t* aStatus) { 682 if (IsValidating()) { 683 // We are currently validating the image, and so our status could revert if 684 // we discard the cache. We should also be deferring notifications, such 685 // that the caller will be notified when validation completes. Rather than 686 // risk misleading the caller, return nothing. 687 *aStatus = imgIRequest::STATUS_NONE; 688 } else { 689 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 690 *aStatus = progressTracker->GetImageStatus(); 691 } 692 693 return NS_OK; 694 } 695 696 NS_IMETHODIMP 697 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) { 698 if (!GetOwner()) { 699 return NS_ERROR_FAILURE; 700 } 701 702 *aStatus = GetOwner()->GetImageErrorCode(); 703 704 return NS_OK; 705 } 706 707 NS_IMETHODIMP 708 imgRequestProxy::GetURI(nsIURI** aURI) { 709 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI"); 710 nsCOMPtr<nsIURI> uri = mURI; 711 uri.forget(aURI); 712 return NS_OK; 713 } 714 715 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) { 716 if (!GetOwner()) { 717 return NS_ERROR_FAILURE; 718 } 719 720 return GetOwner()->GetFinalURI(aURI); 721 } 722 723 NS_IMETHODIMP 724 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) { 725 *aObserver = mListener; 726 NS_IF_ADDREF(*aObserver); 727 return NS_OK; 728 } 729 730 NS_IMETHODIMP 731 imgRequestProxy::GetMimeType(char** aMimeType) { 732 if (!GetOwner()) { 733 return NS_ERROR_FAILURE; 734 } 735 736 const char* type = GetOwner()->GetMimeType(); 737 if (!type) { 738 return NS_ERROR_FAILURE; 739 } 740 741 *aMimeType = NS_xstrdup(type); 742 743 return NS_OK; 744 } 745 746 NS_IMETHODIMP 747 imgRequestProxy::GetFileName(nsACString& aFileName) { 748 if (!GetOwner()) { 749 return NS_ERROR_FAILURE; 750 } 751 752 GetOwner()->GetFileName(aFileName); 753 return NS_OK; 754 } 755 756 imgRequestProxy* imgRequestProxy::NewClonedProxy() { 757 return new imgRequestProxy(); 758 } 759 760 NS_IMETHODIMP 761 imgRequestProxy::Clone(imgINotificationObserver* aObserver, 762 imgIRequest** aClone) { 763 nsresult result; 764 imgRequestProxy* proxy; 765 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy); 766 *aClone = proxy; 767 return result; 768 } 769 770 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver, 771 Document* aLoadingDocument, 772 imgRequestProxy** aClone) { 773 return PerformClone(aObserver, aLoadingDocument, 774 /* aSyncNotify */ true, aClone); 775 } 776 777 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver, 778 Document* aLoadingDocument, 779 imgRequestProxy** aClone) { 780 return PerformClone(aObserver, aLoadingDocument, 781 /* aSyncNotify */ false, aClone); 782 } 783 784 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver, 785 Document* aLoadingDocument, 786 bool aSyncNotify, 787 imgRequestProxy** aClone) { 788 MOZ_ASSERT(aClone, "Null out param"); 789 790 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone"); 791 792 *aClone = nullptr; 793 RefPtr<imgRequestProxy> clone = NewClonedProxy(); 794 795 nsCOMPtr<nsILoadGroup> loadGroup; 796 if (aLoadingDocument) { 797 loadGroup = aLoadingDocument->GetDocumentLoadGroup(); 798 } 799 800 // It is important to call |SetLoadFlags()| before calling |Init()| because 801 // |Init()| adds the request to the loadgroup. 802 // When a request is added to a loadgroup, its load flags are merged 803 // with the load flags of the loadgroup. 804 // XXXldb That's not true anymore. Stuff from imgLoader adds the 805 // request to the loadgroup. 806 clone->SetLoadFlags(mLoadFlags); 807 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, mURI, aObserver); 808 if (NS_FAILED(rv)) { 809 return rv; 810 } 811 812 // Assign to *aClone before calling Notify so that if the caller expects to 813 // only be notified for requests it's already holding pointers to it won't be 814 // surprised. 815 NS_ADDREF(*aClone = clone); 816 817 imgCacheValidator* validator = GetValidator(); 818 if (validator) { 819 // Note that if we have a validator, we don't want to issue notifications at 820 // here because we want to defer until that completes. AddProxy will add us 821 // to the load group; we cannot avoid that in this case, because we don't 822 // know when the validation will complete, and if it will cause us to 823 // discard our cached state anyways. We are probably already blocked by the 824 // original LoadImage(WithChannel) request in any event. 825 clone->MarkValidating(); 826 validator->AddProxy(clone); 827 } else { 828 // We only want to add the request to the load group of the owning document 829 // if it is still in progress. Some callers cannot handle a supurious load 830 // group removal (e.g. print preview) so we must be careful. On the other 831 // hand, if after cloning, the original request proxy is cancelled / 832 // destroyed, we need to ensure that any clones still block the load group 833 // if it is incomplete. 834 bool addToLoadGroup = mIsInLoadGroup; 835 if (!addToLoadGroup) { 836 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker(); 837 addToLoadGroup = 838 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE); 839 } 840 841 if (addToLoadGroup) { 842 clone->AddToLoadGroup(); 843 } 844 845 if (aSyncNotify) { 846 // This is wrong!!! We need to notify asynchronously, but there's code 847 // that assumes that we don't. This will be fixed in bug 580466. Note that 848 // if we have a validator, we won't issue notifications anyways because 849 // they are deferred, so there is no point in requesting. 850 clone->mForceDispatchLoadGroup = true; 851 clone->SyncNotifyListener(); 852 clone->mForceDispatchLoadGroup = false; 853 } else { 854 // Without a validator, we can request asynchronous notifications 855 // immediately. If there was a validator, this would override the deferral 856 // and that would be incorrect. 857 clone->NotifyListener(); 858 } 859 } 860 861 return NS_OK; 862 } 863 864 NS_IMETHODIMP 865 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) { 866 if (!GetOwner()) { 867 return NS_ERROR_FAILURE; 868 } 869 870 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal(); 871 principal.forget(aPrincipal); 872 return NS_OK; 873 } 874 875 NS_IMETHODIMP 876 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) { 877 *aHadCrossOriginRedirects = false; 878 879 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel(); 880 if (timedChannel) { 881 bool allRedirectsSameOrigin = false; 882 *aHadCrossOriginRedirects = 883 NS_SUCCEEDED( 884 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) && 885 !allRedirectsSameOrigin; 886 } 887 888 return NS_OK; 889 } 890 891 NS_IMETHODIMP 892 imgRequestProxy::GetMultipart(bool* aMultipart) { 893 if (!GetOwner()) { 894 return NS_ERROR_FAILURE; 895 } 896 897 *aMultipart = GetOwner()->GetMultipart(); 898 return NS_OK; 899 } 900 901 NS_IMETHODIMP 902 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) { 903 if (!GetOwner()) { 904 return NS_ERROR_FAILURE; 905 } 906 907 *aCorsMode = GetOwner()->GetCORSMode(); 908 return NS_OK; 909 } 910 911 NS_IMETHODIMP 912 imgRequestProxy::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) { 913 if (!GetOwner()) { 914 return NS_ERROR_FAILURE; 915 } 916 917 nsCOMPtr<nsIReferrerInfo> referrerInfo = GetOwner()->GetReferrerInfo(); 918 referrerInfo.forget(aReferrerInfo); 919 return NS_OK; 920 } 921 922 NS_IMETHODIMP 923 imgRequestProxy::BoostPriority(uint32_t aCategory) { 924 NS_ENSURE_STATE(GetOwner() && !mCanceled); 925 GetOwner()->BoostPriority(aCategory); 926 return NS_OK; 927 } 928 929 /** nsISupportsPriority methods **/ 930 931 NS_IMETHODIMP 932 imgRequestProxy::GetPriority(int32_t* priority) { 933 NS_ENSURE_STATE(GetOwner()); 934 *priority = GetOwner()->Priority(); 935 return NS_OK; 936 } 937 938 NS_IMETHODIMP 939 imgRequestProxy::SetPriority(int32_t priority) { 940 NS_ENSURE_STATE(GetOwner() && !mCanceled); 941 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority()); 942 return NS_OK; 943 } 944 945 NS_IMETHODIMP 946 imgRequestProxy::AdjustPriority(int32_t priority) { 947 // We don't require |!mCanceled| here. This may be called even if we're 948 // cancelled, because it's invoked as part of the process of removing an image 949 // from the load group. 950 NS_ENSURE_STATE(GetOwner()); 951 GetOwner()->AdjustPriority(this, priority); 952 return NS_OK; 953 } 954 955 static const char* NotificationTypeToString(int32_t aType) { 956 switch (aType) { 957 case imgINotificationObserver::SIZE_AVAILABLE: 958 return "SIZE_AVAILABLE"; 959 case imgINotificationObserver::FRAME_UPDATE: 960 return "FRAME_UPDATE"; 961 case imgINotificationObserver::FRAME_COMPLETE: 962 return "FRAME_COMPLETE"; 963 case imgINotificationObserver::LOAD_COMPLETE: 964 return "LOAD_COMPLETE"; 965 case imgINotificationObserver::DECODE_COMPLETE: 966 return "DECODE_COMPLETE"; 967 case imgINotificationObserver::DISCARD: 968 return "DISCARD"; 969 case imgINotificationObserver::UNLOCKED_DRAW: 970 return "UNLOCKED_DRAW"; 971 case imgINotificationObserver::IS_ANIMATED: 972 return "IS_ANIMATED"; 973 case imgINotificationObserver::HAS_TRANSPARENCY: 974 return "HAS_TRANSPARENCY"; 975 default: 976 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive"); 977 return "(unknown notification)"; 978 } 979 } 980 981 void imgRequestProxy::Notify(int32_t aType, 982 const mozilla::gfx::IntRect* aRect) { 983 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE, 984 "Should call OnLoadComplete"); 985 986 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type", 987 NotificationTypeToString(aType)); 988 989 if (!mListener || mCanceled) { 990 return; 991 } 992 993 // Make sure the listener stays alive while we notify. 994 nsCOMPtr<imgINotificationObserver> listener(mListener); 995 996 listener->Notify(this, aType, aRect); 997 } 998 999 void imgRequestProxy::OnLoadComplete(bool aLastPart) { 1000 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI); 1001 1002 // There's all sorts of stuff here that could kill us (the OnStopRequest call 1003 // on the listener, the removal from the loadgroup, the release of the 1004 // listener, etc). Don't let them do it. 1005 RefPtr<imgRequestProxy> self(this); 1006 1007 if (mListener && !mCanceled) { 1008 // Hold a ref to the listener while we call it, just in case. 1009 nsCOMPtr<imgINotificationObserver> listener(mListener); 1010 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr); 1011 } 1012 1013 // If we're expecting more data from a multipart channel, re-add ourself 1014 // to the loadgroup so that the document doesn't lose track of the load. 1015 // If the request is already a background request and there's more data 1016 // coming, we can just leave the request in the loadgroup as-is. 1017 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) { 1018 if (aLastPart) { 1019 RemoveFromLoadGroup(); 1020 1021 nsresult errorCode = NS_OK; 1022 // if the load is cross origin without CORS, or the CORS access is 1023 // rejected, always fire load event to avoid leaking site information for 1024 // <link rel=preload>. 1025 // XXXedgar, currently we don't do the same thing for <img>. 1026 imgRequest* request = GetOwner(); 1027 if (!request || !(request->IsDeniedCrossSiteCORSRequest() || 1028 request->IsCrossSiteNoCORSRequest())) { 1029 uint32_t status = imgIRequest::STATUS_NONE; 1030 GetImageStatus(&status); 1031 if (status & imgIRequest::STATUS_ERROR) { 1032 errorCode = NS_ERROR_FAILURE; 1033 } 1034 } 1035 NotifyStop(errorCode); 1036 } else { 1037 // More data is coming, so change the request to be a background request 1038 // and put it back in the loadgroup. 1039 MoveToBackgroundInLoadGroup(); 1040 } 1041 } 1042 1043 if (mListenerIsStrongRef && aLastPart) { 1044 MOZ_ASSERT(mListener, "How did that happen?"); 1045 // Drop our strong ref to the listener now that we're done with 1046 // everything. Note that this can cancel us and other fun things 1047 // like that. Don't add anything in this method after this point. 1048 imgINotificationObserver* obs = mListener; 1049 mListenerIsStrongRef = false; 1050 NS_RELEASE(obs); 1051 } 1052 } 1053 1054 void imgRequestProxy::NullOutListener() { 1055 // If we have animation consumers, then they don't matter anymore 1056 if (mListener) { 1057 ClearAnimationConsumers(); 1058 } 1059 1060 if (mListenerIsStrongRef) { 1061 // Releasing could do weird reentery stuff, so just play it super-safe 1062 nsCOMPtr<imgINotificationObserver> obs; 1063 obs.swap(mListener); 1064 mListenerIsStrongRef = false; 1065 } else { 1066 mListener = nullptr; 1067 } 1068 } 1069 1070 NS_IMETHODIMP 1071 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) { 1072 RefPtr<imgRequestProxy> proxy = 1073 GetStaticRequest(static_cast<Document*>(nullptr)); 1074 if (proxy != this) { 1075 RefPtr<Image> image = GetImage(); 1076 if (image && image->HasError()) { 1077 // image/test/unit/test_async_notification_404.js needs this, but ideally 1078 // this special case can be removed from the scripted codepath. 1079 return NS_ERROR_FAILURE; 1080 } 1081 } 1082 proxy.forget(aReturn); 1083 return NS_OK; 1084 } 1085 1086 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest( 1087 Document* aLoadingDocument) { 1088 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument || 1089 aLoadingDocument->IsStaticDocument()); 1090 RefPtr<Image> image = GetImage(); 1091 1092 bool animated; 1093 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) { 1094 // Early exit - we're not animated, so we don't have to do anything. 1095 return do_AddRef(this); 1096 } 1097 1098 // We are animated. We need to create a frozen version of this image. 1099 RefPtr<Image> frozenImage = ImageOps::Freeze(image); 1100 1101 // Create a static imgRequestProxy with our new extracted frame. 1102 nsCOMPtr<nsIPrincipal> currentPrincipal; 1103 GetImagePrincipal(getter_AddRefs(currentPrincipal)); 1104 bool hadCrossOriginRedirects = true; 1105 GetHadCrossOriginRedirects(&hadCrossOriginRedirects); 1106 nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal(); 1107 RefPtr<imgRequestProxy> req = 1108 new imgRequestProxyStatic(frozenImage, currentPrincipal, 1109 triggeringPrincipal, hadCrossOriginRedirects); 1110 req->Init(nullptr, nullptr, mURI, nullptr); 1111 1112 return req.forget(); 1113 } 1114 1115 void imgRequestProxy::NotifyListener() { 1116 // It would be nice to notify the observer directly in the status tracker 1117 // instead of through the proxy, but there are several places we do extra 1118 // processing when we receive notifications (like OnStopRequest()), and we 1119 // need to check mCanceled everywhere too. 1120 1121 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 1122 if (GetOwner()) { 1123 // Send the notifications to our listener asynchronously. 1124 progressTracker->Notify(this); 1125 } else { 1126 // We don't have an imgRequest, so we can only notify the clone of our 1127 // current state, but we still have to do that asynchronously. 1128 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image"); 1129 progressTracker->NotifyCurrentState(this); 1130 } 1131 } 1132 1133 void imgRequestProxy::SyncNotifyListener() { 1134 // It would be nice to notify the observer directly in the status tracker 1135 // instead of through the proxy, but there are several places we do extra 1136 // processing when we receive notifications (like OnStopRequest()), and we 1137 // need to check mCanceled everywhere too. 1138 1139 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 1140 progressTracker->SyncNotify(this); 1141 } 1142 1143 void imgRequestProxy::SetHasImage() { 1144 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 1145 MOZ_ASSERT(progressTracker); 1146 RefPtr<Image> image = progressTracker->GetImage(); 1147 MOZ_ASSERT(image); 1148 1149 // Force any private status related to the owner to reflect 1150 // the presence of an image; 1151 mBehaviour->SetOwner(mBehaviour->GetOwner()); 1152 1153 // Apply any locks we have 1154 for (uint32_t i = 0; i < mLockCount; ++i) { 1155 image->LockImage(); 1156 } 1157 1158 // Apply any animation consumers we have 1159 for (uint32_t i = 0; i < mAnimationConsumers; i++) { 1160 image->IncrementAnimationConsumers(); 1161 } 1162 } 1163 1164 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const { 1165 return mBehaviour->GetProgressTracker(); 1166 } 1167 1168 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const { 1169 return mBehaviour->GetImage(); 1170 } 1171 1172 bool RequestBehaviour::HasImage() const { 1173 if (!mOwnerHasImage) { 1174 return false; 1175 } 1176 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 1177 return progressTracker ? progressTracker->HasImage() : false; 1178 } 1179 1180 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); } 1181 1182 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); } 1183 1184 imgCacheValidator* imgRequestProxy::GetValidator() const { 1185 imgRequest* owner = GetOwner(); 1186 if (!owner) { 1187 return nullptr; 1188 } 1189 return owner->GetValidator(); 1190 } 1191 1192 nsITimedChannel* imgRequestProxy::TimedChannel() { 1193 if (!GetOwner()) { 1194 return nullptr; 1195 } 1196 return GetOwner()->GetTimedChannel(); 1197 } 1198 1199 ////////////////// imgRequestProxyStatic methods 1200 1201 class StaticBehaviour : public ProxyBehaviour { 1202 public: 1203 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {} 1204 1205 already_AddRefed<mozilla::image::Image> GetImage() const override { 1206 RefPtr<mozilla::image::Image> image = mImage; 1207 return image.forget(); 1208 } 1209 1210 bool HasImage() const override { return mImage; } 1211 1212 already_AddRefed<ProgressTracker> GetProgressTracker() const override { 1213 return mImage->GetProgressTracker(); 1214 } 1215 1216 imgRequest* GetOwner() const override { return nullptr; } 1217 1218 void SetOwner(imgRequest* aOwner) override { 1219 MOZ_ASSERT(!aOwner, 1220 "We shouldn't be giving static requests a non-null owner."); 1221 } 1222 1223 private: 1224 // Our image. We have to hold a strong reference here, because that's normally 1225 // the job of the underlying request. 1226 RefPtr<mozilla::image::Image> mImage; 1227 }; 1228 1229 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage, 1230 nsIPrincipal* aImagePrincipal, 1231 nsIPrincipal* aTriggeringPrincipal, 1232 bool aHadCrossOriginRedirects) 1233 : mImagePrincipal(aImagePrincipal), 1234 mTriggeringPrincipal(aTriggeringPrincipal), 1235 mHadCrossOriginRedirects(aHadCrossOriginRedirects) { 1236 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage); 1237 } 1238 1239 NS_IMETHODIMP 1240 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) { 1241 if (!mImagePrincipal) { 1242 return NS_ERROR_FAILURE; 1243 } 1244 NS_ADDREF(*aPrincipal = mImagePrincipal); 1245 return NS_OK; 1246 } 1247 1248 NS_IMETHODIMP 1249 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) { 1250 NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal); 1251 return NS_OK; 1252 } 1253 1254 NS_IMETHODIMP 1255 imgRequestProxyStatic::GetHadCrossOriginRedirects( 1256 bool* aHadCrossOriginRedirects) { 1257 *aHadCrossOriginRedirects = mHadCrossOriginRedirects; 1258 return NS_OK; 1259 } 1260 1261 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() { 1262 nsCOMPtr<nsIPrincipal> currentPrincipal; 1263 GetImagePrincipal(getter_AddRefs(currentPrincipal)); 1264 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 1265 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); 1266 bool hadCrossOriginRedirects = true; 1267 GetHadCrossOriginRedirects(&hadCrossOriginRedirects); 1268 RefPtr<mozilla::image::Image> image = GetImage(); 1269 return new imgRequestProxyStatic(image, currentPrincipal, triggeringPrincipal, 1270 hadCrossOriginRedirects); 1271 }