nsImageLoadingContent.h (21792B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 /* 8 * A base class which implements nsIImageLoadingContent and can be 9 * subclassed by various content nodes that want to provide image 10 * loading functionality (eg <img>, <object>, etc). 11 */ 12 13 #ifndef nsImageLoadingContent_h__ 14 #define nsImageLoadingContent_h__ 15 16 #include "Units.h" 17 #include "imgINotificationObserver.h" 18 #include "mozilla/CORSMode.h" 19 #include "mozilla/TimeStamp.h" 20 #include "mozilla/dom/BindingDeclarations.h" 21 #include "mozilla/dom/Promise.h" 22 #include "mozilla/dom/RustTypes.h" 23 #include "nsAttrValue.h" 24 #include "nsCOMPtr.h" 25 #include "nsIContentPolicy.h" 26 #include "nsIImageLoadingContent.h" 27 #include "nsIRequest.h" 28 29 class nsINode; 30 class nsIURI; 31 class nsPresContext; 32 class nsIContent; 33 class imgRequestProxy; 34 class ImageLoadTask; 35 36 namespace mozilla { 37 class AsyncEventDispatcher; 38 class ErrorResult; 39 40 namespace dom { 41 struct BindContext; 42 class Document; 43 class Element; 44 enum class FetchPriority : uint8_t; 45 } // namespace dom 46 } // namespace mozilla 47 48 #ifdef LoadImage 49 // Undefine LoadImage to prevent naming conflict with Windows. 50 # undef LoadImage 51 #endif 52 53 class nsImageLoadingContent : public nsIImageLoadingContent { 54 protected: 55 friend class ImageLoadTask; 56 template <typename T> 57 using Maybe = mozilla::Maybe<T>; 58 using Nothing = mozilla::Nothing; 59 using OnNonvisible = mozilla::OnNonvisible; 60 using Visibility = mozilla::Visibility; 61 62 /* METHODS */ 63 public: 64 nsImageLoadingContent(); 65 virtual ~nsImageLoadingContent(); 66 67 NS_DECL_IMGINOTIFICATIONOBSERVER 68 NS_DECL_NSIIMAGELOADINGCONTENT 69 70 // Web IDL binding methods. 71 // Note that the XPCOM SetLoadingEnabled method is OK for Web IDL bindings 72 // to use as well, since it does not throw when called via the Web IDL 73 // bindings. 74 75 bool LoadingEnabled() const { return mLoadingEnabled; } 76 void AddObserver(imgINotificationObserver* aObserver); 77 void RemoveObserver(imgINotificationObserver* aObserver); 78 already_AddRefed<imgIRequest> GetRequest(int32_t aRequestType, 79 mozilla::ErrorResult& aError); 80 int32_t GetRequestType(imgIRequest* aRequest, mozilla::ErrorResult& aError); 81 already_AddRefed<nsIURI> GetCurrentURI(); 82 already_AddRefed<nsIURI> GetCurrentRequestFinalURI(); 83 void ForceReload(bool aNotify, mozilla::ErrorResult& aError); 84 85 mozilla::dom::Element* FindImageMap(); 86 static mozilla::dom::Element* FindImageMap(mozilla::dom::Element*); 87 88 /** 89 * Toggle whether or not to synchronously decode an image on draw. 90 */ 91 void SetSyncDecodingHint(bool aHint); 92 93 /** 94 * Notify us that the document state has changed. Called by nsDocument so that 95 * we may reject any promises which require the document to be active. 96 */ 97 void NotifyOwnerDocumentActivityChanged(); 98 99 // Trigger text recognition for the current image request. 100 already_AddRefed<mozilla::dom::Promise> RecognizeCurrentImageText( 101 mozilla::ErrorResult&); 102 103 protected: 104 enum ImageLoadType { 105 // Most normal image loads 106 eImageLoadType_Normal, 107 // From a <img srcset> or <picture> context. Affects type given to content 108 // policy. 109 eImageLoadType_Imageset 110 }; 111 112 /** 113 * LoadImage is called by subclasses when the appropriate 114 * attributes (eg 'src' for <img> tags) change. The string passed 115 * in is the new uri string; this consolidates the code for getting 116 * the charset, constructing URI objects, and any other incidentals 117 * into this superclass. 118 * 119 * @param aNewURI the URI spec to be loaded (may be a relative URI) 120 * @param aForce If true, make sure to load the URI. If false, only 121 * load if the URI is different from the currently loaded URI. 122 * @param aNotify If true, nsIDocumentObserver state change notifications 123 * will be sent as needed. 124 * @param aImageLoadType The ImageLoadType for this request 125 * @param aTriggeringPrincipal Optional parameter specifying the triggering 126 * principal to use for the image load 127 */ 128 nsresult LoadImage(const nsAString& aNewURI, bool aForce, bool aNotify, 129 ImageLoadType aImageLoadType, 130 nsIPrincipal* aTriggeringPrincipal = nullptr); 131 132 /** 133 * ImageState is called by subclasses that are computing their content state. 134 * The return value will have the ElementState::BROKEN bit set as needed. 135 * 136 * Note that this state assumes that this node is "trying" to be an 137 * image (so for example complete lack of attempt to load an image will lead 138 * to ElementState::BROKEN being set). Subclasses that are not "trying" to 139 * be an image (eg an HTML <input> of type other than "image") should just 140 * not call this method when computing their intrinsic state. 141 */ 142 mozilla::dom::ElementState ImageState() const; 143 144 /** 145 * LoadImage is called by subclasses when the appropriate 146 * attributes (eg 'src' for <img> tags) change. If callers have an 147 * URI object already available, they should use this method. 148 * 149 * @param aNewURI the URI to be loaded 150 * @param aForce If true, make sure to load the URI. If false, only 151 * load if the URI is different from the currently loaded URI. 152 * @param aNotify If true, nsIDocumentObserver state change notifications 153 * will be sent as needed. 154 * @param aImageLoadType The ImageLoadType for this request 155 * @param aDocument Optional parameter giving the document this node is in. 156 * This is purely a performance optimization. 157 * @param aLoadFlags Optional parameter specifying load flags to use for 158 * the image load 159 * @param aTriggeringPrincipal Optional parameter specifying the triggering 160 * principal to use for the image load 161 */ 162 nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify, 163 ImageLoadType aImageLoadType, nsLoadFlags aLoadFlags, 164 mozilla::dom::Document* aDocument = nullptr, 165 nsIPrincipal* aTriggeringPrincipal = nullptr); 166 167 nsresult LoadImage(nsIURI* aNewURI, bool aForce, bool aNotify, 168 ImageLoadType aImageLoadType, 169 nsIPrincipal* aTriggeringPrincipal) { 170 return LoadImage(aNewURI, aForce, aNotify, aImageLoadType, LoadFlags(), 171 nullptr, aTriggeringPrincipal); 172 } 173 174 /** 175 * helpers to get the document for this content (from the nodeinfo 176 * and such). Not named GetOwnerDoc/GetCurrentDoc to prevent ambiguous 177 * method names in subclasses 178 * 179 * @return the document we belong to 180 */ 181 mozilla::dom::Document* GetOurOwnerDoc(); 182 mozilla::dom::Document* GetOurCurrentDoc(); 183 184 /** 185 * Helper function to get the frame associated with this content. Not named 186 * GetPrimaryFrame to prevent ambiguous method names in subclasses. 187 * 188 * @return The frame we own, or nullptr if it doesn't exist, or isn't 189 * associated with any of our requests. 190 */ 191 nsIFrame* GetOurPrimaryImageFrame(); 192 193 /** 194 * Helper function to get the PresContext associated with this content's 195 * frame. Not named GetPresContext to prevent ambiguous method names in 196 * subclasses. 197 * 198 * @return The nsPresContext associated with our frame, or nullptr if either 199 * the frame doesn't exist, or the frame's prescontext doesn't exist. 200 */ 201 nsPresContext* GetFramePresContext(); 202 203 /** 204 * CancelImageRequests is called by subclasses when they want to 205 * cancel all image requests (for example when the subclass is 206 * somehow not an image anymore). 207 */ 208 void CancelImageRequests(bool aNotify); 209 210 /** 211 * Derived classes of nsImageLoadingContent MUST call Destroy from their 212 * destructor, or earlier. It does things that cannot be done in 213 * ~nsImageLoadingContent because they rely on being able to QueryInterface to 214 * other derived classes, which cannot happen once the derived class 215 * destructor has started calling the base class destructors. 216 */ 217 void Destroy(); 218 219 /** 220 * Returns the CORS mode that will be used for all future image loads. The 221 * default implementation returns CORS_NONE unconditionally. 222 */ 223 virtual mozilla::CORSMode GetCORSMode(); 224 225 // Subclasses are *required* to call BindToTree/UnbindFromTree. 226 void BindToTree(mozilla::dom::BindContext&, nsINode& aParent); 227 void UnbindFromTree(); 228 229 void OnLoadComplete(imgIRequest* aRequest, uint32_t aImageStatus); 230 void OnUnlockedDraw(); 231 void OnImageIsAnimated(imgIRequest* aRequest); 232 233 // The nsContentPolicyType we would use for this ImageLoadType 234 static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType); 235 236 void AsyncEventRunning(mozilla::AsyncEventDispatcher* aEvent); 237 238 // Get ourselves as an nsIContent*. Not const because some of the callers 239 // want a non-const nsIContent. 240 virtual nsIContent* AsContent() = 0; 241 242 virtual mozilla::dom::FetchPriority GetFetchPriorityForImage() const; 243 244 /** 245 * Get the natural size of the current request, as defined here: 246 * https://html.spec.whatwg.org/multipage/images.html#preferred-density-corrected-dimensions 247 * 248 * By default, we return the density-corrected natural size, though we skip 249 * density-correction if DoDensityCorrection::No is passed. 250 */ 251 enum class DoDensityCorrection : bool { No, Yes }; 252 mozilla::CSSIntSize NaturalSize( 253 DoDensityCorrection = DoDensityCorrection::Yes); 254 255 /** 256 * Get width and height of the current request, using given image request if 257 * attributes are unset. 258 */ 259 MOZ_CAN_RUN_SCRIPT mozilla::CSSIntSize GetWidthHeightForImage(); 260 261 /** 262 * Create a promise and queue a microtask which will ensure the current 263 * request (after any pending loads are applied) has requested a full decode. 264 * The promise is fulfilled once the request has a fully decoded surface that 265 * is available for drawing, or an error condition occurrs (e.g. broken image, 266 * current request is updated, etc). 267 * 268 * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode 269 */ 270 already_AddRefed<mozilla::dom::Promise> QueueDecodeAsync( 271 mozilla::ErrorResult& aRv); 272 273 enum class ImageDecodingType : uint8_t { 274 Auto, 275 Async, 276 Sync, 277 }; 278 279 static constexpr nsAttrValue::EnumTableEntry kDecodingTable[] = { 280 {"auto", nsImageLoadingContent::ImageDecodingType::Auto}, 281 {"async", nsImageLoadingContent::ImageDecodingType::Async}, 282 {"sync", nsImageLoadingContent::ImageDecodingType::Sync}, 283 }; 284 static constexpr const nsAttrValue::EnumTableEntry* kDecodingTableDefault = 285 &nsImageLoadingContent::kDecodingTable[0]; 286 287 private: 288 /** 289 * Enqueue and/or fulfill a promise created by QueueDecodeAsync. 290 */ 291 void DecodeAsync(RefPtr<mozilla::dom::Promise>&& aPromise, 292 uint32_t aRequestGeneration); 293 294 /** 295 * Attempt to resolve all queued promises based on the state of the current 296 * request. If the current request does not yet have all of the encoded data, 297 * or the decoding has not yet completed, it will return without changing the 298 * promise states. 299 */ 300 void MaybeResolveDecodePromises(); 301 302 /** 303 * Reject all queued promises with the given status. 304 */ 305 void RejectDecodePromises(nsresult aStatus); 306 307 /** 308 * Age the generation counter if we have a new current request with a 309 * different URI. If the generation counter is aged, then all queued promises 310 * will also be rejected. 311 */ 312 void MaybeAgeRequestGeneration(nsIURI* aNewURI); 313 314 /** 315 * Deregister as an observer for the owner document's activity notifications 316 * if we have no outstanding decode promises. 317 */ 318 void MaybeDeregisterActivityObserver(); 319 320 /** 321 * Struct used to manage the native image observers. 322 */ 323 struct ImageObserver { 324 explicit ImageObserver(imgINotificationObserver* aObserver); 325 ~ImageObserver(); 326 327 nsCOMPtr<imgINotificationObserver> mObserver; 328 ImageObserver* mNext; 329 }; 330 331 /** 332 * Struct used to manage the scripted/XPCOM image observers. 333 */ 334 class ScriptedImageObserver final { 335 public: 336 NS_INLINE_DECL_REFCOUNTING(ScriptedImageObserver) 337 338 ScriptedImageObserver(imgINotificationObserver* aObserver, 339 RefPtr<imgRequestProxy>&& aCurrentRequest, 340 RefPtr<imgRequestProxy>&& aPendingRequest); 341 bool CancelRequests(); 342 343 nsCOMPtr<imgINotificationObserver> mObserver; 344 RefPtr<imgRequestProxy> mCurrentRequest; 345 RefPtr<imgRequestProxy> mPendingRequest; 346 347 private: 348 ~ScriptedImageObserver(); 349 }; 350 351 /** 352 * Method to fire an event once we know what's going on with the image load. 353 * 354 * @param aEventType "load", or "error" depending on how things went 355 * @param aIsCancelable true if event is cancelable. 356 */ 357 nsresult FireEvent(const nsAString& aEventType, bool aIsCancelable = false); 358 359 /** 360 * Method to cancel and null-out pending event if they exist. 361 */ 362 void CancelPendingEvent(); 363 364 RefPtr<mozilla::AsyncEventDispatcher> mPendingEvent; 365 366 protected: 367 /** 368 * UpdateImageState recomputes the current state of this image loading 369 * content and updates what ImageState() returns accordingly. It will also 370 * fire a ContentStatesChanged() notification as needed if aNotify is true. 371 */ 372 void UpdateImageState(bool aNotify); 373 374 /** 375 * Method to create an nsIURI object from the given string (will 376 * handle getting the right charset, base, etc). You MUST pass in a 377 * non-null document to this function. 378 * 379 * @param aSpec the string spec (from an HTML attribute, eg) 380 * @param aDocument the document we belong to 381 * @return the URI we want to be loading 382 */ 383 nsresult StringToURI(const nsAString& aSpec, 384 mozilla::dom::Document* aDocument, nsIURI** aURI); 385 386 /** 387 * Prepare and returns a reference to the "next request". If there's already 388 * a _usable_ current request (one with SIZE_AVAILABLE), this request is 389 * "pending" until it becomes usable. Otherwise, this becomes the current 390 * request. 391 * 392 * @param aImageLoadType The ImageLoadType for this request 393 * @param aNewURI The uri that we're going to load 394 */ 395 RefPtr<imgRequestProxy>& PrepareNextRequest(ImageLoadType, nsIURI* aNewURI); 396 397 /** 398 * Returns a COMPtr reference to the current/pending image requests, cleaning 399 * up and canceling anything that was there before. Note that if you just want 400 * to get rid of one of the requests, you should call 401 * Clear*Request(NS_BINDING_ABORTED) instead. 402 * 403 * @param aImageLoadType The ImageLoadType for this request 404 * @param aNewURI The uri that we're going to load 405 */ 406 RefPtr<imgRequestProxy>& PrepareCurrentRequest(ImageLoadType, 407 nsIURI* aNewURI); 408 RefPtr<imgRequestProxy>& PreparePendingRequest(ImageLoadType); 409 410 /** 411 * Switch our pending request to be our current request. 412 * mPendingRequest must be non-null! 413 */ 414 void MakePendingRequestCurrent(); 415 416 /** 417 * Cancels and nulls-out the "current" and "pending" requests if they exist. 418 * 419 * @param aNonvisibleAction An action to take if the image is no longer 420 * visible as a result; see |UntrackImage|. 421 */ 422 void ClearCurrentRequest( 423 nsresult aReason, 424 const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()); 425 void ClearPendingRequest( 426 nsresult aReason, 427 const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()); 428 429 /** 430 * Static helper method to tell us if we have the size of a request. The 431 * image may be null. 432 */ 433 static bool HaveSize(imgIRequest* aImage); 434 435 /** 436 * Adds/Removes a given imgIRequest from our document's tracker. 437 * 438 * No-op if aImage is null. 439 * 440 * @param aFrame If called from FrameCreated the frame passed to FrameCreated. 441 * This is our frame, but at the time of the FrameCreated call 442 * our primary frame pointer hasn't been set yet, so this is 443 * only way to get our frame. 444 * 445 * @param aNonvisibleAction A requested action if the frame has become 446 * nonvisible. If Nothing(), no action is 447 * requested. If DISCARD_IMAGES is specified, the 448 * frame is requested to ask any images it's 449 * associated with to discard their surfaces if 450 * possible. 451 */ 452 void TrackImage(imgIRequest* aImage, nsIFrame* aFrame = nullptr); 453 void UntrackImage(imgIRequest* aImage, 454 const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()); 455 456 nsLoadFlags LoadFlags(); 457 458 private: 459 /** 460 * Clones the given "current" or "pending" request for each scripted observer. 461 */ 462 void CloneScriptedRequests(imgRequestProxy* aRequest); 463 464 /** 465 * Cancels and nulls-out the "current" or "pending" requests if they exist 466 * for each scripted observer. 467 */ 468 void ClearScriptedRequests(int32_t aRequestType, nsresult aReason); 469 470 /** 471 * Moves the "pending" request into the "current" request for each scripted 472 * observer. If there is an existing "current" request, it will cancel it 473 * first. 474 */ 475 void MakePendingScriptedRequestsCurrent(); 476 477 /** 478 * Depending on the configured decoding hint, and/or how recently we updated 479 * the image request, force or stop the frame from decoding the image 480 * synchronously when it is drawn. 481 * @param aPrepareNextRequest True if this is when updating the image request. 482 * @param aFrame If called from FrameCreated the frame passed to FrameCreated. 483 * This is our frame, but at the time of the FrameCreated call 484 * our primary frame pointer hasn't been set yet, so this is 485 * only way to get our frame. 486 */ 487 void MaybeForceSyncDecoding(bool aPrepareNextRequest, 488 nsIFrame* aFrame = nullptr); 489 490 protected: 491 void QueueImageTask(nsIURI* aURI, nsIPrincipal* aSrcTriggeringPrincipal, 492 bool aForceAsync, bool aAlwaysLoad, bool aNotify); 493 void QueueImageTask(nsIURI* aURI, bool aAlwaysLoad, bool aNotify) { 494 QueueImageTask(aURI, nullptr, false, aAlwaysLoad, aNotify); 495 } 496 497 void ClearImageLoadTask(); 498 499 virtual void LoadSelectedImage(bool aAlwaysLoad, bool aStopLazyLoading) = 0; 500 501 RefPtr<ImageLoadTask> mPendingImageLoadTask; 502 503 RefPtr<imgRequestProxy> mCurrentRequest; 504 RefPtr<imgRequestProxy> mPendingRequest; 505 506 private: 507 /** 508 * Typically we will have only one observer (our frame in the screen 509 * prescontext), so we want to only make space for one and to 510 * heap-allocate anything past that (saves memory and malloc churn 511 * in the common case). The storage is a linked list, we just 512 * happen to actually hold the first observer instead of a pointer 513 * to it. 514 */ 515 ImageObserver mObserverList; 516 517 /** 518 * Typically we will have no scripted observers, as this is only used by 519 * chrome, legacy extensions, and some mochitests. An empty array reserves 520 * minimal memory. 521 */ 522 nsTArray<RefPtr<ScriptedImageObserver>> mScriptedObservers; 523 524 // If the image was blocked or if there was an error loading, it's nice to 525 // still keep track of what the URI was despite not having an imgIRequest. 526 // We only maintain this in those situations (in the common case, this is 527 // always null). 528 nsCOMPtr<nsIURI> mCurrentURI; 529 530 mozilla::TimeStamp mMostRecentRequestChange; 531 532 /** 533 * Promises created by QueueDecodeAsync that are still waiting to be 534 * fulfilled by the image being fully decoded. 535 */ 536 nsTArray<RefPtr<mozilla::dom::Promise>> mDecodePromises; 537 538 /** 539 * Total number of outstanding decode promises, including those stored in 540 * mDecodePromises and those embedded in runnables waiting to be enqueued. 541 * This is used to determine whether we need to register as an observer for 542 * document activity notifications. 543 */ 544 size_t mOutstandingDecodePromises = 0; 545 546 /** 547 * An incrementing counter representing the current request generation; 548 * Each time mCurrentRequest is modified with a different URI, this will 549 * be incremented. Each QueueDecodeAsync call will cache the generation 550 * of the current request so that when it is processed, it knows if it 551 * should have rejected because the request changed. 552 */ 553 uint32_t mRequestGeneration = 0; 554 555 protected: 556 bool mLoadingEnabled : 1 = true; 557 /** 558 * Flag to indicate whether the channel should be mark as urgent-start. 559 * It should be set in *Element and passed to nsContentUtils::LoadImage. 560 * True if we want to set nsIClassOfService::UrgentStart to the channel to 561 * get the response ASAP for better user responsiveness. 562 */ 563 bool mUseUrgentStartForChannel : 1 = false; 564 565 // Represents the image is deferred loading until this element gets visible. 566 bool mLazyLoading : 1 = false; 567 568 // If true, force frames to synchronously decode images on draw. 569 bool mSyncDecodingHint : 1 = false; 570 571 // Whether we're in the doc responsive content set (HTMLImageElement only). 572 bool mInDocResponsiveContent : 1 = false; 573 574 private: 575 // Flags to indicate whether each of the current and pending requests are 576 // registered with the refresh driver. 577 bool mCurrentRequestRegistered = false; 578 bool mPendingRequestRegistered = false; 579 580 enum { 581 // Set if the request is currently tracked with the document. 582 REQUEST_IS_TRACKED = 1 << 0, 583 // Set if this is an imageset request, such as from <img srcset> or 584 // <picture> 585 REQUEST_IS_IMAGESET = 1 << 1, 586 }; 587 uint8_t mCurrentRequestFlags = 0; 588 uint8_t mPendingRequestFlags = 0; 589 }; 590 591 #endif // nsImageLoadingContent_h__