summaryrefslogtreecommitdiffstats
path: root/dom/base/nsImageLoadingContent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsImageLoadingContent.cpp')
-rw-r--r--dom/base/nsImageLoadingContent.cpp1608
1 files changed, 1608 insertions, 0 deletions
diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp
new file mode 100644
index 000000000..d25dd6319
--- /dev/null
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -0,0 +1,1608 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 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/. */
+
+/*
+ * A base class which implements nsIImageLoadingContent and can be
+ * subclassed by various content nodes that want to provide image
+ * loading functionality (eg <img>, <object>, etc).
+ */
+
+#include "nsImageLoadingContent.h"
+#include "nsError.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsImageFrame.h"
+
+#include "nsIPresShell.h"
+
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+
+#include "nsIFrame.h"
+#include "nsIDOMNode.h"
+
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsIContentPolicy.h"
+#include "nsSVGEffects.h"
+
+#include "gfxPrefs.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Preferences.h"
+
+#ifdef LoadImage
+// Undefine LoadImage to prevent naming conflict with Windows.
+#undef LoadImage
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#ifdef DEBUG_chb
+static void PrintReqURL(imgIRequest* req) {
+ if (!req) {
+ printf("(null req)\n");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ req->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ printf("(null uri)\n");
+ return;
+ }
+
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ printf("spec='%s'\n", spec.get());
+}
+#endif /* DEBUG_chb */
+
+
+nsImageLoadingContent::nsImageLoadingContent()
+ : mCurrentRequestFlags(0),
+ mPendingRequestFlags(0),
+ mObserverList(nullptr),
+ mImageBlockingStatus(nsIContentPolicy::ACCEPT),
+ mLoadingEnabled(true),
+ mIsImageStateForced(false),
+ mLoading(false),
+ // mBroken starts out true, since an image without a URI is broken....
+ mBroken(true),
+ mUserDisabled(false),
+ mSuppressed(false),
+ mNewRequestsWillNeedAnimationReset(false),
+ mStateChangerDepth(0),
+ mCurrentRequestRegistered(false),
+ mPendingRequestRegistered(false),
+ mFrameCreateCalled(false)
+{
+ if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = false;
+ }
+
+ bool isInconsistent;
+ mMostRecentRequestChange = TimeStamp::ProcessCreation(isInconsistent);
+}
+
+void
+nsImageLoadingContent::DestroyImageLoadingContent()
+{
+ // Cancel our requests so they won't hold stale refs to us
+ // NB: Don't ask to discard the images here.
+ ClearCurrentRequest(NS_BINDING_ABORTED);
+ ClearPendingRequest(NS_BINDING_ABORTED);
+}
+
+nsImageLoadingContent::~nsImageLoadingContent()
+{
+ NS_ASSERTION(!mCurrentRequest && !mPendingRequest,
+ "DestroyImageLoadingContent not called");
+ NS_ASSERTION(!mObserverList.mObserver && !mObserverList.mNext,
+ "Observers still registered?");
+}
+
+/*
+ * imgINotificationObserver impl
+ */
+NS_IMETHODIMP
+nsImageLoadingContent::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::IS_ANIMATED) {
+ return OnImageIsAnimated(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
+ OnUnlockedDraw();
+ return NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ // We should definitely have a request here
+ MOZ_ASSERT(aRequest, "no request?");
+
+ NS_PRECONDITION(aRequest == mCurrentRequest || aRequest == mPendingRequest,
+ "Unknown request");
+ }
+
+ {
+ // Calling Notify on observers can modify the list of observers so make
+ // a local copy.
+ AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
+ for (ImageObserver* observer = &mObserverList, *next; observer;
+ observer = next) {
+ next = observer->mNext;
+ if (observer->mObserver) {
+ observers.AppendElement(observer->mObserver);
+ }
+ }
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ for (auto& observer : observers) {
+ observer->Notify(aRequest, aType, aData);
+ }
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Have to check for state changes here, since we might have been in
+ // the LOADING state before.
+ UpdateImageState(true);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ uint32_t reqStatus;
+ aRequest->GetImageStatus(&reqStatus);
+ /* triage STATUS_ERROR */
+ if (reqStatus & imgIRequest::STATUS_ERROR) {
+ nsresult errorCode = NS_OK;
+ aRequest->GetImageErrorCode(&errorCode);
+
+ /* Handle image not loading error because source was a tracking URL.
+ * We make a note of this image node by including it in a dedicated
+ * array of blocked tracking nodes under its parent document.
+ */
+ if (errorCode == NS_ERROR_TRACKING_URI) {
+ nsCOMPtr<nsIContent> thisNode
+ = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ nsIDocument *doc = GetOurOwnerDoc();
+ doc->AddBlockedTrackingNode(thisNode);
+ }
+ }
+ nsresult status =
+ reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ nsCOMPtr<imgIContainer> container;
+ aRequest->GetImage(getter_AddRefs(container));
+ if (container) {
+ container->PropagateUseCounters(GetOurOwnerDoc());
+ }
+
+ UpdateImageState(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
+{
+ uint32_t oldStatus;
+ aRequest->GetImageStatus(&oldStatus);
+
+ //XXXjdm This occurs when we have a pending request created, then another
+ // pending request replaces it before the first one is finished.
+ // This begs the question of what the correct behaviour is; we used
+ // to not have to care because we ran this code in OnStopDecode which
+ // wasn't called when the first request was cancelled. For now, I choose
+ // to punt when the given request doesn't appear to have terminated in
+ // an expected state.
+ if (!(oldStatus & (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE)))
+ return NS_OK;
+
+ // Our state may change. Watch it.
+ AutoStateChanger changer(this, true);
+
+ // If the pending request is loaded, switch to it.
+ if (aRequest == mPendingRequest) {
+ MakePendingRequestCurrent();
+ }
+ MOZ_ASSERT(aRequest == mCurrentRequest,
+ "One way or another, we should be current by now");
+
+ // Fire the appropriate DOM event.
+ if (NS_SUCCEEDED(aStatus)) {
+ FireEvent(NS_LITERAL_STRING("load"));
+
+ // Do not fire loadend event for multipart/x-mixed-replace image streams.
+ bool isMultipart;
+ if (NS_FAILED(aRequest->GetMultipart(&isMultipart)) || !isMultipart) {
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ }
+ } else {
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ }
+
+ nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
+
+ return NS_OK;
+}
+
+static bool
+ImageIsAnimated(imgIRequest* aRequest)
+{
+ if (!aRequest) {
+ return false;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ bool isAnimated = false;
+ nsresult rv = image->GetAnimated(&isAnimated);
+ if (NS_SUCCEEDED(rv) && isAnimated) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsImageLoadingContent::OnUnlockedDraw()
+{
+ // It's OK for non-animated images to wait until the next frame visibility
+ // update to become locked. (And that's preferable, since in the case of
+ // scrolling it keeps memory usage minimal.) For animated images, though, we
+ // want to mark them visible right away so we can call
+ // IncrementAnimationConsumers() on them and they'll start animating.
+ if (!ImageIsAnimated(mCurrentRequest) && !ImageIsAnimated(mPendingRequest)) {
+ return;
+ }
+
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+
+ if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) {
+ // This frame is already marked visible; there's nothing to do.
+ return;
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ if (!presContext) {
+ return;
+ }
+
+ nsIPresShell* presShell = presContext->PresShell();
+ if (!presShell) {
+ return;
+ }
+
+ presShell->EnsureFrameInApproximatelyVisibleList(frame);
+}
+
+nsresult
+nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
+{
+ bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
+ if (requestFlag) {
+ nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
+ aRequest, requestFlag);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * nsIImageLoadingContent impl
+ */
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetLoadingEnabled(bool *aLoadingEnabled)
+{
+ *aLoadingEnabled = mLoadingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled)
+{
+ if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
+ mLoadingEnabled = aLoadingEnabled;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
+{
+ NS_PRECONDITION(aStatus, "Null out param");
+ *aStatus = ImageBlockingStatus();
+ return NS_OK;
+}
+
+static void
+ReplayImageStatus(imgIRequest* aRequest, imgINotificationObserver* aObserver)
+{
+ if (!aRequest) {
+ return;
+ }
+
+ uint32_t status = 0;
+ nsresult rv = aRequest->GetImageStatus(&status);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE, nullptr);
+ }
+ if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE, nullptr);
+ }
+ if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
+ aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY, nullptr);
+ }
+ if (status & imgIRequest::STATUS_IS_ANIMATED) {
+ aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
+ }
+ if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE, nullptr);
+ }
+ if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
+ aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE, nullptr);
+ }
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ if (!mObserverList.mObserver) {
+ // Don't touch the linking of the list!
+ mObserverList.mObserver = aObserver;
+
+ ReplayImageStatus(mCurrentRequest, aObserver);
+ ReplayImageStatus(mPendingRequest, aObserver);
+
+ return NS_OK;
+ }
+
+ // otherwise we have to create a new entry
+
+ ImageObserver* observer = &mObserverList;
+ while (observer->mNext) {
+ observer = observer->mNext;
+ }
+
+ observer->mNext = new ImageObserver(aObserver);
+ ReplayImageStatus(mCurrentRequest, aObserver);
+ ReplayImageStatus(mPendingRequest, aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::RemoveObserver(imgINotificationObserver* aObserver)
+{
+ NS_ENSURE_ARG_POINTER(aObserver);
+
+ if (mObserverList.mObserver == aObserver) {
+ mObserverList.mObserver = nullptr;
+ // Don't touch the linking of the list!
+ return NS_OK;
+ }
+
+ // otherwise have to find it and splice it out
+ ImageObserver* observer = &mObserverList;
+ while (observer->mNext && observer->mNext->mObserver != aObserver) {
+ observer = observer->mNext;
+ }
+
+ // At this point, we are pointing to the list element whose mNext is
+ // the right observer (assuming of course that mNext is not null)
+ if (observer->mNext) {
+ // splice it out
+ ImageObserver* oldObserver = observer->mNext;
+ observer->mNext = oldObserver->mNext;
+ oldObserver->mNext = nullptr; // so we don't destroy them all
+ delete oldObserver;
+ }
+#ifdef DEBUG
+ else {
+ NS_WARNING("Asked to remove nonexistent observer");
+ }
+#endif
+ return NS_OK;
+}
+
+already_AddRefed<imgIRequest>
+nsImageLoadingContent::GetRequest(int32_t aRequestType,
+ ErrorResult& aError)
+{
+ nsCOMPtr<imgIRequest> request;
+ switch(aRequestType) {
+ case CURRENT_REQUEST:
+ request = mCurrentRequest;
+ break;
+ case PENDING_REQUEST:
+ request = mPendingRequest;
+ break;
+ default:
+ NS_ERROR("Unknown request type");
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ }
+
+ return request.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetRequest(int32_t aRequestType,
+ imgIRequest** aRequest)
+{
+ NS_ENSURE_ARG_POINTER(aRequest);
+
+ ErrorResult result;
+ *aRequest = GetRequest(aRequestType, result).take();
+
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP_(bool)
+nsImageLoadingContent::CurrentRequestHasSize()
+{
+ return HaveSize(mCurrentRequest);
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameCreated(nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "aFrame is null");
+
+ mFrameCreateCalled = true;
+
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+
+ // We need to make sure that our image request is registered, if it should
+ // be registered.
+ nsPresContext* presContext = aFrame->PresContext();
+ if (mCurrentRequest) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest,
+ &mCurrentRequestRegistered);
+ }
+
+ if (mPendingRequest) {
+ nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest,
+ &mPendingRequestRegistered);
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "aFrame is null");
+
+ mFrameCreateCalled = false;
+
+ // We need to make sure that our image request is deregistered.
+ nsPresContext* presContext = GetFramePresContext();
+ if (mCurrentRequest) {
+ nsLayoutUtils::DeregisterImageRequest(presContext,
+ mCurrentRequest,
+ &mCurrentRequestRegistered);
+ }
+
+ if (mPendingRequest) {
+ nsLayoutUtils::DeregisterImageRequest(presContext,
+ mPendingRequest,
+ &mPendingRequestRegistered);
+ }
+
+ UntrackImage(mCurrentRequest);
+ UntrackImage(mPendingRequest);
+
+ nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
+ if (presShell) {
+ presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
+ }
+}
+
+/* static */
+nsContentPolicyType
+nsImageLoadingContent::PolicyTypeForLoad(ImageLoadType aImageLoadType)
+{
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ return nsIContentPolicy::TYPE_IMAGESET;
+ }
+
+ MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
+ "Unknown ImageLoadType type in PolicyTypeForLoad");
+ return nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+}
+
+int32_t
+nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
+ ErrorResult& aError)
+{
+ if (aRequest == mCurrentRequest) {
+ return CURRENT_REQUEST;
+ }
+
+ if (aRequest == mPendingRequest) {
+ return PENDING_REQUEST;
+ }
+
+ NS_ERROR("Unknown request");
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return UNKNOWN_REQUEST;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
+ int32_t* aRequestType)
+{
+ NS_PRECONDITION(aRequestType, "Null out param");
+
+ ErrorResult result;
+ *aRequestType = GetRequestType(aRequest, result);
+ return result.StealNSResult();
+}
+
+already_AddRefed<nsIURI>
+nsImageLoadingContent::GetCurrentURI(ErrorResult& aError)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetURI(getter_AddRefs(uri));
+ } else if (mCurrentURI) {
+ nsresult rv = NS_EnsureSafeToReturn(mCurrentURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+ }
+
+ return uri.forget();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetCurrentURI(nsIURI** aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ ErrorResult result;
+ *aURI = GetCurrentURI(result).take();
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
+ nsIStreamListener** aListener)
+{
+ imgLoader* loader =
+ nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
+ if (!loader) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIDocument> doc = GetOurOwnerDoc();
+ if (!doc) {
+ // Don't bother
+ *aListener = nullptr;
+ return NS_OK;
+ }
+
+ // XXX what should we do with content policies here, if anything?
+ // Shouldn't that be done before the start of the load?
+ // XXX what about shouldProcess?
+
+ // Our state might change. Watch it.
+ AutoStateChanger changer(this, true);
+
+ // Do the load.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal);
+ nsresult rv = loader->
+ LoadImageWithChannel(aChannel, this, doc, aListener, getter_AddRefs(req));
+ if (NS_SUCCEEDED(rv)) {
+ TrackImage(req);
+ ResetAnimationIfNeeded();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ // If we don't have a current URI, we might as well store this URI so people
+ // know what we tried (and failed) to load.
+ if (!mCurrentRequest)
+ aChannel->GetURI(getter_AddRefs(mCurrentURI));
+
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ return rv;
+}
+
+void
+nsImageLoadingContent::ForceReload(const mozilla::dom::Optional<bool>& aNotify,
+ mozilla::ErrorResult& aError)
+{
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ if (!currentURI) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ // defaults to true
+ bool notify = !aNotify.WasPassed() || aNotify.Value();
+
+ // We keep this flag around along with the old URI even for failed requests
+ // without a live request object
+ ImageLoadType loadType = \
+ (mCurrentRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
+ : eImageLoadType_Normal;
+ nsresult rv = LoadImage(currentURI, true, notify, loadType, true, nullptr,
+ nsIRequest::VALIDATE_ALWAYS);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::ForceReload(bool aNotify /* = true */,
+ uint8_t aArgc)
+{
+ mozilla::dom::Optional<bool> notify;
+ if (aArgc >= 1) {
+ notify.Construct() = aNotify;
+ }
+
+ ErrorResult result;
+ ForceReload(notify, result);
+ return result.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::BlockOnload(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ NS_ASSERTION(!(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD),
+ "Double BlockOnload!?");
+ mCurrentRequestFlags |= REQUEST_BLOCKS_ONLOAD;
+ } else if (aRequest == mPendingRequest) {
+ NS_ASSERTION(!(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD),
+ "Double BlockOnload!?");
+ mPendingRequestFlags |= REQUEST_BLOCKS_ONLOAD;
+ } else {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (doc) {
+ doc->BlockOnload();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ NS_ASSERTION(mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD,
+ "Double UnblockOnload!?");
+ mCurrentRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
+ } else if (aRequest == mPendingRequest) {
+ NS_ASSERTION(mPendingRequestFlags & REQUEST_BLOCKS_ONLOAD,
+ "Double UnblockOnload!?");
+ mPendingRequestFlags &= ~REQUEST_BLOCKS_ONLOAD;
+ } else {
+ return NS_OK;
+ }
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (doc) {
+ doc->UnblockOnload(false);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Non-interface methods
+ */
+
+nsresult
+nsImageLoadingContent::LoadImage(const nsAString& aNewURI,
+ bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType)
+{
+ // First, get a document (needed for security checks and the like)
+ nsIDocument* doc = GetOurOwnerDoc();
+ if (!doc) {
+ // No reason to bother, I think...
+ return NS_OK;
+ }
+
+ // Pending load/error events need to be canceled in some situations. This
+ // is not documented in the spec, but can cause site compat problems if not
+ // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
+ CancelPendingEvent();
+
+ if (aNewURI.IsEmpty()) {
+ // Cancel image requests and then fire only error event per spec.
+ CancelImageRequests(aNotify);
+ // Mark error event as cancelable only for src="" case, since only this
+ // error causes site compat problem (bug 1308069) for now.
+ FireEvent(NS_LITERAL_STRING("error"), true);
+ return NS_OK;
+ }
+
+ // Fire loadstart event
+ FireEvent(NS_LITERAL_STRING("loadstart"));
+
+ // Parse the URI string to get image URI
+ nsCOMPtr<nsIURI> imageURI;
+ nsresult rv = StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // XXXbiesi fire onerror if that failed?
+
+ NS_TryToSetImmutable(imageURI);
+
+ return LoadImage(imageURI, aForce, aNotify, aImageLoadType, false, doc);
+}
+
+nsresult
+nsImageLoadingContent::LoadImage(nsIURI* aNewURI,
+ bool aForce,
+ bool aNotify,
+ ImageLoadType aImageLoadType,
+ bool aLoadStart,
+ nsIDocument* aDocument,
+ nsLoadFlags aLoadFlags)
+{
+ // Pending load/error events need to be canceled in some situations. This
+ // is not documented in the spec, but can cause site compat problems if not
+ // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
+ CancelPendingEvent();
+
+ // Fire loadstart event if required
+ if (aLoadStart) {
+ FireEvent(NS_LITERAL_STRING("loadstart"));
+ }
+
+ if (!mLoadingEnabled) {
+ // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
+ // don't want/need it.
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(),
+ "Bogus document passed in");
+ // First, get a document (needed for security checks and the like)
+ if (!aDocument) {
+ aDocument = GetOurOwnerDoc();
+ if (!aDocument) {
+ // No reason to bother, I think...
+ return NS_OK;
+ }
+ }
+
+ // URI equality check.
+ //
+ // We skip the equality check if our current image was blocked, since in that
+ // case we really do want to try loading again.
+ if (!aForce && NS_CP_ACCEPTED(mImageBlockingStatus)) {
+ nsCOMPtr<nsIURI> currentURI;
+ GetCurrentURI(getter_AddRefs(currentURI));
+ bool equal;
+ if (currentURI &&
+ NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) &&
+ equal) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+ }
+
+ // From this point on, our image state could change. Watch it.
+ AutoStateChanger changer(this, aNotify);
+
+ // Sanity check.
+ //
+ // We use the principal of aDocument to avoid having to QI |this| an extra
+ // time. It should always be the same as the principal of this node.
+#ifdef DEBUG
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ MOZ_ASSERT(thisContent &&
+ thisContent->NodePrincipal() == aDocument->NodePrincipal(),
+ "Principal mismatch?");
+#endif
+
+ // Are we blocked?
+ int16_t cpDecision = nsIContentPolicy::REJECT_REQUEST;
+ nsContentPolicyType policyType = PolicyTypeForLoad(aImageLoadType);
+
+ nsContentUtils::CanLoadImage(aNewURI,
+ static_cast<nsIImageLoadingContent*>(this),
+ aDocument,
+ aDocument->NodePrincipal(),
+ &cpDecision,
+ policyType);
+ if (!NS_CP_ACCEPTED(cpDecision)) {
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ SetBlockedRequest(aNewURI, cpDecision);
+ return NS_OK;
+ }
+
+ nsLoadFlags loadFlags = aLoadFlags;
+ int32_t corsmode = GetCORSMode();
+ if (corsmode == CORS_ANONYMOUS) {
+ loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
+ } else if (corsmode == CORS_USE_CREDENTIALS) {
+ loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
+ }
+
+ // get document wide referrer policy
+ // if referrer attributes are enabled in preferences, load img referrer attribute
+ // if the image does not provide a referrer attribute, ignore this
+ net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy();
+ net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy();
+ if (imgReferrerPolicy != net::RP_Unset) {
+ referrerPolicy = imgReferrerPolicy;
+ }
+
+ // Not blocked. Do the load.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsCOMPtr<nsINode> thisNode =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ nsresult rv = nsContentUtils::LoadImage(aNewURI,
+ thisNode,
+ aDocument,
+ aDocument->NodePrincipal(),
+ aDocument->GetDocumentURI(),
+ referrerPolicy,
+ this, loadFlags,
+ content->LocalName(),
+ getter_AddRefs(req),
+ policyType);
+
+ // Tell the document to forget about the image preload, if any, for
+ // this URI, now that we might have another imgRequestProxy for it.
+ // That way if we get canceled later the image load won't continue.
+ aDocument->ForgetImagePreload(aNewURI);
+
+ if (NS_SUCCEEDED(rv)) {
+ TrackImage(req);
+ ResetAnimationIfNeeded();
+
+ // Handle cases when we just ended up with a pending request but it's
+ // already done. In that situation we have to synchronously switch that
+ // request to being the current request, because websites depend on that
+ // behavior.
+ if (req == mPendingRequest) {
+ uint32_t pendingLoadStatus;
+ rv = req->GetImageStatus(&pendingLoadStatus);
+ if (NS_SUCCEEDED(rv) &&
+ (pendingLoadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ MakePendingRequestCurrent();
+ MOZ_ASSERT(mCurrentRequest,
+ "How could we not have a current request here?");
+
+ nsImageFrame *f = do_QueryFrame(GetOurPrimaryFrame());
+ if (f) {
+ f->NotifyNewCurrentRequest(mCurrentRequest, NS_OK);
+ }
+ }
+ }
+ } else {
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ // If we don't have a current URI, we might as well store this URI so people
+ // know what we tried (and failed) to load.
+ if (!mCurrentRequest)
+ mCurrentURI = aNewURI;
+
+ FireEvent(NS_LITERAL_STRING("error"));
+ FireEvent(NS_LITERAL_STRING("loadend"));
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsImageLoadingContent::ForceImageState(bool aForce,
+ EventStates::InternalType aState)
+{
+ mIsImageStateForced = aForce;
+ mForcedImageState = EventStates(aState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetNaturalWidth(uint32_t* aNaturalWidth)
+{
+ NS_ENSURE_ARG_POINTER(aNaturalWidth);
+
+ nsCOMPtr<imgIContainer> image;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ }
+
+ int32_t width;
+ if (image && NS_SUCCEEDED(image->GetWidth(&width))) {
+ *aNaturalWidth = width;
+ } else {
+ *aNaturalWidth = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImageLoadingContent::GetNaturalHeight(uint32_t* aNaturalHeight)
+{
+ NS_ENSURE_ARG_POINTER(aNaturalHeight);
+
+ nsCOMPtr<imgIContainer> image;
+ if (mCurrentRequest) {
+ mCurrentRequest->GetImage(getter_AddRefs(image));
+ }
+
+ int32_t height;
+ if (image && NS_SUCCEEDED(image->GetHeight(&height))) {
+ *aNaturalHeight = height;
+ } else {
+ *aNaturalHeight = 0;
+ }
+
+ return NS_OK;
+}
+
+EventStates
+nsImageLoadingContent::ImageState() const
+{
+ if (mIsImageStateForced) {
+ return mForcedImageState;
+ }
+
+ EventStates states;
+
+ if (mBroken) {
+ states |= NS_EVENT_STATE_BROKEN;
+ }
+ if (mUserDisabled) {
+ states |= NS_EVENT_STATE_USERDISABLED;
+ }
+ if (mSuppressed) {
+ states |= NS_EVENT_STATE_SUPPRESSED;
+ }
+ if (mLoading) {
+ states |= NS_EVENT_STATE_LOADING;
+ }
+
+ return states;
+}
+
+void
+nsImageLoadingContent::UpdateImageState(bool aNotify)
+{
+ if (mStateChangerDepth > 0) {
+ // Ignore this call; we'll update our state when the outermost state changer
+ // is destroyed. Need this to work around the fact that some ImageLib
+ // stuff is actually sync and hence we can get OnStopDecode called while
+ // we're still under LoadImage, and OnStopDecode doesn't know anything about
+ // aNotify.
+ // XXX - This machinery should be removed after bug 521604.
+ return;
+ }
+
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ if (!thisContent) {
+ return;
+ }
+
+ mLoading = mBroken = mUserDisabled = mSuppressed = false;
+
+ // If we were blocked by server-based content policy, we claim to be
+ // suppressed. If we were blocked by type-based content policy, we claim to
+ // be user-disabled. Otherwise, claim to be broken.
+ if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
+ mSuppressed = true;
+ } else if (mImageBlockingStatus == nsIContentPolicy::REJECT_TYPE) {
+ mUserDisabled = true;
+ } else if (!mCurrentRequest) {
+ // No current request means error, since we weren't disabled or suppressed
+ mBroken = true;
+ } else {
+ uint32_t currentLoadStatus;
+ nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
+ if (NS_FAILED(rv) || (currentLoadStatus & imgIRequest::STATUS_ERROR)) {
+ mBroken = true;
+ } else if (!(currentLoadStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) {
+ mLoading = true;
+ }
+ }
+
+ NS_ASSERTION(thisContent->IsElement(), "Not an element?");
+ thisContent->AsElement()->UpdateState(aNotify);
+}
+
+void
+nsImageLoadingContent::CancelImageRequests(bool aNotify)
+{
+ AutoStateChanger changer(this, aNotify);
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+}
+
+nsresult
+nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest,
+ bool aNotify,
+ ImageLoadType aImageLoadType)
+{
+ // Our state will change. Watch it.
+ AutoStateChanger changer(this, aNotify);
+
+ // Get rid if our existing images
+ ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+ ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES));
+
+ // Clone the request we were given.
+ RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
+ nsresult rv = aRequest->Clone(this, getter_AddRefs(req));
+ if (NS_SUCCEEDED(rv)) {
+ TrackImage(req);
+ } else {
+ MOZ_ASSERT(!req, "Shouldn't have non-null request here");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsIDocument*
+nsImageLoadingContent::GetOurOwnerDoc()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ENSURE_TRUE(thisContent, nullptr);
+
+ return thisContent->OwnerDoc();
+}
+
+nsIDocument*
+nsImageLoadingContent::GetOurCurrentDoc()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ENSURE_TRUE(thisContent, nullptr);
+
+ return thisContent->GetComposedDoc();
+}
+
+nsIFrame*
+nsImageLoadingContent::GetOurPrimaryFrame()
+{
+ nsCOMPtr<nsIContent> thisContent =
+ do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ return thisContent->GetPrimaryFrame();
+}
+
+nsPresContext* nsImageLoadingContent::GetFramePresContext()
+{
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ return frame->PresContext();
+}
+
+nsresult
+nsImageLoadingContent::StringToURI(const nsAString& aSpec,
+ nsIDocument* aDocument,
+ nsIURI** aURI)
+{
+ NS_PRECONDITION(aDocument, "Must have a document");
+ NS_PRECONDITION(aURI, "Null out param");
+
+ // (1) Get the base URI
+ nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+ NS_ASSERTION(thisContent, "An image loading content must be an nsIContent");
+ nsCOMPtr<nsIURI> baseURL = thisContent->GetBaseURI();
+
+ // (2) Get the charset
+ const nsAFlatCString &charset = aDocument->GetDocumentCharacterSet();
+
+ // (3) Construct the silly thing
+ return NS_NewURI(aURI,
+ aSpec,
+ charset.IsEmpty() ? nullptr : charset.get(),
+ baseURL,
+ nsContentUtils::GetIOService());
+}
+
+nsresult
+nsImageLoadingContent::FireEvent(const nsAString& aEventType, bool aIsCancelable)
+{
+ if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
+ // Don't bother to fire any events, especially error events.
+ return NS_OK;
+ }
+
+ // We have to fire the event asynchronously so that we won't go into infinite
+ // loops in cases when onLoad handlers reset the src and the new src is in
+ // cache.
+
+ nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+
+ RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
+ new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, false, false);
+ loadBlockingAsyncDispatcher->PostDOMEvent();
+
+ if (aIsCancelable) {
+ mPendingEvent = loadBlockingAsyncDispatcher;
+ }
+
+ return NS_OK;
+}
+
+void
+nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ if (mPendingEvent == aEvent) {
+ mPendingEvent = nullptr;
+ }
+}
+
+void
+nsImageLoadingContent::CancelPendingEvent()
+{
+ if (mPendingEvent) {
+ mPendingEvent->Cancel();
+ mPendingEvent = nullptr;
+ }
+}
+
+RefPtr<imgRequestProxy>&
+nsImageLoadingContent::PrepareNextRequest(ImageLoadType aImageLoadType)
+{
+ nsImageFrame* frame = do_QueryFrame(GetOurPrimaryFrame());
+ if (frame) {
+ // Detect JavaScript-based animations created by changing the |src|
+ // attribute on a timer.
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration threshold =
+ TimeDuration::FromMilliseconds(
+ gfxPrefs::ImageInferSrcAnimationThresholdMS());
+
+ // If the length of time between request changes is less than the threshold,
+ // then force sync decoding to eliminate flicker from the animation.
+ frame->SetForceSyncDecoding(now - mMostRecentRequestChange < threshold);
+
+ mMostRecentRequestChange = now;
+ }
+
+ // If we don't have a usable current request, get rid of any half-baked
+ // request that might be sitting there and make this one current.
+ if (!HaveSize(mCurrentRequest))
+ return PrepareCurrentRequest(aImageLoadType);
+
+ // Otherwise, make it pending.
+ return PreparePendingRequest(aImageLoadType);
+}
+
+void
+nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision)
+{
+ // Sanity
+ MOZ_ASSERT(!NS_CP_ACCEPTED(aContentDecision), "Blocked but not?");
+
+ // We do some slightly illogical stuff here to maintain consistency with
+ // old behavior that people probably depend on. Even in the case where the
+ // new image is blocked, the old one should really be canceled with the
+ // reason "image source changed". However, apparently there's some abuse
+ // over in nsImageFrame where the displaying of the "broken" icon for the
+ // next image depends on the cancel reason of the previous image. ugh.
+ // XXX(seth): So shouldn't we fix nsImageFrame?!
+ ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ // For the blocked case, we only want to cancel the existing current request
+ // if size is not available. bz says the web depends on this behavior.
+ if (!HaveSize(mCurrentRequest)) {
+
+ mImageBlockingStatus = aContentDecision;
+ uint32_t keepFlags = mCurrentRequestFlags & REQUEST_IS_IMAGESET;
+ ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ // We still want to remember what URI we were and if it was an imageset,
+ // despite not having an actual request. These are both cleared as part of
+ // ClearCurrentRequest() before a new request is started.
+ mCurrentURI = aURI;
+ mCurrentRequestFlags = keepFlags;
+ }
+}
+
+RefPtr<imgRequestProxy>&
+nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType)
+{
+ // Blocked images go through SetBlockedRequest, which is a separate path. For
+ // everything else, we're unblocked.
+ mImageBlockingStatus = nsIContentPolicy::ACCEPT;
+
+ // Get rid of anything that was there previously.
+ ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ if (mNewRequestsWillNeedAnimationReset) {
+ mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
+ }
+
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
+ }
+
+ // Return a reference.
+ return mCurrentRequest;
+}
+
+RefPtr<imgRequestProxy>&
+nsImageLoadingContent::PreparePendingRequest(ImageLoadType aImageLoadType)
+{
+ // Get rid of anything that was there previously.
+ ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED,
+ Some(OnNonvisible::DISCARD_IMAGES));
+
+ if (mNewRequestsWillNeedAnimationReset) {
+ mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET;
+ }
+
+ if (aImageLoadType == eImageLoadType_Imageset) {
+ mPendingRequestFlags |= REQUEST_IS_IMAGESET;
+ }
+
+ // Return a reference.
+ return mPendingRequest;
+}
+
+namespace {
+
+class ImageRequestAutoLock
+{
+public:
+ explicit ImageRequestAutoLock(imgIRequest* aRequest)
+ : mRequest(aRequest)
+ {
+ if (mRequest) {
+ mRequest->LockImage();
+ }
+ }
+
+ ~ImageRequestAutoLock()
+ {
+ if (mRequest) {
+ mRequest->UnlockImage();
+ }
+ }
+
+private:
+ nsCOMPtr<imgIRequest> mRequest;
+};
+
+} // namespace
+
+void
+nsImageLoadingContent::MakePendingRequestCurrent()
+{
+ MOZ_ASSERT(mPendingRequest);
+
+ // Lock mCurrentRequest for the duration of this method. We do this because
+ // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest
+ // and mPendingRequest are both requests for the same image, unlocking
+ // mCurrentRequest before we lock mPendingRequest can cause the lock count
+ // to go to 0 and the image to be discarded!
+ ImageRequestAutoLock autoLock(mCurrentRequest);
+
+ ImageLoadType loadType = \
+ (mPendingRequestFlags & REQUEST_IS_IMAGESET) ? eImageLoadType_Imageset
+ : eImageLoadType_Normal;
+
+ PrepareCurrentRequest(loadType) = mPendingRequest;
+ mPendingRequest = nullptr;
+ mCurrentRequestFlags = mPendingRequestFlags;
+ mPendingRequestFlags = 0;
+ ResetAnimationIfNeeded();
+}
+
+void
+nsImageLoadingContent::ClearCurrentRequest(nsresult aReason,
+ const Maybe<OnNonvisible>& aNonvisibleAction)
+{
+ if (!mCurrentRequest) {
+ // Even if we didn't have a current request, we might have been keeping
+ // a URI and flags as a placeholder for a failed load. Clear that now.
+ mCurrentURI = nullptr;
+ mCurrentRequestFlags = 0;
+ return;
+ }
+ MOZ_ASSERT(!mCurrentURI,
+ "Shouldn't have both mCurrentRequest and mCurrentURI!");
+
+ // Deregister this image from the refresh driver so it no longer receives
+ // notifications.
+ nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
+ &mCurrentRequestRegistered);
+
+ // Clean up the request.
+ UntrackImage(mCurrentRequest, aNonvisibleAction);
+ mCurrentRequest->CancelAndForgetObserver(aReason);
+ mCurrentRequest = nullptr;
+ mCurrentRequestFlags = 0;
+}
+
+void
+nsImageLoadingContent::ClearPendingRequest(nsresult aReason,
+ const Maybe<OnNonvisible>& aNonvisibleAction)
+{
+ if (!mPendingRequest)
+ return;
+
+ // Deregister this image from the refresh driver so it no longer receives
+ // notifications.
+ nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
+ &mPendingRequestRegistered);
+
+ UntrackImage(mPendingRequest, aNonvisibleAction);
+ mPendingRequest->CancelAndForgetObserver(aReason);
+ mPendingRequest = nullptr;
+ mPendingRequestFlags = 0;
+}
+
+bool*
+nsImageLoadingContent::GetRegisteredFlagForRequest(imgIRequest* aRequest)
+{
+ if (aRequest == mCurrentRequest) {
+ return &mCurrentRequestRegistered;
+ } else if (aRequest == mPendingRequest) {
+ return &mPendingRequestRegistered;
+ } else {
+ return nullptr;
+ }
+}
+
+void
+nsImageLoadingContent::ResetAnimationIfNeeded()
+{
+ if (mCurrentRequest &&
+ (mCurrentRequestFlags & REQUEST_NEEDS_ANIMATION_RESET)) {
+ nsCOMPtr<imgIContainer> container;
+ mCurrentRequest->GetImage(getter_AddRefs(container));
+ if (container)
+ container->ResetAnimation();
+ mCurrentRequestFlags &= ~REQUEST_NEEDS_ANIMATION_RESET;
+ }
+}
+
+bool
+nsImageLoadingContent::HaveSize(imgIRequest *aImage)
+{
+ // Handle the null case
+ if (!aImage)
+ return false;
+
+ // Query the image
+ uint32_t status;
+ nsresult rv = aImage->GetImageStatus(&status);
+ return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE));
+}
+
+void
+nsImageLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ // We may be entering the document, so if our image should be tracked,
+ // track it.
+ if (!aDocument)
+ return;
+
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+
+ if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
+ aDocument->BlockOnload();
+}
+
+void
+nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // We may be leaving the document, so if our image is tracked, untrack it.
+ nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc();
+ if (!doc)
+ return;
+
+ UntrackImage(mCurrentRequest);
+ UntrackImage(mPendingRequest);
+
+ if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD)
+ doc->UnblockOnload(false);
+}
+
+void
+nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
+ const Maybe<OnNonvisible>& aNonvisibleAction)
+{
+ switch (aNewVisibility) {
+ case Visibility::APPROXIMATELY_VISIBLE:
+ TrackImage(mCurrentRequest);
+ TrackImage(mPendingRequest);
+ break;
+
+ case Visibility::APPROXIMATELY_NONVISIBLE:
+ UntrackImage(mCurrentRequest, aNonvisibleAction);
+ UntrackImage(mPendingRequest, aNonvisibleAction);
+ break;
+
+ case Visibility::UNTRACKED:
+ MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
+ break;
+ }
+}
+
+void
+nsImageLoadingContent::TrackImage(imgIRequest* aImage)
+{
+ if (!aImage)
+ return;
+
+ MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
+ "Why haven't we heard of this request?");
+
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (!doc) {
+ return;
+ }
+
+ // We only want to track this request if we're visible. Ordinarily we check
+ // the visible count, but that requires a frame; in cases where
+ // GetOurPrimaryFrame() cannot obtain a frame (e.g. <feImage>), we assume
+ // we're visible if FrameCreated() was called.
+ nsIFrame* frame = GetOurPrimaryFrame();
+ if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) ||
+ (!frame && !mFrameCreateCalled)) {
+ return;
+ }
+
+ if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
+ mCurrentRequestFlags |= REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Add(mCurrentRequest);
+ }
+ if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
+ mPendingRequestFlags |= REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Add(mPendingRequest);
+ }
+}
+
+void
+nsImageLoadingContent::UntrackImage(imgIRequest* aImage,
+ const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */)
+{
+ if (!aImage)
+ return;
+
+ MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
+ "Why haven't we heard of this request?");
+
+ // We may not be in the document. If we outlived our document that's fine,
+ // because the document empties out the tracker and unlocks all locked images
+ // on destruction. But if we were never in the document we may need to force
+ // discarding the image here, since this is the only chance we have.
+ nsIDocument* doc = GetOurCurrentDoc();
+ if (aImage == mCurrentRequest) {
+ if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
+ mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Remove(
+ mCurrentRequest,
+ aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
+ // If we're not in the document we may still need to be discarded.
+ aImage->RequestDiscard();
+ }
+ }
+ if (aImage == mPendingRequest) {
+ if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
+ mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
+ doc->ImageTracker()->Remove(
+ mPendingRequest,
+ aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)
+ ? ImageTracker::REQUEST_DISCARD
+ : 0);
+ } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) {
+ // If we're not in the document we may still need to be discarded.
+ aImage->RequestDiscard();
+ }
+ }
+}
+
+
+void
+nsImageLoadingContent::CreateStaticImageClone(nsImageLoadingContent* aDest) const
+{
+ aDest->mCurrentRequest = nsContentUtils::GetStaticRequest(mCurrentRequest);
+ aDest->TrackImage(aDest->mCurrentRequest);
+ aDest->mForcedImageState = mForcedImageState;
+ aDest->mImageBlockingStatus = mImageBlockingStatus;
+ aDest->mLoadingEnabled = mLoadingEnabled;
+ aDest->mStateChangerDepth = mStateChangerDepth;
+ aDest->mIsImageStateForced = mIsImageStateForced;
+ aDest->mLoading = mLoading;
+ aDest->mBroken = mBroken;
+ aDest->mUserDisabled = mUserDisabled;
+ aDest->mSuppressed = mSuppressed;
+}
+
+CORSMode
+nsImageLoadingContent::GetCORSMode()
+{
+ return CORS_NONE;
+}
+
+nsImageLoadingContent::ImageObserver::ImageObserver(imgINotificationObserver* aObserver)
+ : mObserver(aObserver)
+ , mNext(nullptr)
+{
+ MOZ_COUNT_CTOR(ImageObserver);
+}
+
+nsImageLoadingContent::ImageObserver::~ImageObserver()
+{
+ MOZ_COUNT_DTOR(ImageObserver);
+ NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
+}
+
+// Only HTMLInputElement.h overrides this for <img> tags
+// all other subclasses use this one, i.e. ignore referrer attributes
+mozilla::net::ReferrerPolicy
+nsImageLoadingContent::GetImageReferrerPolicy()
+{
+ return mozilla::net::RP_Unset;
+};