blob: 1cde362805bd4b60be970ffe609a646ca089850b [file] [log] [blame]
* Copyright (C) 1999 Lars Knoll (
* (C) 1999 Antti Koivisto (
* 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
* 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.
#include <memory>
#include "base/memory/weak_ptr.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 IncrementLoadEventDelayCount;
class Element;
class LayoutImageResource;
class ExceptionState;
class ScriptState;
class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
public ImageResourceObserver {
USING_PRE_FINALIZER(ImageLoader, Dispose);
explicit ImageLoader(Element*);
~ImageLoader() override;
void Trace(blink::Visitor*);
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.
// 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.
// This forces the image to update its intrinsic size, even if the image
// source has not changed.
// This force the image to refetch and reload the image source, even if it
// has not changed.
enum BypassMainWorldBehavior {
void UpdateFromElement(UpdateFromElementBehavior = kUpdateNormal,
network::mojom::ReferrerPolicy =
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) 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:
// When |loading_image_document_| is true:
// Loading via ImageDocument.
// |image_resource_for_image_document_| points to a ImageResource that is
// not associated with a ResourceLoader.
// The corresponding ImageDocument is responsible for supplying the response
// and data to |image_resource_for_image_document_| and thus
// |image_content_|.
// Otherwise:
// Normal loading via ResourceFetcher/ResourceLoader.
// |image_resource_for_image_document_| is null.
bool IsLoadingImageDocument() { return loading_image_document_; }
void SetLoadingImageDocument() { loading_image_document_ = true; }
ImageResource* ImageResourceForImageDocument() const {
return image_resource_for_image_document_;
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(ImageAnimationPolicy&) final;
ScriptPromise Decode(ScriptState*, ExceptionState&);
void LoadDeferredImage(network::mojom::ReferrerPolicy);
void ImageChanged(ImageResourceContent*, CanDeferInvalidation) override;
void ImageNotifyFinished(ImageResourceContent*) override;
class Task;
enum class UpdateType { kAsync, kSync };
// LazyImages: Defer the image load until the image is near the viewport.
// The state transition is better captured in the below doc.
enum class LazyImageLoadState {
kNone, // LazyImages not active.
kDeferred, // Placeholder is loading/loaded. Full image load not started.
// Once the placeholder is loaded, document load event is
// unblocked, but image load event is not fired yet.
kFullImage // Full image is loading/loaded, due to element coming near the
// viewport or if a placeholder load actually fetched the full
// image. image_complete_ can 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(
const KURL&,
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 SetImageForImageDocument(ImageResource*);
void SetImageWithoutConsideringPendingLoadEvent(ImageResourceContent*);
void UpdateImageState(ImageResourceContent*);
void ClearFailedLoadURL();
void DispatchErrorEvent();
void CrossSiteOrCSPViolationOccurred(AtomicString);
void EnqueueImageLoadingMicroTask(const KURL&,
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<ImageResource> image_resource_for_image_document_;
String last_base_element_url_;
AtomicString failed_load_url_;
base::WeakPtr<Task> pending_task_; // owned by Microtask
// 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.
TaskHandle pending_load_event_;
TaskHandle pending_error_event_;
bool image_complete_ : 1;
bool loading_image_document_ : 1;
bool suppress_error_events_ : 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> {
enum State { kPendingMicrotask, kPendingLoad, kDispatched };
DecodeRequest(ImageLoader*, ScriptPromiseResolver*);
~DecodeRequest() = default;
void Trace(blink::Visitor*);
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();
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