Commit 396a1266 authored by hiroshige's avatar hiroshige Committed by Commit bot

Phase III Step 2: Call imageNotifyFinished() and image load event after SVG loading completes

design doc:
https://docs.google.com/document/d/1O-fB83mrE0B_V8gzXNqHgmRLCvstTB4MMi3RnVLr8bE/edit#
https://docs.google.com/document/d/1obEb8K4mJpG9Y_x4_ZhvnbFHojZoLLzMCFsalkl8voc/edit#

This CL calls ImageResourceObserver::ImageNotifyFinished()
after SVG image loading is completed, not necessarily the same time as
ResourceLoader completion (e.g. DidFinishLoading()).
This makes SVG's <img> load event to be fired after SVG image loading
is completed even when the SVG has subresources loaded asynchronously.

1. Image::SetData() can return SizeAvailableAndLoadingAsynchronously
   when SVG image loading is not completed synchronously (due to
   subresources), and notifies the completion asynchronously via
   ImageObserver:LoadCompleted().
   LocalFrameClient::DispatchDidHandleOnloadEvents() is used as a
   notification of SVG image loading completion in SVGImage.

2. ImageResourceContent delays calling ImageNotifyFinished() and
   setting |content_status_| to kCached until SVG image loading is
   completed.

3. ImageLoader additionally delays the Document load event
   - from the first ImageChanged() during image loading
   - until (potentially delayed) ImageNotifyFinished().

In image loading not by ImageLoader, the Document load event is not
delayed from ResourceLoader completion until ImageNotifyFinished(),
but this is not a regression.

In image loading by ImageLoader, the Document load event is delayed
until ImageNotifyFinished() by the combination of
ResourceFetcher::loaders_ and ImageLoader.
ImageLoader starts delaying the Document load event
- Not from |image_| is set, because we shouldn't delay the Document
  load event when image loading is deferred.
- Not from ImageResourceContent::NotifyStartLoad(), to avoid additional
  ImageResourceObserver callbacks.

BUG=382170, 690480, 667641

