| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2004, 2009 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IMAGE_LOADER_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_IMAGE_LOADER_H_ |
| |
| #include <memory> |
| #include "base/memory/weak_ptr.h" |
| #include "services/network/public/mojom/referrer_policy.mojom-blink.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/loader/resource/image_resource.h" |
| #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h" |
| #include "third_party/blink/renderer/core/loader/resource/image_resource_observer.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_set.h" |
| #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" |
| |
| namespace blink { |
| |
| class ContainerNode; |
| class DOMWrapperWorld; |
| class Element; |
| class ExceptionState; |
| class IncrementLoadEventDelayCount; |
| class LayoutImageResource; |
| class ScriptState; |
| |
| class CORE_EXPORT ImageLoader : public GarbageCollected<ImageLoader>, |
| public ImageResourceObserver { |
| USING_PRE_FINALIZER(ImageLoader, Dispose); |
| |
| public: |
| explicit ImageLoader(Element*); |
| ~ImageLoader() override; |
| |
| void Trace(Visitor*) const; |
| |
| enum UpdateFromElementBehavior { |
| // This should be the update behavior when the element is attached to a |
| // document, or when DOM mutations trigger a new load. Starts loading if a |
| // load hasn't already been started. |
| kUpdateNormal, |
| // This should be the update behavior when the resource was changed (via |
| // 'src', 'srcset' or 'sizes'). Starts a new load even if a previous load of |
| // the same resource have failed, to match Firefox's behavior. |
| // FIXME - Verify that this is the right behavior according to the spec. |
| kUpdateIgnorePreviousError, |
| // This forces the image to update its intrinsic size, even if the image |
| // source has not changed. |
| kUpdateSizeChanged, |
| // This force the image to refetch and reload the image source, even if it |
| // has not changed. |
| kUpdateForcedReload |
| }; |
| |
| void UpdateFromElement(UpdateFromElementBehavior = kUpdateNormal, |
| network::mojom::ReferrerPolicy = |
| network::mojom::ReferrerPolicy::kDefault); |
| |
| void ElementDidMoveToNewDocument(); |
| |
| Element* GetElement() const { return element_; } |
| bool ImageComplete() const { return image_complete_ && !pending_task_; } |
| |
| ImageResourceContent* GetContent() const { return image_content_.Get(); } |
| |
| // Returns true if this loader should be updated via UpdateFromElement() when |
| // being inserted into a new parent; returns false otherwise. |
| bool ShouldUpdateOnInsertedInto( |
| ContainerNode& insertion_point, |
| network::mojom::ReferrerPolicy referrer_policy = |
| network::mojom::ReferrerPolicy::kDefault) const; |
| |
| // Returns true if a the owner of this loader should consider the image being |
| // loaded as "potentially available", i.e that it may eventually become |
| // available. |
| bool ImageIsPotentiallyAvailable() const; |
| |
| // Cancels pending load events, and doesn't dispatch new ones. |
| // Note: ClearImage/SetImage.*() are not a simple setter. |
| // Check the implementation to see what they do. |
| // TODO(hiroshige): Cleanup these methods. |
| void ClearImage(); |
| void SetImageForTest(ImageResourceContent*); |
| |
| // Image document loading: |
| // |
| // Loading via ImageDocument: |
| // ImageDocumentParser creates an ImageResource. |
| // The associated ImageResourceContent is provided to |
| // SetImageDocumentContent and set as |
| // |image_content_for_image_document_|. This ImageResourceContent is not |
| // associated with a ResourceLoader. |
| // When loading is initiated (through the HTMLImageElement that is the |
| // owner of the ImageLoader), |image_content_for_image_document_| is picked |
| // as the ImageResourceContent to use and is then reset to null. Thus |
| // |image_content_for_image_document_| should only be set (and used) when |
| // HTMLImageElement::StartLoadingImageDocument() is in the caller chain to |
| // UpdateFromElement(). |
| // The corresponding ImageDocument is responsible for supplying the |
| // response and data via the ImageResourceContent it provided (which is now |
| // set as |image_content_|). |
| // Otherwise: |
| // Normal loading via ResourceFetcher/ResourceLoader. |
| // |image_content_for_image_document_| is null. |
| void SetImageDocumentContent(ImageResourceContent* image_content) { |
| image_content_for_image_document_ = image_content; |
| } |
| |
| bool HasPendingActivity() const { return HasPendingEvent() || pending_task_; } |
| |
| bool HasPendingError() const { return pending_error_event_.IsActive(); } |
| |
| bool HadError() const { return !failed_load_url_.IsEmpty(); } |
| |
| bool GetImageAnimationPolicy(mojom::blink::ImageAnimationPolicy&) final; |
| |
| ScriptPromise Decode(ScriptState*, ExceptionState&); |
| |
| void LoadDeferredImage(network::mojom::ReferrerPolicy); |
| |
| protected: |
| void ImageChanged(ImageResourceContent*, CanDeferInvalidation) override; |
| void ImageNotifyFinished(ImageResourceContent*) override; |
| |
| private: |
| class Task; |
| |
| enum class UpdateType { kAsync, kSync }; |
| |
| // LazyImages: Defer the image load until the image is near the viewport. |
| // https://docs.google.com/document/d/1jF1eSOhqTEt0L1WBCccGwH9chxLd9d1Ez0zo11obj14 |
| // The state transition is better captured in the below doc. |
| // https://docs.google.com/document/d/1Ym0EOwyZJmaB5afnCVPu0SFb8EWLBj_facm2fK9kgC0/ |
| enum class LazyImageLoadState { |
| kNone, // LazyImages not active. |
| kDeferred, // Full image load not started, and image load event will not be |
| // fired. Image will not block the document's load event. |
| kFullImage // Full image is loading/loaded, due to element coming near the |
| // viewport. image_complete_ can be used to differentiate if the |
| // fetch is complete or not. After the fetch, image load event |
| // is fired. |
| }; |
| |
| // Called from the task or from updateFromElement to initiate the load. |
| void DoUpdateFromElement( |
| scoped_refptr<const DOMWrapperWorld> world, |
| UpdateFromElementBehavior, |
| network::mojom::ReferrerPolicy = network::mojom::ReferrerPolicy::kDefault, |
| UpdateType = UpdateType::kAsync); |
| |
| virtual void DispatchLoadEvent() = 0; |
| virtual void NoImageResourceToLoad() {} |
| |
| bool HasPendingEvent() const; |
| |
| void DispatchPendingLoadEvent(std::unique_ptr<IncrementLoadEventDelayCount>); |
| void DispatchPendingErrorEvent(std::unique_ptr<IncrementLoadEventDelayCount>); |
| |
| LayoutImageResource* GetLayoutImageResource(); |
| void UpdateLayoutObject(); |
| |
| // Note: SetImage.*() are not a simple setter. |
| // Check the implementation to see what they do. |
| // TODO(hiroshige): Cleanup these methods. |
| void SetImageWithoutConsideringPendingLoadEvent(ImageResourceContent*); |
| void UpdateImageState(ImageResourceContent*); |
| |
| void ClearFailedLoadURL(); |
| void DispatchErrorEvent(); |
| void CrossSiteOrCSPViolationOccurred(AtomicString); |
| void EnqueueImageLoadingMicroTask(UpdateFromElementBehavior, |
| network::mojom::ReferrerPolicy); |
| |
| KURL ImageSourceToKURL(AtomicString) const; |
| |
| // Used to determine whether to immediately initiate the load or to schedule a |
| // microtask. |
| bool ShouldLoadImmediately(const KURL&) const; |
| |
| // For Oilpan, we must run dispose() as a prefinalizer and call |
| // m_image->removeClient(this) (and more.) Otherwise, the ImageResource can |
| // invoke didAddClient() for the ImageLoader that is about to die in the |
| // current lazy sweeping, and the didAddClient() can access on-heap objects |
| // that have already been finalized in the current lazy sweeping. |
| void Dispose(); |
| |
| void DispatchDecodeRequestsIfComplete(); |
| void RejectPendingDecodes(UpdateType = UpdateType::kAsync); |
| void DecodeRequestFinished(uint64_t request_id, bool success); |
| |
| Member<Element> element_; |
| Member<ImageResourceContent> image_content_; |
| Member<ImageResourceContent> image_content_for_image_document_; |
| |
| String last_base_element_url_; |
| network::mojom::ReferrerPolicy last_referrer_policy_ = |
| network::mojom::ReferrerPolicy::kDefault; |
| AtomicString failed_load_url_; |
| base::WeakPtr<Task> pending_task_; // owned by Microtask |
| std::unique_ptr<IncrementLoadEventDelayCount> |
| delay_until_do_update_from_element_; |
| |
| // Delaying load event: the timeline should be: |
| // (0) ImageResource::Fetch() is called. |
| // (1) ResourceFetcher::StartLoad(): Resource loading is actually started. |
| // (2) ResourceLoader::DidFinishLoading() etc: |
| // Resource loading is finished, but SVG document load might be |
| // incomplete because of asynchronously loaded subresources. |
| // (3) ImageNotifyFinished(): Image is completely loaded. |
| // and we delay Document load event from (1) to (3): |
| // - |ResourceFetcher::loaders_| delays Document load event from (1) to (2). |
| // - |delay_until_image_notify_finished_| delays Document load event from |
| // the first ImageChanged() (at some time between (1) and (2)) until (3). |
| // Ideally, we might want to delay Document load event from (1) to (3), |
| // but currently we piggyback on ImageChanged() because adding a callback |
| // hook at (1) might complicate the code. |
| std::unique_ptr<IncrementLoadEventDelayCount> |
| delay_until_image_notify_finished_; |
| |
| TaskHandle pending_load_event_; |
| TaskHandle pending_error_event_; |
| |
| bool image_complete_ : 1; |
| bool suppress_error_events_ : 1; |
| // Tracks whether or not an image whose load was deferred was explicitly lazy |
| // (i.e., had developer-supplied `loading=lazy`). This matters because images |
| // that were not explicitly lazy but were deferred via automatic lazy image |
| // loading should continue to block the window load event, whereas explicitly |
| // lazy images should never block the window load event. |
| bool was_deferred_explicitly_ : 1; |
| |
| LazyImageLoadState lazy_image_load_state_; |
| |
| // DecodeRequest represents a single request to the Decode() function. The |
| // decode requests have one of the following states: |
| // |
| // - kPendingMicrotask: This is the initial state. The caller is responsible |
| // for scheduling a microtask that would advance the state to the next value. |
| // Images invalidated by the pending mutations microtask (|pending_task_|) do |
| // not invalidate decode requests in this state. The exception is synchronous |
| // updates that do not go through |pending_task_|. |
| // |
| // - kPendingLoad: Once the microtask runs, it advances the state to |
| // kPendingLoad which waits for the image to be complete. If |pending_task_| |
| // runs and modifies the image, it invalidates any DecodeRequests in this |
| // state. |
| // |
| // - kDispatched: Once the image is loaded and the request to decode it is |
| // dispatched on behalf of this DecodeRequest, the state changes to |
| // kDispatched. If |pending_task_| runs and modifies the image, it invalidates |
| // any DecodeRequests in this state. |
| class DecodeRequest : public GarbageCollected<DecodeRequest> { |
| public: |
| enum State { kPendingMicrotask, kPendingLoad, kDispatched }; |
| |
| DecodeRequest(ImageLoader*, ScriptPromiseResolver*); |
| ~DecodeRequest() = default; |
| |
| void Trace(Visitor*) const; |
| |
| uint64_t request_id() const { return request_id_; } |
| State state() const { return state_; } |
| ScriptPromise promise() { return resolver_->Promise(); } |
| |
| void Resolve(); |
| void Reject(); |
| |
| void ProcessForTask(); |
| void NotifyDecodeDispatched(); |
| |
| private: |
| static uint64_t s_next_request_id_; |
| |
| uint64_t request_id_ = 0; |
| State state_ = kPendingMicrotask; |
| |
| Member<ScriptPromiseResolver> resolver_; |
| Member<ImageLoader> loader_; |
| }; |
| |
| HeapVector<Member<DecodeRequest>> decode_requests_; |
| }; |
| |
| } // namespace blink |
| |
| #endif |