Commit 4a03c28e authored by Peter Beverloo's avatar Peter Beverloo Committed by Commit Bot

Decode and resize Background Fetch icons on a background thread

This is currently done on the thread that invokes fetch(), i.e. either
the main or the worker thread. Especially now that we're going to resize
the image to the desired size too, let's move that to the background.

Per this CL we also resize the icon to the display size indicated by the
browser process.

Change-Id: Iee082c67f9c6b4903b931f95c40142cd5f77b853
Reviewed-on: https://chromium-review.googlesource.com/1166965
Commit-Queue: Peter Beverloo <peter@chromium.org>
Reviewed-by: default avatarRayan Kanso <rayankans@chromium.org>
Cr-Commit-Position: refs/heads/master@{#582200}
parent 00e27ae1
...@@ -13,12 +13,15 @@ ...@@ -13,12 +13,15 @@
#include "third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h" #include "third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h"
#include "third_party/blink/renderer/modules/manifest/image_resource.h" #include "third_party/blink/renderer/modules/manifest/image_resource.h"
#include "third_party/blink/renderer/modules/manifest/image_resource_type_converters.h" #include "third_party/blink/renderer/modules/manifest/image_resource_type_converters.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/graphics/color_behavior.h" #include "third_party/blink/renderer/platform/graphics/color_behavior.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h" #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" #include "third_party/blink/renderer/platform/image-decoders/image_frame.h"
#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/scheduler/public/background_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/string_impl.h" #include "third_party/blink/renderer/platform/wtf/text/string_impl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
...@@ -28,8 +31,13 @@ namespace blink { ...@@ -28,8 +31,13 @@ namespace blink {
namespace { namespace {
const unsigned long kIconFetchTimeoutInMs = 30000; constexpr unsigned long kIconFetchTimeoutInMs = 30000;
const int kMinimumIconSizeInPx = 0; constexpr int kMinimumIconSizeInPx = 0;
// Because including base::ClampToRange would be a dependency violation.
int ClampToRange(const int value, const int min, const int max) {
return std::min(std::max(value, min), max);
}
} // namespace } // namespace
...@@ -58,13 +66,16 @@ void BackgroundFetchIconLoader::DidGetIconDisplaySizeIfSoLoadIcon( ...@@ -58,13 +66,16 @@ void BackgroundFetchIconLoader::DidGetIconDisplaySizeIfSoLoadIcon(
ExecutionContext* execution_context, ExecutionContext* execution_context,
IconCallback icon_callback, IconCallback icon_callback,
const WebSize& icon_display_size_pixels) { const WebSize& icon_display_size_pixels) {
if (icon_display_size_pixels.IsEmpty()) { icon_display_size_pixels_ = icon_display_size_pixels;
// If |icon_display_size_pixels_| is empty then no image will be displayed by
// the UI powering Background Fetch. Bail out immediately.
if (icon_display_size_pixels_.IsEmpty()) {
std::move(icon_callback).Run(SkBitmap()); std::move(icon_callback).Run(SkBitmap());
return; return;
} }
KURL best_icon_url = KURL best_icon_url = PickBestIconForDisplay(execution_context);
PickBestIconForDisplay(execution_context, icon_display_size_pixels);
if (best_icon_url.IsEmpty()) { if (best_icon_url.IsEmpty()) {
// None of the icons provided was suitable. // None of the icons provided was suitable.
std::move(icon_callback).Run(SkBitmap()); std::move(icon_callback).Run(SkBitmap());
...@@ -90,8 +101,7 @@ void BackgroundFetchIconLoader::DidGetIconDisplaySizeIfSoLoadIcon( ...@@ -90,8 +101,7 @@ void BackgroundFetchIconLoader::DidGetIconDisplaySizeIfSoLoadIcon(
} }
KURL BackgroundFetchIconLoader::PickBestIconForDisplay( KURL BackgroundFetchIconLoader::PickBestIconForDisplay(
ExecutionContext* execution_context, ExecutionContext* execution_context) {
const WebSize& icon_display_size_pixels) {
std::vector<Manifest::ImageResource> icons; std::vector<Manifest::ImageResource> icons;
for (auto& icon : icons_) { for (auto& icon : icons_) {
// Update the src of |icon| to include the base URL in case relative paths // Update the src of |icon| to include the base URL in case relative paths
...@@ -102,7 +112,7 @@ KURL BackgroundFetchIconLoader::PickBestIconForDisplay( ...@@ -102,7 +112,7 @@ KURL BackgroundFetchIconLoader::PickBestIconForDisplay(
// TODO(crbug.com/868875): Handle cases where `sizes` or `purpose` is empty. // TODO(crbug.com/868875): Handle cases where `sizes` or `purpose` is empty.
return KURL(ManifestIconSelector::FindBestMatchingIcon( return KURL(ManifestIconSelector::FindBestMatchingIcon(
std::move(icons), icon_display_size_pixels.height, kMinimumIconSizeInPx, std::move(icons), icon_display_size_pixels_.height, kMinimumIconSizeInPx,
Manifest::ImageResource::Purpose::ANY)); Manifest::ImageResource::Purpose::ANY));
} }
...@@ -128,22 +138,69 @@ void BackgroundFetchIconLoader::DidFinishLoading( ...@@ -128,22 +138,69 @@ void BackgroundFetchIconLoader::DidFinishLoading(
unsigned long resource_identifier) { unsigned long resource_identifier) {
if (stopped_) if (stopped_)
return; return;
if (data_) {
// Decode data. if (!data_) {
const bool data_complete = true; RunCallbackWithEmptyBitmap();
return;
}
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
Platform::Current()->CurrentThread()->GetTaskRunner();
BackgroundScheduler::PostOnBackgroundThread(
FROM_HERE,
CrossThreadBind(
&BackgroundFetchIconLoader::DecodeAndResizeImageOnBackgroundThread,
WrapCrossThreadPersistent(this), std::move(task_runner),
SegmentReader::CreateFromSharedBuffer(std::move(data_))));
}
void BackgroundFetchIconLoader::DecodeAndResizeImageOnBackgroundThread(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
scoped_refptr<SegmentReader> data) {
DCHECK(task_runner);
DCHECK(data);
// Explicitly pass in the |icon_display_size_pixels_| to benefit from decoders
// that have optimizations for partial decoding.
std::unique_ptr<ImageDecoder> decoder = ImageDecoder::Create( std::unique_ptr<ImageDecoder> decoder = ImageDecoder::Create(
data_, data_complete, ImageDecoder::kAlphaPremultiplied, std::move(data), /* data_complete= */ true,
ImageDecoder::kDefaultBitDepth, ColorBehavior::TransformToSRGB()); ImageDecoder::kAlphaPremultiplied, ImageDecoder::kDefaultBitDepth,
ColorBehavior::TransformToSRGB(),
{icon_display_size_pixels_.width, icon_display_size_pixels_.height});
if (decoder) { if (decoder) {
// the |ImageFrame*| is owned by the decoder.
ImageFrame* image_frame = decoder->DecodeFrameBufferAtIndex(0); ImageFrame* image_frame = decoder->DecodeFrameBufferAtIndex(0);
if (image_frame) { if (image_frame) {
std::move(icon_callback_).Run(image_frame->Bitmap()); decoded_icon_ = image_frame->Bitmap();
return;
int width = decoded_icon_.width();
int height = decoded_icon_.height();
// If the |decoded_icon_| is larger than |icon_display_size_pixels_|
// permits, we need to resize it as well. This can be done synchronously
// given that we're on a background thread already.
double scale = std::min(
static_cast<double>(icon_display_size_pixels_.width) / width,
static_cast<double>(icon_display_size_pixels_.height) / height);
if (scale < 1) {
width = ClampToRange(scale * width, 1, icon_display_size_pixels_.width);
height =
ClampToRange(scale * height, 1, icon_display_size_pixels_.height);
// Use the RESIZE_GOOD quality allowing the implementation to pick an
// appropriate method for the resize. Can be increased to RESIZE_BETTER
// or RESIZE_BEST if the quality looks poor.
decoded_icon_ = skia::ImageOperations::Resize(
decoded_icon_, skia::ImageOperations::RESIZE_GOOD, width, height);
} }
} }
} }
RunCallbackWithEmptyBitmap();
PostCrossThreadTask(*task_runner, FROM_HERE,
CrossThreadBind(&BackgroundFetchIconLoader::RunCallback,
WrapCrossThreadPersistent(this)));
} }
void BackgroundFetchIconLoader::DidFail(const ResourceError& error) { void BackgroundFetchIconLoader::DidFail(const ResourceError& error) {
...@@ -154,13 +211,18 @@ void BackgroundFetchIconLoader::DidFailRedirectCheck() { ...@@ -154,13 +211,18 @@ void BackgroundFetchIconLoader::DidFailRedirectCheck() {
RunCallbackWithEmptyBitmap(); RunCallbackWithEmptyBitmap();
} }
void BackgroundFetchIconLoader::RunCallbackWithEmptyBitmap() { void BackgroundFetchIconLoader::RunCallback() {
// If this has been stopped it is not desirable to trigger further work, // If this has been stopped it is not desirable to trigger further work,
// there is a shutdown of some sort in progress. // there is a shutdown of some sort in progress.
if (stopped_) if (stopped_)
return; return;
std::move(icon_callback_).Run(SkBitmap()); std::move(icon_callback_).Run(decoded_icon_);
}
void BackgroundFetchIconLoader::RunCallbackWithEmptyBitmap() {
DCHECK(decoded_icon_.isNull());
RunCallback();
} }
} // namespace blink } // namespace blink
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
namespace blink { namespace blink {
class BackgroundFetchBridge; class BackgroundFetchBridge;
class SegmentReader;
struct WebSize; struct WebSize;
class MODULES_EXPORT BackgroundFetchIconLoader final class MODULES_EXPORT BackgroundFetchIconLoader final
...@@ -30,16 +31,12 @@ class MODULES_EXPORT BackgroundFetchIconLoader final ...@@ -30,16 +31,12 @@ class MODULES_EXPORT BackgroundFetchIconLoader final
BackgroundFetchIconLoader(); BackgroundFetchIconLoader();
~BackgroundFetchIconLoader() override; ~BackgroundFetchIconLoader() override;
// Scales down |icon| and returns result. If it is already small enough,
// |icon| is returned unchanged.
static SkBitmap ScaleDownIfNeeded(const SkBitmap& icon);
// Asynchronously download an icon from the given url, decodes the loaded // Asynchronously download an icon from the given url, decodes the loaded
// data, and passes the bitmap to the given callback. // data, and passes the bitmap to the given callback.
void Start(BackgroundFetchBridge* bridge, void Start(BackgroundFetchBridge* bridge,
ExecutionContext* execution_context, ExecutionContext* execution_context,
HeapVector<ManifestImageResource> icons, HeapVector<ManifestImageResource> icons,
IconCallback callback); IconCallback icon_callback);
// Cancels the pending load, if there is one. The |icon_callback_| will not // Cancels the pending load, if there is one. The |icon_callback_| will not
// be run. // be run.
...@@ -58,6 +55,8 @@ class MODULES_EXPORT BackgroundFetchIconLoader final ...@@ -58,6 +55,8 @@ class MODULES_EXPORT BackgroundFetchIconLoader final
private: private:
friend class BackgroundFetchIconLoaderTest; friend class BackgroundFetchIconLoaderTest;
void RunCallback();
void RunCallbackWithEmptyBitmap(); void RunCallbackWithEmptyBitmap();
// Callback for BackgroundFetchBridge::GetIconDisplaySize() // Callback for BackgroundFetchBridge::GetIconDisplaySize()
...@@ -67,16 +66,37 @@ class MODULES_EXPORT BackgroundFetchIconLoader final ...@@ -67,16 +66,37 @@ class MODULES_EXPORT BackgroundFetchIconLoader final
const WebSize& icon_display_size_pixels); const WebSize& icon_display_size_pixels);
// Picks the best icon from the list of developer provided icons, for current // Picks the best icon from the list of developer provided icons, for current
// display, given the ideal |icon_display_size_pixels|, and returns its KURL. // display, given the ideal |icon_display_size_pixels_|, and returns its KURL.
// If none of the icons is appropriate, this returns an empty URL. // If none of the icons is appropriate, this returns an empty URL.
KURL PickBestIconForDisplay(ExecutionContext* execution_context, KURL PickBestIconForDisplay(ExecutionContext* execution_context);
const WebSize& icon_display_size_pixels);
// Decodes the |data| to a SkBitmap using the image decoders and resizes it to
// be no larger than |icon_display_size_pixels_|.
void DecodeAndResizeImageOnBackgroundThread(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
scoped_refptr<SegmentReader> data);
// Called when the image has been decoded and resized on a background thread.
void DidFinishDecoding();
bool stopped_ = false;
scoped_refptr<SharedBuffer> data_;
IconCallback icon_callback_;
HeapVector<ManifestImageResource> icons_; HeapVector<ManifestImageResource> icons_;
IconCallback icon_callback_;
Member<ThreadableLoader> threadable_loader_; Member<ThreadableLoader> threadable_loader_;
// Size at which the icon will be presented to the user.
WebSize icon_display_size_pixels_;
// Data received from the ThreadableLoader. Will be invalidated when decoding
// of the image data starts.
scoped_refptr<SharedBuffer> data_;
// Result of decoding the icon on the background thread.
SkBitmap decoded_icon_;
// Whether the icon loading operation has been stopped. The process should
// be aborted then, and |icon_callback_| must not be invoked anymore.
bool stopped_ = false;
}; };
} // namespace blink } // namespace blink
......
...@@ -64,11 +64,15 @@ class BackgroundFetchIconLoaderTest : public PageTestBase { ...@@ -64,11 +64,15 @@ class BackgroundFetchIconLoaderTest : public PageTestBase {
// Callback for BackgroundFetchIconLoader. This will set up the state of the // Callback for BackgroundFetchIconLoader. This will set up the state of the
// load as either success or failed based on whether the bitmap is empty. // load as either success or failed based on whether the bitmap is empty.
void IconLoaded(const SkBitmap& bitmap) { void IconLoaded(base::OnceClosure quit_closure, const SkBitmap& bitmap) {
if (!bitmap.empty()) bitmap_ = bitmap;
if (!bitmap_.isNull())
loaded_ = BackgroundFetchLoadState::kLoadSuccessful; loaded_ = BackgroundFetchLoadState::kLoadSuccessful;
else else
loaded_ = BackgroundFetchLoadState::kLoadFailed; loaded_ = BackgroundFetchLoadState::kLoadFailed;
std::move(quit_closure).Run();
} }
ManifestImageResource CreateTestIcon(const String& url_str, ManifestImageResource CreateTestIcon(const String& url_str,
...@@ -84,10 +88,14 @@ class BackgroundFetchIconLoaderTest : public PageTestBase { ...@@ -84,10 +88,14 @@ class BackgroundFetchIconLoaderTest : public PageTestBase {
KURL PickRightIcon(HeapVector<ManifestImageResource> icons, KURL PickRightIcon(HeapVector<ManifestImageResource> icons,
const WebSize& ideal_display_size) { const WebSize& ideal_display_size) {
loader_->icons_ = std::move(icons); loader_->icons_ = std::move(icons);
return loader_->PickBestIconForDisplay(GetContext(), ideal_display_size); loader_->icon_display_size_pixels_ = ideal_display_size;
return loader_->PickBestIconForDisplay(GetContext());
} }
void LoadIcon(const KURL& url) { void LoadIcon(const KURL& url,
const WebSize& maximum_size,
base::OnceClosure quit_closure) {
ManifestImageResource icon; ManifestImageResource icon;
icon.setSrc(url.GetString()); icon.setSrc(url.GetString());
icon.setType("image/png"); icon.setType("image/png");
...@@ -97,8 +105,9 @@ class BackgroundFetchIconLoaderTest : public PageTestBase { ...@@ -97,8 +105,9 @@ class BackgroundFetchIconLoaderTest : public PageTestBase {
loader_->icons_ = std::move(icons); loader_->icons_ = std::move(icons);
loader_->DidGetIconDisplaySizeIfSoLoadIcon( loader_->DidGetIconDisplaySizeIfSoLoadIcon(
GetContext(), GetContext(),
Bind(&BackgroundFetchIconLoaderTest::IconLoaded, WTF::Unretained(this)), WTF::Bind(&BackgroundFetchIconLoaderTest::IconLoaded,
WebSize(192, 192)); WTF::Unretained(this), WTF::Passed(std::move(quit_closure))),
maximum_size);
} }
ExecutionContext* GetContext() const { return &GetDocument(); } ExecutionContext* GetContext() const { return &GetDocument(); }
...@@ -106,15 +115,30 @@ class BackgroundFetchIconLoaderTest : public PageTestBase { ...@@ -106,15 +115,30 @@ class BackgroundFetchIconLoaderTest : public PageTestBase {
protected: protected:
ScopedTestingPlatformSupport<TestingPlatformSupport> platform_; ScopedTestingPlatformSupport<TestingPlatformSupport> platform_;
BackgroundFetchLoadState loaded_ = BackgroundFetchLoadState::kNotLoaded; BackgroundFetchLoadState loaded_ = BackgroundFetchLoadState::kNotLoaded;
SkBitmap bitmap_;
private: private:
Persistent<BackgroundFetchIconLoader> loader_; Persistent<BackgroundFetchIconLoader> loader_;
}; };
TEST_F(BackgroundFetchIconLoaderTest, SuccessTest) { TEST_F(BackgroundFetchIconLoaderTest, SuccessTest) {
LoadIcon(KURL(kBackgroundFetchImageLoaderIcon500x500FullPath)); base::RunLoop run_loop;
WebSize maximum_size{192, 168};
LoadIcon(KURL(kBackgroundFetchImageLoaderIcon500x500FullPath), maximum_size,
run_loop.QuitClosure());
platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); platform_->GetURLLoaderMockFactory()->ServeAsynchronousRequests();
EXPECT_EQ(BackgroundFetchLoadState::kLoadSuccessful, loaded_);
run_loop.Run();
ASSERT_EQ(BackgroundFetchLoadState::kLoadSuccessful, loaded_);
ASSERT_FALSE(bitmap_.drawsNothing());
// Resizing a 500x500 image to fit on a canvas of 192x168 pixels should yield
// a decoded image size of 168x168, avoiding image data to get lost.
EXPECT_EQ(bitmap_.width(), 168);
EXPECT_EQ(bitmap_.height(), 168);
} }
TEST_F(BackgroundFetchIconLoaderTest, PickIconRelativePath) { TEST_F(BackgroundFetchIconLoaderTest, PickIconRelativePath) {
......
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