Review-Url: https://codereview.chromium.org/2613853002
Cr-Commit-Position: refs/heads/master@{#472229}
parent 03d66d9c
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
var t1 = async_test("Loading of SVG with a valid font completes " +
"before <img> load event and drawImage() doesn't crash.");
var imgOnLoadCalled = false;
var imgOnLoad = t1.step_func(function() {
imgOnLoadCalled = true;
var svg = document.getElementById('img');
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(svg, 0, 0);
});
var bodyOnLoad = t1.step_func_done(function() {
assert_true(imgOnLoadCalled,
"<img> load event must be fired before <body> load event");
});
</script>
<body onload="bodyOnLoad()">
<img src="resources/data-font-in-css.svg" id="img" onload="imgOnLoad()">
</body>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
var t1 = async_test("Loading of SVG with a font with a invalid URL completes " +
"before <img> load event and drawImage() doesn't crash.");
var imgOnLoadCalled = false;
var imgOnLoad = t1.step_func(function() {
imgOnLoadCalled = true;
var svg = document.getElementById('img');
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(svg, 0, 0);
});
var bodyOnLoad = t1.step_func_done(function() {
assert_true(imgOnLoadCalled,
"<img> load event must be fired before <body> load event");
});
</script>
<body onload="bodyOnLoad()">
<img src="resources/data-font-in-css-invalid-data-url.svg" id="img" onload="imgOnLoad()">
</body>
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
var t1 = async_test("Loading of SVG with a invalid font completes " +
"before <img> load event and drawImage() doesn't crash.");
var imgOnLoadCalled = false;
var imgOnLoad = t1.step_func(function() {
imgOnLoadCalled = true;
var svg = document.getElementById('img');
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(svg, 0, 0);
});
var bodyOnLoad = t1.step_func_done(function() {
assert_true(imgOnLoadCalled,
"<img> load event must be fired before <body> load event");
});
</script>
<body onload="bodyOnLoad()">
<img src="resources/data-font-in-css-invalid-font.svg" id="img" onload="imgOnLoad()">
</body>
<svg xmlns="http://www.w3.org/2000/svg" width="198" height="100">
<style>
<![CDATA[@font-face{font-family:"ahem-data"; src:url('data:font/ttf;base64,invalidAsDataURL@');}]]>
</style>
<text x="50" y="50" font-family="ahem-data" font-size="16">Hello, World</text>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="198" height="100">
<style>
<![CDATA[@font-face{font-family:"ahem-data"; src:url('data:font/ttf;base64,validAsDataUrlButInvalidAsFontData');}]]>
</style>
<text x="50" y="50" font-family="ahem-data" font-size="16">Hello, World</text>
</svg>
...@@ -177,6 +177,7 @@ void ImageLoader::Dispose() { ...@@ -177,6 +177,7 @@ void ImageLoader::Dispose() {
if (image_) { if (image_) {
image_->RemoveObserver(this); image_->RemoveObserver(this);
image_ = nullptr; image_ = nullptr;
delay_until_image_notify_finished_ = nullptr;
} }
} }
...@@ -293,7 +294,7 @@ inline void ImageLoader::EnqueueImageLoadingMicroTask( ...@@ -293,7 +294,7 @@ inline void ImageLoader::EnqueueImageLoadingMicroTask(
pending_task_ = task->CreateWeakPtr(); pending_task_ = task->CreateWeakPtr();
Microtask::EnqueueMicrotask( Microtask::EnqueueMicrotask(
WTF::Bind(&Task::Run, WTF::Passed(std::move(task)))); WTF::Bind(&Task::Run, WTF::Passed(std::move(task))));
load_delay_counter_ = delay_until_do_update_from_element_ =
IncrementLoadEventDelayCount::Create(element_->GetDocument()); IncrementLoadEventDelayCount::Create(element_->GetDocument());
} }
...@@ -301,6 +302,7 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image) { ...@@ -301,6 +302,7 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image) {
image_ = new_image; image_ = new_image;
has_pending_load_event_ = new_image; has_pending_load_event_ = new_image;
image_complete_ = !new_image; image_complete_ = !new_image;
delay_until_image_notify_finished_ = nullptr;
} }
void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior, void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior,
...@@ -318,7 +320,7 @@ void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior, ...@@ -318,7 +320,7 @@ void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior,
pending_task_.reset(); pending_task_.reset();
// Make sure to only decrement the count when we exit this function // Make sure to only decrement the count when we exit this function
std::unique_ptr<IncrementLoadEventDelayCount> load_delay_counter; std::unique_ptr<IncrementLoadEventDelayCount> load_delay_counter;
load_delay_counter.swap(load_delay_counter_); load_delay_counter.swap(delay_until_do_update_from_element_);
Document& document = element_->GetDocument(); Document& document = element_->GetDocument();
if (!document.IsActive()) if (!document.IsActive())
...@@ -469,6 +471,7 @@ void ImageLoader::UpdateFromElement(UpdateFromElementBehavior update_behavior, ...@@ -469,6 +471,7 @@ void ImageLoader::UpdateFromElement(UpdateFromElementBehavior update_behavior,
image->RemoveObserver(this); image->RemoveObserver(this);
} }
image_ = nullptr; image_ = nullptr;
delay_until_image_notify_finished_ = nullptr;
} }
// Don't load images for inactive documents. We don't want to slow down the // Don't load images for inactive documents. We don't want to slow down the
...@@ -511,6 +514,20 @@ bool ImageLoader::ShouldLoadImmediately(const KURL& url) const { ...@@ -511,6 +514,20 @@ bool ImageLoader::ShouldLoadImmediately(const KURL& url) const {
return (isHTMLObjectElement(element_) || isHTMLEmbedElement(element_)); return (isHTMLObjectElement(element_) || isHTMLEmbedElement(element_));
} }
void ImageLoader::ImageChanged(ImageResourceContent* content, const IntRect*) {
DCHECK_EQ(content, image_.Get());
if (image_complete_ || !content->IsLoading() ||
delay_until_image_notify_finished_)
return;
Document& document = element_->GetDocument();
if (!document.IsActive())
return;
delay_until_image_notify_finished_ =
IncrementLoadEventDelayCount::Create(document);
}
void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) { void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) {
RESOURCE_LOADING_DVLOG(1) RESOURCE_LOADING_DVLOG(1)
<< "ImageLoader::imageNotifyFinished " << this << "ImageLoader::imageNotifyFinished " << this
...@@ -530,6 +547,7 @@ void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) { ...@@ -530,6 +547,7 @@ void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) {
CHECK(!image_complete_); CHECK(!image_complete_);
image_complete_ = true; image_complete_ = true;
delay_until_image_notify_finished_ = nullptr;
// Update ImageAnimationPolicy for image_. // Update ImageAnimationPolicy for image_.
if (image_) if (image_)
...@@ -695,8 +713,14 @@ void ImageLoader::DispatchPendingErrorEvents() { ...@@ -695,8 +713,14 @@ void ImageLoader::DispatchPendingErrorEvents() {
} }
void ImageLoader::ElementDidMoveToNewDocument() { void ImageLoader::ElementDidMoveToNewDocument() {
if (load_delay_counter_) if (delay_until_do_update_from_element_) {
load_delay_counter_->DocumentChanged(element_->GetDocument()); delay_until_do_update_from_element_->DocumentChanged(
element_->GetDocument());
}
if (delay_until_image_notify_finished_) {
delay_until_image_notify_finished_->DocumentChanged(
element_->GetDocument());
}
ClearFailedLoadURL(); ClearFailedLoadURL();
ClearImage(); ClearImage();
} }
......
...@@ -126,6 +126,7 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, ...@@ -126,6 +126,7 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
bool GetImageAnimationPolicy(ImageAnimationPolicy&) final; bool GetImageAnimationPolicy(ImageAnimationPolicy&) final;
protected: protected:
void ImageChanged(ImageResourceContent*, const IntRect*) override;
void ImageNotifyFinished(ImageResourceContent*) override; void ImageNotifyFinished(ImageResourceContent*) override;
private: private:
...@@ -186,7 +187,26 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, ...@@ -186,7 +187,26 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
Timer<ImageLoader> deref_element_timer_; Timer<ImageLoader> deref_element_timer_;
AtomicString failed_load_url_; AtomicString failed_load_url_;
WeakPtr<Task> pending_task_; // owned by Microtask WeakPtr<Task> pending_task_; // owned by Microtask
std::unique_ptr<IncrementLoadEventDelayCount> load_delay_counter_; 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_;
bool has_pending_load_event_ : 1; bool has_pending_load_event_ : 1;
bool has_pending_error_event_ : 1; bool has_pending_error_event_ : 1;
bool image_complete_ : 1; bool image_complete_ : 1;
......
...@@ -182,6 +182,16 @@ bool ImageResource::CanReuse(const FetchParameters& params) const { ...@@ -182,6 +182,16 @@ bool ImageResource::CanReuse(const FetchParameters& params) const {
return true; return true;
} }
bool ImageResource::CanUseCacheValidator() const {
// Disable revalidation while ImageResourceContent is still waiting for
// SVG load completion.
// TODO(hiroshige): Clean up revalidation-related dependencies.
if (!GetContent()->IsLoaded())
return false;
return Resource::CanUseCacheValidator();
}
ImageResource* ImageResource::Create(const ResourceRequest& request) { ImageResource* ImageResource::Create(const ResourceRequest& request) {
return new ImageResource(request, ResourceLoaderOptions(), return new ImageResource(request, ResourceLoaderOptions(),
ImageResourceContent::CreateNotStarted(), false); ImageResourceContent::CreateNotStarted(), false);
......
...@@ -80,6 +80,7 @@ class CORE_EXPORT ImageResource final ...@@ -80,6 +80,7 @@ class CORE_EXPORT ImageResource final
void AllClientsAndObserversRemoved() override; void AllClientsAndObserversRemoved() override;
bool CanReuse(const FetchParameters&) const override; bool CanReuse(const FetchParameters&) const override;
bool CanUseCacheValidator() const override;
PassRefPtr<const SharedBuffer> ResourceBuffer() const override; PassRefPtr<const SharedBuffer> ResourceBuffer() const override;
void NotifyStartLoad() override; void NotifyStartLoad() override;
......
...@@ -406,6 +406,15 @@ void ImageResourceContent::NotifyStartLoad() { ...@@ -406,6 +406,15 @@ void ImageResourceContent::NotifyStartLoad() {
content_status_ = ResourceStatus::kPending; content_status_ = ResourceStatus::kPending;
} }
void ImageResourceContent::AsyncLoadCompleted(const blink::Image* image) {
if (image_ != image)
return;
CHECK_EQ(size_available_, Image::kSizeAvailableAndLoadingAsynchronously);
size_available_ = Image::kSizeAvailable;
UpdateToLoadedContentStatus(ResourceStatus::kCached);
NotifyObservers(kShouldNotifyFinish);
}
ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage( ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage(
PassRefPtr<SharedBuffer> data, PassRefPtr<SharedBuffer> data,
ResourceStatus status, ResourceStatus status,
...@@ -446,6 +455,9 @@ ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage( ...@@ -446,6 +455,9 @@ ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage(
image_ = CreateImage(); image_ = CreateImage();
DCHECK(image_); DCHECK(image_);
size_available_ = image_->SetData(std::move(data), all_data_received); size_available_ = image_->SetData(std::move(data), all_data_received);
DCHECK(all_data_received ||
size_available_ !=
Image::kSizeAvailableAndLoadingAsynchronously);
} }
// Go ahead and tell our observers to try to draw if we have either // Go ahead and tell our observers to try to draw if we have either
...@@ -469,11 +481,18 @@ ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage( ...@@ -469,11 +481,18 @@ ImageResourceContent::UpdateImageResult ImageResourceContent::UpdateImage(
break; break;
} }
DCHECK(all_data_received ||
size_available_ != Image::kSizeAvailableAndLoadingAsynchronously);
// Notifies the observers. // Notifies the observers.
// It would be nice to only redraw the decoded band of the image, but with the // It would be nice to only redraw the decoded band of the image, but with the
// current design (decoding delayed until painting) that seems hard. // current design (decoding delayed until painting) that seems hard.
//
if (all_data_received) { // In the case of kSizeAvailableAndLoadingAsynchronously, we are waiting for
// SVG image completion, and thus we notify observers of kDoNotNotifyFinish
// here, and will notify observers of finish later in AsyncLoadCompleted().
if (all_data_received &&
size_available_ != Image::kSizeAvailableAndLoadingAsynchronously) {
UpdateToLoadedContentStatus(status); UpdateToLoadedContentStatus(status);
NotifyObservers(kShouldNotifyFinish); NotifyObservers(kShouldNotifyFinish);
} else { } else {
......
...@@ -93,7 +93,7 @@ class CORE_EXPORT ImageResourceContent final ...@@ -93,7 +93,7 @@ class CORE_EXPORT ImageResourceContent final
void RemoveObserver(ImageResourceObserver*); void RemoveObserver(ImageResourceObserver*);
bool IsSizeAvailable() const { bool IsSizeAvailable() const {
return size_available_ == Image::kSizeAvailable; return size_available_ != Image::kSizeUnavailable;
} }
DECLARE_TRACE(); DECLARE_TRACE();
...@@ -189,6 +189,7 @@ class CORE_EXPORT ImageResourceContent final ...@@ -189,6 +189,7 @@ class CORE_EXPORT ImageResourceContent final
bool ShouldPauseAnimation(const blink::Image*) override; bool ShouldPauseAnimation(const blink::Image*) override;
void AnimationAdvanced(const blink::Image*) override; void AnimationAdvanced(const blink::Image*) override;
void ChangedInRect(const blink::Image*, const IntRect&) override; void ChangedInRect(const blink::Image*, const IntRect&) override;
void AsyncLoadCompleted(const blink::Image*) override;
PassRefPtr<Image> CreateImage(); PassRefPtr<Image> CreateImage();
void ClearImage(); void ClearImage();
......
...@@ -161,6 +161,15 @@ String GetTestFilePath() { ...@@ -161,6 +161,15 @@ String GetTestFilePath() {
return testing::WebTestDataPath("cancelTest.html"); return testing::WebTestDataPath("cancelTest.html");
} }
constexpr char kSvgImageWithSubresource[] =
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"198\" height=\"100\">"
"<style>"
" <![CDATA[@font-face{font-family:\"test\"; "
" src:url('data:font/ttf;base64,invalidFontData');}]]>"
"</style>"
"<text x=\"50\" y=\"50\" font-family=\"test\" font-size=\"16\">Fox</text>"
"</svg>";
void ReceiveResponse(ImageResource* image_resource, void ReceiveResponse(ImageResource* image_resource,
const KURL& url, const KURL& url,
const AtomicString& mime_type, const AtomicString& mime_type,
...@@ -773,6 +782,51 @@ TEST(ImageResourceTest, SVGImage) { ...@@ -773,6 +782,51 @@ TEST(ImageResourceTest, SVGImage) {
EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage()); EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage());
} }
TEST(ImageResourceTest, SVGImageWithSubresource) {
KURL url(kParsedURLString, "http://127.0.0.1:8000/foo");
ImageResource* image_resource = ImageResource::Create(ResourceRequest(url));
std::unique_ptr<MockImageResourceObserver> observer =
MockImageResourceObserver::Create(image_resource->GetContent());
ReceiveResponse(image_resource, url, "image/svg+xml",
kSvgImageWithSubresource, strlen(kSvgImageWithSubresource));
EXPECT_FALSE(image_resource->ErrorOccurred());
ASSERT_TRUE(image_resource->GetContent()->HasImage());
EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsBitmapImage());
// At this point, image is (mostly) available but the loading is not yet
// finished because of SVG's subresources, and thus ImageChanged() or
// ImageNotifyFinished() are not called.
EXPECT_EQ(ResourceStatus::kPending,
image_resource->GetContent()->GetContentStatus());
EXPECT_EQ(1, observer->ImageChangedCount());
EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width());
EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height());
// A new client added here shouldn't notified of finish.
std::unique_ptr<MockImageResourceObserver> observer2 =
MockImageResourceObserver::Create(image_resource->GetContent());
EXPECT_EQ(1, observer2->ImageChangedCount());
EXPECT_FALSE(observer2->ImageNotifyFinishedCalled());
// After asynchronous tasks are executed, the loading of SVG document is
// completed and ImageNotifyFinished() is called.
testing::RunPendingTasks();
EXPECT_EQ(ResourceStatus::kCached,
image_resource->GetContent()->GetContentStatus());
EXPECT_EQ(2, observer->ImageChangedCount());
EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
EXPECT_EQ(2, observer2->ImageChangedCount());
EXPECT_TRUE(observer2->ImageNotifyFinishedCalled());
EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width());
EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height());
GetMemoryCache()->EvictResources();
}
TEST(ImageResourceTest, SuccessfulRevalidationJpeg) { TEST(ImageResourceTest, SuccessfulRevalidationJpeg) {
KURL url(kParsedURLString, "http://127.0.0.1:8000/foo"); KURL url(kParsedURLString, "http://127.0.0.1:8000/foo");
ImageResource* image_resource = ImageResource::Create(ResourceRequest(url)); ImageResource* image_resource = ImageResource::Create(ResourceRequest(url));
......
...@@ -65,12 +65,39 @@ ...@@ -65,12 +65,39 @@
namespace blink { namespace blink {
// SVGImageLocalFrameClient is used to wait until SVG document's load event
// in the case where there are subresources asynchronously loaded.
//
// Reference cycle: SVGImage -(Persistent)-> Page -(Member)-> Frame -(Member)->
// FrameClient == SVGImageLocalFrameClient -(raw)-> SVGImage.
class SVGImage::SVGImageLocalFrameClient : public EmptyLocalFrameClient {
public:
SVGImageLocalFrameClient(SVGImage* image) : image_(image) {}
void ClearImage() { image_ = nullptr; }
private:
void DispatchDidHandleOnloadEvents() override {
// The SVGImage was destructed before SVG load completion.
if (!image_)
return;
image_->LoadCompleted();
}
// Cleared manually by SVGImage's destructor when |image_| is destructed.
SVGImage* image_;
};
SVGImage::SVGImage(ImageObserver* observer) SVGImage::SVGImage(ImageObserver* observer)
: Image(observer), : Image(observer),
paint_controller_(PaintController::Create()), paint_controller_(PaintController::Create()),
has_pending_timeline_rewind_(false) {} has_pending_timeline_rewind_(false) {}
SVGImage::~SVGImage() { SVGImage::~SVGImage() {
if (frame_client_)
frame_client_->ClearImage();
if (page_) { if (page_) {
// Store m_page in a local variable, clearing m_page, so that // Store m_page in a local variable, clearing m_page, so that
// SVGImageChromeClient knows we're destructed. // SVGImageChromeClient knows we're destructed.
...@@ -605,6 +632,25 @@ void SVGImage::UpdateUseCounters(const Document& document) const { ...@@ -605,6 +632,25 @@ void SVGImage::UpdateUseCounters(const Document& document) const {
} }
} }
void SVGImage::LoadCompleted() {
switch (load_state_) {
case kInDataChanged:
load_state_ = kLoadCompleted;
break;
case kWaitingForAsyncLoadCompletion:
load_state_ = kLoadCompleted;
if (GetImageObserver())
GetImageObserver()->AsyncLoadCompleted(this);
break;
case kDataChangedNotStarted:
case kLoadCompleted:
CHECK(false);
break;
}
}
Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) { Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) {
TRACE_EVENT0("blink", "SVGImage::dataChanged"); TRACE_EVENT0("blink", "SVGImage::dataChanged");
...@@ -612,60 +658,64 @@ Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) { ...@@ -612,60 +658,64 @@ Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) {
if (!Data()->size()) if (!Data()->size())
return kSizeAvailable; return kSizeAvailable;
if (all_data_received) { if (!all_data_received)
// SVGImage will fire events (and the default C++ handlers run) but doesn't return page_ ? kSizeAvailable : kSizeUnavailable;
// actually allow script to run so it's fine to call into it. We allow this
// since it means an SVG data url can synchronously load like other image CHECK(!page_);
// types.
EventDispatchForbiddenScope::AllowUserAgentEvents allow_user_agent_events; // SVGImage will fire events (and the default C++ handlers run) but doesn't
// actually allow script to run so it's fine to call into it. We allow this
DEFINE_STATIC_LOCAL(LocalFrameClient, dummy_local_frame_client, // since it means an SVG data url can synchronously load like other image
(EmptyLocalFrameClient::Create())); // types.
EventDispatchForbiddenScope::AllowUserAgentEvents allow_user_agent_events;
CHECK(!page_);
CHECK_EQ(load_state_, kDataChangedNotStarted);
Page::PageClients page_clients; load_state_ = kInDataChanged;
FillWithEmptyClients(page_clients);
chrome_client_ = SVGImageChromeClient::Create(this); Page::PageClients page_clients;
page_clients.chrome_client = chrome_client_.Get(); FillWithEmptyClients(page_clients);
chrome_client_ = SVGImageChromeClient::Create(this);
// FIXME: If this SVG ends up loading itself, we might leak the world. page_clients.chrome_client = chrome_client_.Get();
// The Cache code does not know about ImageResources holding Frames and
// won't know to break the cycle. // FIXME: If this SVG ends up loading itself, we might leak the world.
// This will become an issue when SVGImage will be able to load other // The Cache code does not know about ImageResources holding Frames and
// SVGImage objects, but we're safe now, because SVGImage can only be // won't know to break the cycle.
// loaded by a top-level document. // This will become an issue when SVGImage will be able to load other
Page* page; // SVGImage objects, but we're safe now, because SVGImage can only be
{ // loaded by a top-level document.
TRACE_EVENT0("blink", "SVGImage::dataChanged::createPage"); Page* page;
page = Page::Create(page_clients); {
page->GetSettings().SetScriptEnabled(false); TRACE_EVENT0("blink", "SVGImage::dataChanged::createPage");
page->GetSettings().SetPluginsEnabled(false); page = Page::Create(page_clients);
page->GetSettings().SetAcceleratedCompositingEnabled(false); page->GetSettings().SetScriptEnabled(false);
page->GetSettings().SetPluginsEnabled(false);
// Because this page is detached, it can't get default font settings page->GetSettings().SetAcceleratedCompositingEnabled(false);
// from the embedder. Copy over font settings so we have sensible
// defaults. These settings are fixed and will not update if changed. // Because this page is detached, it can't get default font settings
if (!Page::OrdinaryPages().IsEmpty()) { // from the embedder. Copy over font settings so we have sensible
Settings& default_settings = // defaults. These settings are fixed and will not update if changed.
(*Page::OrdinaryPages().begin())->GetSettings(); if (!Page::OrdinaryPages().IsEmpty()) {
page->GetSettings().GetGenericFontFamilySettings() = Settings& default_settings =
default_settings.GetGenericFontFamilySettings(); (*Page::OrdinaryPages().begin())->GetSettings();
page->GetSettings().SetMinimumFontSize( page->GetSettings().GetGenericFontFamilySettings() =
default_settings.GetMinimumFontSize()); default_settings.GetGenericFontFamilySettings();
page->GetSettings().SetMinimumLogicalFontSize( page->GetSettings().SetMinimumFontSize(
default_settings.GetMinimumLogicalFontSize()); default_settings.GetMinimumFontSize());
page->GetSettings().SetDefaultFontSize( page->GetSettings().SetMinimumLogicalFontSize(
default_settings.GetDefaultFontSize()); default_settings.GetMinimumLogicalFontSize());
page->GetSettings().SetDefaultFixedFontSize( page->GetSettings().SetDefaultFontSize(
default_settings.GetDefaultFixedFontSize()); default_settings.GetDefaultFontSize());
} page->GetSettings().SetDefaultFixedFontSize(
default_settings.GetDefaultFixedFontSize());
}
} }
LocalFrame* frame = nullptr; LocalFrame* frame = nullptr;
{ {
TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame"); TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame");
frame = LocalFrame::Create(&dummy_local_frame_client, *page, 0); DCHECK(!frame_client_);
frame_client_ = new SVGImageLocalFrameClient(this);
frame = LocalFrame::Create(frame_client_, *page, 0);
frame->SetView(FrameView::Create(*frame)); frame->SetView(FrameView::Create(*frame));
frame->Init(); frame->Init();
} }
...@@ -691,9 +741,24 @@ Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) { ...@@ -691,9 +741,24 @@ Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) {
// Set the concrete object size before a container size is available. // Set the concrete object size before a container size is available.
intrinsic_size_ = RoundedIntSize(ConcreteObjectSize(FloatSize( intrinsic_size_ = RoundedIntSize(ConcreteObjectSize(FloatSize(
LayoutReplaced::kDefaultWidth, LayoutReplaced::kDefaultHeight))); LayoutReplaced::kDefaultWidth, LayoutReplaced::kDefaultHeight)));
DCHECK(page_);
switch (load_state_) {
case kInDataChanged:
load_state_ = kWaitingForAsyncLoadCompletion;
return kSizeAvailableAndLoadingAsynchronously;
case kLoadCompleted:
return kSizeAvailable;
case kDataChangedNotStarted:
case kWaitingForAsyncLoadCompletion:
CHECK(false);
break;
} }
return page_ ? kSizeAvailable : kSizeUnavailable; NOTREACHED();
return kSizeAvailable;
} }
String SVGImage::FilenameExtension() const { String SVGImage::FilenameExtension() const {
......
...@@ -183,6 +183,10 @@ class CORE_EXPORT SVGImage final : public Image { ...@@ -183,6 +183,10 @@ class CORE_EXPORT SVGImage final : public Image {
void ScheduleTimelineRewind(); void ScheduleTimelineRewind();
void FlushPendingTimelineRewind(); void FlushPendingTimelineRewind();
void LoadCompleted();
class SVGImageLocalFrameClient;
Persistent<SVGImageChromeClient> chrome_client_; Persistent<SVGImageChromeClient> chrome_client_;
Persistent<Page> page_; Persistent<Page> page_;
std::unique_ptr<PaintController> paint_controller_; std::unique_ptr<PaintController> paint_controller_;
...@@ -194,6 +198,17 @@ class CORE_EXPORT SVGImage final : public Image { ...@@ -194,6 +198,17 @@ class CORE_EXPORT SVGImage final : public Image {
// the "concrete object size". For more, see: SVGImageForContainer.h // the "concrete object size". For more, see: SVGImageForContainer.h
IntSize intrinsic_size_; IntSize intrinsic_size_;
bool has_pending_timeline_rewind_; bool has_pending_timeline_rewind_;
enum LoadState {
kDataChangedNotStarted,
kInDataChanged,
kWaitingForAsyncLoadCompletion,
kLoadCompleted,
};
LoadState load_state_ = kDataChangedNotStarted;
Persistent<SVGImageLocalFrameClient> frame_client_;
}; };
DEFINE_IMAGE_TYPE_CASTS(SVGImage); DEFINE_IMAGE_TYPE_CASTS(SVGImage);
......
...@@ -55,6 +55,8 @@ class SVGImageTest : public ::testing::Test { ...@@ -55,6 +55,8 @@ class SVGImageTest : public ::testing::Test {
void ChangedInRect(const Image*, const IntRect&) override {} void ChangedInRect(const Image*, const IntRect&) override {}
void AsyncLoadCompleted(const blink::Image*) override {}
DEFINE_INLINE_VIRTUAL_TRACE() { ImageObserver::Trace(visitor); } DEFINE_INLINE_VIRTUAL_TRACE() { ImageObserver::Trace(visitor); }
private: private:
......
...@@ -59,6 +59,7 @@ class BitmapImageTest : public ::testing::Test { ...@@ -59,6 +59,7 @@ class BitmapImageTest : public ::testing::Test {
} }
bool ShouldPauseAnimation(const Image*) override { return false; } bool ShouldPauseAnimation(const Image*) override { return false; }
void AnimationAdvanced(const Image*) override {} void AnimationAdvanced(const Image*) override {}
void AsyncLoadCompleted(const Image*) override { NOTREACHED(); }
virtual void ChangedInRect(const Image*, const IntRect&) {} virtual void ChangedInRect(const Image*, const IntRect&) {}
......
...@@ -102,10 +102,22 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> { ...@@ -102,10 +102,22 @@ class PLATFORM_EXPORT Image : public ThreadSafeRefCounted<Image> {
int height() const { return Size().Height(); } int height() const { return Size().Height(); }
virtual bool GetHotSpot(IntPoint&) const { return false; } virtual bool GetHotSpot(IntPoint&) const { return false; }
enum SizeAvailability { kSizeAvailable, kSizeUnavailable }; enum SizeAvailability {
kSizeUnavailable,
kSizeAvailableAndLoadingAsynchronously,
kSizeAvailable,
};
// If SetData() returns |kSizeAvailableAndLoadingAsynchronously|:
// Image loading is continuing asynchronously
// (only when |this| is SVGImage and |all_data_received| is true), and
// ImageResourceObserver::AsyncLoadCompleted() is called when finished.
// Otherwise:
// Image loading is completed synchronously.
// ImageResourceObserver::AsyncLoadCompleted() is not called.
virtual SizeAvailability SetData(PassRefPtr<SharedBuffer> data, virtual SizeAvailability SetData(PassRefPtr<SharedBuffer> data,
bool all_data_received); bool all_data_received);
virtual SizeAvailability DataChanged(bool /*allDataReceived*/) { virtual SizeAvailability DataChanged(bool /*all_data_received*/) {
return kSizeUnavailable; return kSizeUnavailable;
} }
......
...@@ -48,6 +48,9 @@ class PLATFORM_EXPORT ImageObserver : public GarbageCollectedMixin { ...@@ -48,6 +48,9 @@ class PLATFORM_EXPORT ImageObserver : public GarbageCollectedMixin {
virtual void ChangedInRect(const Image*, const IntRect&) = 0; virtual void ChangedInRect(const Image*, const IntRect&) = 0;
// See the comment of Image::SetData().
virtual void AsyncLoadCompleted(const Image*) = 0;
DEFINE_INLINE_VIRTUAL_TRACE() {} DEFINE_INLINE_VIRTUAL_TRACE() {}
}; };
......
...@@ -263,7 +263,7 @@ class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>, ...@@ -263,7 +263,7 @@ class PLATFORM_EXPORT Resource : public GarbageCollectedFinalized<Resource>,
bool CanReuseRedirectChain() const; bool CanReuseRedirectChain() const;
bool MustRevalidateDueToCacheHeaders() const; bool MustRevalidateDueToCacheHeaders() const;
bool CanUseCacheValidator() const; virtual bool CanUseCacheValidator() const;
bool IsCacheValidator() const { return is_revalidating_; } bool IsCacheValidator() const { return is_revalidating_; }
bool HasCacheControlNoStoreHeader() const; bool HasCacheControlNoStoreHeader() const;
bool MustReloadDueToVaryHeader(const ResourceRequest& new_request) const; bool MustReloadDueToVaryHeader(const ResourceRequest& new_request) const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment