/* -*- 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).
*/
void nsImageLoadingContent::Destroy() { // Cancel our requests so they won't hold stale refs to us // NB: Don't ask to discard the images here.
RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
ClearCurrentRequest(NS_BINDING_ABORTED);
ClearPendingRequest(NS_BINDING_ABORTED);
}
nsImageLoadingContent::~nsImageLoadingContent() {
MOZ_ASSERT(!mCurrentRequest && !mPendingRequest, "Destroy not called");
MOZ_ASSERT(!mObserverList.mObserver && !mObserverList.mNext, "Observers still registered?");
MOZ_ASSERT(mScriptedObservers.IsEmpty(), "Scripted observers still registered?");
MOZ_ASSERT(mOutstandingDecodePromises == 0, "Decode promises still unfulfilled?");
MOZ_ASSERT(mDecodePromises.IsEmpty(), "Decode promises still unfulfilled?");
}
/* * imgINotificationObserver impl
*/ void nsImageLoadingContent::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData) {
MOZ_ASSERT(aRequest, "no request?");
MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest, "Forgot to cancel a previous request?");
if (aType == imgINotificationObserver::IS_ANIMATED) { return OnImageIsAnimated(aRequest);
}
if (aType == imgINotificationObserver::UNLOCKED_DRAW) { return OnUnlockedDraw();
}
{ // 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);
}
}
/* Handle image not loading error because source was a tracking URL (or * fingerprinting, cryptomining, etc). * We make a note of this image node by including it in a dedicated * array of blocked tracking nodes under its parent document.
*/ if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
errorCode)) {
Document* doc = GetOurOwnerDoc();
doc->AddBlockedNodeByClassifier(AsContent());
}
} return OnLoadComplete(aRequest, reqStatus);
}
if (aType == imgINotificationObserver::DECODE_COMPLETE) {
nsCOMPtr<imgIContainer> container;
aRequest->GetImage(getter_AddRefs(container)); if (container) {
container->PropagateUseCounters(GetOurOwnerDoc());
}
UpdateImageState(true);
}
}
void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest,
uint32_t aImageStatus) { // 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 (!(aImageStatus &
(imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) { return;
}
// 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 (!(aImageStatus & imgIRequest::STATUS_ERROR)) {
FireEvent(u"load"_ns);
} else {
FireEvent(u"error"_ns);
}
Element* element = AsContent()->AsElement();
SVGObserverUtils::InvalidateDirectRenderingObservers(element);
MaybeResolveDecodePromises();
LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest,
element);
UpdateImageState(true);
}
void nsImageLoadingContent::OnUnlockedDraw() { // This notification is only sent for animated images. 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.
nsIFrame* frame = GetOurPrimaryImageFrame(); if (!frame) { return;
}
if (frame->GetVisibility() == Visibility::ApproximatelyVisible) { // This frame is already marked visible; there's nothing to do. return;
}
nsPresContext* presContext = frame->PresContext(); if (!presContext) { return;
}
PresShell* presShell = presContext->GetPresShell(); if (!presShell) { return;
}
// The request may have gotten updated since the decode call was issued. if (aRequestGeneration != mRequestGeneration) {
aPromise->MaybeReject(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); // We never got placed in mDecodePromises, so we must ensure we decrement // the counter explicitly.
--mOutstandingDecodePromises;
MaybeDeregisterActivityObserver(); return;
}
void nsImageLoadingContent::MaybeResolveDecodePromises() { if (mDecodePromises.IsEmpty()) { return;
}
if (!mCurrentRequest) {
RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); return;
}
// Only can resolve if our document is the active document. If not we are // supposed to reject the promise, even if it was fulfilled successfully. if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT); return;
}
// If any error occurred while decoding, we need to reject first.
uint32_t status = imgIRequest::STATUS_NONE;
mCurrentRequest->GetImageStatus(&status); if (status & imgIRequest::STATUS_ERROR) {
RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN); return;
}
// We need the size to bother with requesting a decode, as we are either // blocked on validation or metadata decoding. if (!(status & imgIRequest::STATUS_SIZE_AVAILABLE)) { return;
}
// Check the surface cache status and/or request decoding begin. We do this // before LOAD_COMPLETE because we want to start as soon as possible.
uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE;
imgIContainer::DecodeResult decodeResult =
mCurrentRequest->RequestDecodeWithResult(flags); if (decodeResult == imgIContainer::DECODE_REQUESTED) { return;
} if (decodeResult == imgIContainer::DECODE_REQUEST_FAILED) {
RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN); return;
}
MOZ_ASSERT(decodeResult == imgIContainer::DECODE_SURFACE_AVAILABLE);
// We can only fulfill the promises once we have all the data. if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) { return;
}
for (auto& promise : mDecodePromises) {
promise->MaybeResolveWithUndefined();
}
// If the current request is about to change, we need to verify if the new // URI matches the existing current request's URI. If it doesn't, we need to // reject any outstanding promises due to the current request mutating as per // step 2.2 of the decode API requirements. // // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode if (aNewURI) {
nsCOMPtr<nsIURI> currentURI;
mCurrentRequest->GetURI(getter_AddRefs(currentURI));
void nsImageLoadingContent::MaybeForceSyncDecoding( bool aPrepareNextRequest, nsIFrame* aFrame /* = nullptr */) { // GetOurPrimaryImageFrame() might not return the frame during frame init.
nsIFrame* frame = aFrame ? aFrame : GetOurPrimaryImageFrame(); if (!frame) { return;
}
bool forceSync = mSyncDecodingHint; if (!forceSync && aPrepareNextRequest) { // Detect JavaScript-based animations created by changing the |src| // attribute on a timer.
TimeStamp now = TimeStamp::Now();
TimeDuration threshold = TimeDuration::FromMilliseconds(
StaticPrefs::image_infer_src_animation_threshold_ms());
// If the length of time between request changes is less than the threshold, // then force sync decoding to eliminate flicker from the animation.
forceSync = (now - mMostRecentRequestChange < threshold);
mMostRecentRequestChange = now;
}
observer->mNext = new ImageObserver(aObserver);
ReplayImageStatus(mCurrentRequest, aObserver);
ReplayImageStatus(mPendingRequest, aObserver);
}
void nsImageLoadingContent::RemoveNativeObserver(
imgINotificationObserver* aObserver) { if (NS_WARN_IF(!aObserver)) { return;
}
if (mObserverList.mObserver == aObserver) {
mObserverList.mObserver = nullptr; // Don't touch the linking of the list! return;
}
// 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
}
void nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) { if (NS_WARN_IF(!aObserver)) { return;
}
RefPtr<imgRequestProxy> currentReq; if (mCurrentRequest) { // Scripted observers may not belong to the same document as us, so when we // create the imgRequestProxy, we shouldn't use any. This allows the request // to dispatch notifications from the correct scheduler group.
nsresult rv =
mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq)); if (NS_FAILED(rv)) { return;
}
}
RefPtr<imgRequestProxy> pendingReq; if (mPendingRequest) { // See above for why we don't use the loading document.
nsresult rv =
mPendingRequest->Clone(aObserver, nullptr, getter_AddRefs(pendingReq)); if (NS_FAILED(rv)) {
mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); return;
}
}
void nsImageLoadingContent::RemoveObserver(
imgINotificationObserver* aObserver) { if (NS_WARN_IF(!aObserver)) { return;
}
if (NS_WARN_IF(mScriptedObservers.IsEmpty())) { return;
}
RefPtr<ScriptedImageObserver> observer; auto i = mScriptedObservers.Length(); do {
--i; if (mScriptedObservers[i]->mObserver == aObserver) {
observer = std::move(mScriptedObservers[i]);
mScriptedObservers.RemoveElementAt(i); break;
}
} while (i > 0);
if (NS_WARN_IF(!observer)) { return;
}
// If the cancel causes a mutation, it will be harmless, because we have // already removed the observer from the list.
observer->CancelRequests();
}
// 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");
// 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);
}
// 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. auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(true); }); // Do the load.
nsCOMPtr<nsIURI> uri;
aChannel->GetOriginalURI(getter_AddRefs(uri));
RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal, uri);
nsresult rv = loader->LoadImageWithChannel(aChannel, this, doc, aListener,
getter_AddRefs(req)); if (NS_SUCCEEDED(rv)) {
CloneScriptedRequests(req);
TrackImage(req); 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));
// 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, aNotify, loadType,
nsIRequest::VALIDATE_ALWAYS | LoadFlags()); if (NS_FAILED(rv)) {
aError.Throw(rv);
}
}
/* * Non-interface methods
*/
nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce, bool aNotify,
ImageLoadType aImageLoadType,
nsIPrincipal* aTriggeringPrincipal) { // First, get a document (needed for security checks and the like)
Document* doc = GetOurOwnerDoc(); if (!doc) { // No reason to bother, I think... return NS_OK;
}
// Parse the URI string to get image URI
nsCOMPtr<nsIURI> imageURI; if (!aNewURI.IsEmpty()) {
Unused << StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
}
nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify,
ImageLoadType aImageLoadType,
nsLoadFlags aLoadFlags,
Document* aDocument,
nsIPrincipal* aTriggeringPrincipal) { // 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) { // Cancel image requests and then fire only error event per spec.
CancelImageRequests(aNotify); if (aImageLoadType == eImageLoadType_Normal) { // Mark error event as cancelable only for src="" case, since only this // error causes site compat problem (bug 1308069) for now.
FireEvent(u"error"_ns, true);
} return NS_OK;
}
if (!mLoadingEnabled) { // XXX Why fire an error here? seems like the callers to SetLoadingEnabled // don't want/need it.
FireEvent(u"error"_ns); 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;
}
}
// Data documents, or documents from DOMParser shouldn't perform image // loading. // // FIXME(emilio): Shouldn't this check be part of // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here // instead? (It seems we only check ShouldLoadImages in HTMLImageElement, // which seems wrong...) if (aDocument->IsLoadedAsData() && !aDocument->IsStaticDocument()) { // Clear our pending request if we do have one.
ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
FireEvent(u"error"_ns); return NS_OK;
}
// URI equality check. // // We skip the equality check if we don't have a current image, since in that // case we really do want to try loading again. if (!aForce && mCurrentRequest) {
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. auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(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.
Element* element = AsContent()->AsElement();
MOZ_ASSERT(element->NodePrincipal() == aDocument->NodePrincipal(), "Principal mismatch?");
nsCOMPtr<nsIPrincipal> triggeringPrincipal; bool result = nsContentUtils::QueryTriggeringPrincipal(
element, aTriggeringPrincipal, getter_AddRefs(triggeringPrincipal));
// If result is true, which means this node has specified // 'triggeringprincipal' attribute on it, so we use favicon as the policy // type.
nsContentPolicyType policyType =
result ? nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
: PolicyTypeForLoad(aImageLoadType);
if (fetchPriority != FetchPriority::Auto) {
aDocument->SetPageloadEventFeature(
pageload_event::FeatureBits::FETCH_PRIORITY_IMAGES);
}
// Reset the flag to avoid loading from XPCOM or somewhere again else without // initiated by user interaction.
mUseUrgentStartForChannel = false;
// 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)) { // Based on performance testing unsuppressing painting soon after the page // has gotten an image may improve visual metrics. if (Document* doc = element->GetComposedDoc()) { if (PresShell* shell = doc->GetPresShell()) {
shell->TryUnsuppressPaintingSoon();
}
}
CloneScriptedRequests(req);
TrackImage(req);
// Handle cases when we just ended up with a 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.
{
uint32_t loadStatus; if (NS_SUCCEEDED(req->GetImageStatus(&loadStatus)) &&
(loadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) { if (req == mPendingRequest) {
MakePendingRequestCurrent();
}
MOZ_ASSERT(mCurrentRequest, "How could we not have a current request here?");
if (nsImageFrame* f = do_QueryFrame(GetOurPrimaryImageFrame())) {
f->NotifyNewCurrentRequest(mCurrentRequest);
}
}
}
} 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(u"error"_ns);
}
return NS_OK;
}
already_AddRefed<Promise> nsImageLoadingContent::RecognizeCurrentImageText(
ErrorResult& aRv) { using widget::TextRecognition;
if (!mCurrentRequest) {
aRv.ThrowInvalidStateError("No current request"); return nullptr;
}
nsCOMPtr<imgIContainer> image;
mCurrentRequest->GetImage(getter_AddRefs(image)); if (!image) {
aRv.ThrowInvalidStateError("No image"); return nullptr;
}
// The list of ISO 639-1 language tags to pass to the text recognition API.
AutoTArray<nsCString, 4> languages;
{ // The document's locale should be the top language to use. Parse the BCP 47 // locale and extract the ISO 639-1 language tag. e.g. "en-US" -> "en".
nsAutoCString elementLanguage;
nsAtom* imgLanguage = AsContent()->GetLang();
intl::Locale locale; if (imgLanguage) {
imgLanguage->ToUTF8String(elementLanguage); auto result = intl::LocaleParser::TryParse(elementLanguage, locale); if (result.isOk()) {
languages.AppendElement(locale.Language().Span());
}
}
}
{ // The app locales should also be included after the document's locales. // Extract the language tag like above.
nsTArray<nsCString> appLocales;
intl::LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
for (constauto& localeString : appLocales) {
intl::Locale locale; auto result = intl::LocaleParser::TryParse(localeString, locale); if (result.isErr()) {
NS_WARNING("Could not parse an app locale string, ignoring it."); continue;
}
languages.AppendElement(locale.Language().Span());
}
}
TextRecognition::FindText(*image, languages)
->Then(
GetCurrentSerialEventTarget(), __func__,
[weak = RefPtr{do_GetWeakReference(this)},
request = RefPtr{mCurrentRequest}, domPromise](
TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) { if (aValue.IsReject()) {
domPromise->MaybeRejectWithNotSupportedError(
aValue.RejectValue()); return;
}
RefPtr<nsIImageLoadingContent> iilc = do_QueryReferent(weak.get()); if (!iilc) {
domPromise->MaybeRejectWithInvalidStateError( "Element was dead when we got the results"); return;
} auto* ilc = static_cast<nsImageLoadingContent*>(iilc.get()); if (ilc->mCurrentRequest != request) {
domPromise->MaybeRejectWithInvalidStateError( "Request not current"); return;
} auto& textRecognitionResult = aValue.ResolveValue();
Element* el = ilc->AsContent()->AsElement();
// When enabled, this feature will place the recognized text as // spans inside of the shadow dom of the img element. These are then // positioned so that the user can select the text. if (Preferences::GetBool("dom.text-recognition.shadow-dom-enabled", false)) {
el->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::Yes);
TextRecognition::FillShadow(*el->GetShadowRoot(),
textRecognitionResult);
el->NotifyUAWidgetSetupOrChange();
}
nsTArray<ImageText> imageTexts(
textRecognitionResult.quads().Length());
nsIGlobalObject* global = el->OwnerDoc()->GetOwnerGlobal();
for (constauto& quad : textRecognitionResult.quads()) {
NotNull<ImageText*> imageText = imageTexts.AppendElement();
// Note: These points are not actually CSSPixels, but a DOMQuad is // a conveniently similar structure that can store these values.
CSSPoint points[4];
points[0] = CSSPoint(quad.points()[0].x, quad.points()[0].y);
points[1] = CSSPoint(quad.points()[1].x, quad.points()[1].y);
points[2] = CSSPoint(quad.points()[2].x, quad.points()[2].y);
points[3] = CSSPoint(quad.points()[3].x, quad.points()[3].y);
nsresult nsImageLoadingContent::FireEvent(const nsAString& aEventType, bool aIsCancelable) { if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) { // Don't bother to fire any events, especially error events.
RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT); 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 = AsContent();
RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher = new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, CanBubble::eNo,
ChromeOnlyDispatch::eNo);
loadBlockingAsyncDispatcher->PostDOMEvent();
if (aIsCancelable) {
mPendingEvent = loadBlockingAsyncDispatcher;
}
// We only want to cancel the existing current request if size is not // available. bz says the web depends on this behavior. // Otherwise, we get rid of any half-baked request that might be sitting there // and make this one current. return HaveSize(mCurrentRequest)
? PreparePendingRequest(aImageLoadType)
: PrepareCurrentRequest(aImageLoadType, aNewURI);
}
RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest(
ImageLoadType aImageLoadType, nsIURI* aNewURI) { if (mCurrentRequest) {
MaybeAgeRequestGeneration(aNewURI);
} // Get rid of anything that was there previously.
ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
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_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
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();
}
}
// If we have a pending request, we know that there is an existing current // request with size information. If the pending request is for a different // URI, then we need to reject any outstanding promises.
nsCOMPtr<nsIURI> uri;
mPendingRequest->GetURI(getter_AddRefs(uri));
// 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);
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);
ClearScriptedRequests(CURRENT_REQUEST, aReason);
mCurrentRequest->CancelAndForgetObserver(aReason);
mCurrentRequest = nullptr;
mCurrentRequestFlags = 0;
}
// Deregister this image from the refresh driver so it no longer receives // notifications.
nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
&mPendingRequestRegistered);
void nsImageLoadingContent::NotifyOwnerDocumentActivityChanged() { if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
}
}
void nsImageLoadingContent::BindToTree(BindContext& aContext,
nsINode& aParent) { // We may be getting connected, if so our image should be tracked, if (aContext.InComposedDoc()) {
TrackImage(mCurrentRequest);
TrackImage(mPendingRequest);
}
}
void nsImageLoadingContent::UnbindFromTree() { // We may be leaving the document, so if our image is tracked, untrack it.
nsCOMPtr<Document> doc = GetOurCurrentDoc(); if (!doc) { return;
}
MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest, "Why haven't we heard of this request?");
Document* doc = GetOurCurrentDoc(); if (!doc) { return;
}
if (!aFrame) {
aFrame = GetOurPrimaryImageFrame();
}
/* This line is deceptively simple. It hides a lot of subtlety. Before we * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor * to determine if we should create an nsImageFrame or create a frame based * on the display of the element (ie inline, block, etc). Inline, block, etc * frames don't register for visibility tracking so they will return UNTRACKED * from GetVisibility(). So this line is choosing to mark such images as * visible. Once the image loads we will get an nsImageFrame and the proper * visibility. This is a pitfall of tracking the visibility on the frames * instead of the content node.
*/ if (!aFrame ||
aFrame->GetVisibility() == Visibility::ApproximatelyNonVisible) { 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.
Document* doc = GetOurCurrentDoc(); if (aImage == mCurrentRequest) { if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
doc->ImageTracker()->Remove(
mCurrentRequest,
aNonvisibleAction == Some(OnNonvisible::DiscardImages)
? ImageTracker::REQUEST_DISCARD
: 0);
} elseif (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) { // 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::DiscardImages)
? ImageTracker::REQUEST_DISCARD
: 0);
} elseif (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) { // If we're not in the document we may still need to be discarded.
aImage->RequestDiscard();
}
}
}
nsImageLoadingContent::ScriptedImageObserver::~ScriptedImageObserver() { // We should have cancelled any requests before getting released.
DebugOnly<bool> cancel = CancelRequests();
MOZ_ASSERT(!cancel, "Still have requests in ~ScriptedImageObserver!");
}
int32_t hash = useMap.FindChar('#'); if (hash < 0) { return nullptr;
} // useMap contains a '#', set start to point right after the '#'
start.advance(hash + 1);
RefPtr<nsContentList> imageMapList; if (aElement->IsInUncomposedDoc()) { // Optimize the common case and use document level image map.
imageMapList = aElement->OwnerDoc()->ImageMapList();
} else { // Per HTML spec image map should be searched in the element's scope, // so using SubtreeRoot() here. // Because this is a temporary list, we don't need to make it live.
imageMapList = new nsContentList(aElement->SubtreeRoot(), kNameSpaceID_XHTML,
nsGkAtoms::map, nsGkAtoms::map, true, /* deep */ false/* live */);
}
nsAutoString mapName(Substring(start, end));
uint32_t i, n = imageMapList->Length(true); for (i = 0; i < n; ++i) {
nsIContent* map = imageMapList->Item(i); if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
eCaseMatters) ||
map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
mapName, eCaseMatters)) { return map->AsElement();
}
}
return nullptr;
}
nsLoadFlags nsImageLoadingContent::LoadFlags() { auto* image = HTMLImageElement::FromNode(AsContent()); if (image && image->OwnerDoc()->IsScriptEnabled() &&
!image->OwnerDoc()->IsStaticDocument() &&
image->LoadingState() == Element::Loading::Lazy) { // Note that LOAD_BACKGROUND is not about priority of the load, but about // whether it blocks the load event (by bypassing the loadgroup). return nsIRequest::LOAD_BACKGROUND;
} return nsIRequest::LOAD_NORMAL;
}