blob: 40a76b16a7bbd553099699b0be0cc0fb687a1745 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/notifications/NotificationImageLoader.h"
#include <memory>
#include "core/dom/ExecutionContext.h"
#include "platform/Histogram.h"
#include "platform/image-decoders/ImageDecoder.h"
#include "platform/image-decoders/ImageFrame.h"
#include "platform/loader/fetch/ResourceError.h"
#include "platform/loader/fetch/ResourceLoadPriority.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/loader/fetch/ResourceRequest.h"
#include "platform/weborigin/KURL.h"
#include "platform/wtf/CurrentTime.h"
#include "platform/wtf/Threading.h"
#include "public/platform/WebURLRequest.h"
#include "public/platform/modules/notifications/WebNotificationConstants.h"
#include "skia/ext/image_operations.h"
#define NOTIFICATION_PER_TYPE_HISTOGRAM_COUNTS(metric, type_name, value, max) \
case NotificationImageLoader::Type::k##type_name: { \
DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram, \
metric##type_name##Histogram, \
("Notifications." #metric "." #type_name, \
1 /* min */, max, 50 /* buckets */)); \
metric##type_name##Histogram.Count(value); \
break; \
}
#define NOTIFICATION_HISTOGRAM_COUNTS(metric, type, value, max) \
switch (type) { \
NOTIFICATION_PER_TYPE_HISTOGRAM_COUNTS(metric, Image, value, max) \
NOTIFICATION_PER_TYPE_HISTOGRAM_COUNTS(metric, Icon, value, max) \
NOTIFICATION_PER_TYPE_HISTOGRAM_COUNTS(metric, Badge, value, max) \
NOTIFICATION_PER_TYPE_HISTOGRAM_COUNTS(metric, ActionIcon, value, max) \
}
namespace {
// 99.9% of all images were fetched successfully in 90 seconds.
const unsigned long kImageFetchTimeoutInMs = 90000;
} // namespace
namespace blink {
NotificationImageLoader::NotificationImageLoader(Type type)
: type_(type), stopped_(false), start_time_(0.0) {}
NotificationImageLoader::~NotificationImageLoader() {}
// static
SkBitmap NotificationImageLoader::ScaleDownIfNeeded(const SkBitmap& image,
Type type) {
int max_width_px = 0, max_height_px = 0;
switch (type) {
case Type::kImage:
max_width_px = kWebNotificationMaxImageWidthPx;
max_height_px = kWebNotificationMaxImageHeightPx;
break;
case Type::kIcon:
max_width_px = kWebNotificationMaxIconSizePx;
max_height_px = kWebNotificationMaxIconSizePx;
break;
case Type::kBadge:
max_width_px = kWebNotificationMaxBadgeSizePx;
max_height_px = kWebNotificationMaxBadgeSizePx;
break;
case Type::kActionIcon:
max_width_px = kWebNotificationMaxActionIconSizePx;
max_height_px = kWebNotificationMaxActionIconSizePx;
break;
}
DCHECK_GT(max_width_px, 0);
DCHECK_GT(max_height_px, 0);
// TODO(peter): Explore doing the scaling on a background thread.
if (image.width() > max_width_px || image.height() > max_height_px) {
double scale =
std::min(static_cast<double>(max_width_px) / image.width(),
static_cast<double>(max_height_px) / image.height());
double start_time = MonotonicallyIncreasingTimeMS();
// TODO(peter): Try using RESIZE_BETTER for large images.
SkBitmap scaled_image =
skia::ImageOperations::Resize(image, skia::ImageOperations::RESIZE_BEST,
std::lround(scale * image.width()),
std::lround(scale * image.height()));
NOTIFICATION_HISTOGRAM_COUNTS(LoadScaleDownTime, type,
MonotonicallyIncreasingTimeMS() - start_time,
1000 * 10 /* 10 seconds max */);
return scaled_image;
}
return image;
}
void NotificationImageLoader::Start(
ExecutionContext* execution_context,
const KURL& url,
std::unique_ptr<ImageCallback> image_callback) {
DCHECK(!stopped_);
start_time_ = MonotonicallyIncreasingTimeMS();
image_callback_ = std::move(image_callback);
ThreadableLoaderOptions threadable_loader_options;
threadable_loader_options.fetch_request_mode =
WebURLRequest::kFetchRequestModeNoCORS;
threadable_loader_options.timeout_milliseconds = kImageFetchTimeoutInMs;
// TODO(mvanouwerkerk): Add an entry for notifications to
// FetchInitiatorTypeNames and use it.
ResourceLoaderOptions resource_loader_options;
if (execution_context->IsWorkerGlobalScope())
resource_loader_options.request_initiator_context = kWorkerContext;
ResourceRequest resource_request(url);
resource_request.SetFetchCredentialsMode(
WebURLRequest::kFetchCredentialsModeInclude);
resource_request.SetRequestContext(WebURLRequest::kRequestContextImage);
resource_request.SetPriority(kResourceLoadPriorityMedium);
resource_request.SetRequestorOrigin(execution_context->GetSecurityOrigin());
threadable_loader_ = ThreadableLoader::Create(*execution_context, this,
threadable_loader_options,
resource_loader_options);
threadable_loader_->Start(resource_request);
}
void NotificationImageLoader::Stop() {
if (stopped_)
return;
stopped_ = true;
if (threadable_loader_) {
threadable_loader_->Cancel();
threadable_loader_ = nullptr;
}
}
void NotificationImageLoader::DidReceiveData(const char* data,
unsigned length) {
if (!data_)
data_ = SharedBuffer::Create();
data_->Append(data, length);
}
void NotificationImageLoader::DidFinishLoading(
unsigned long resource_identifier,
double finish_time) {
// If this has been stopped it is not desirable to trigger further work,
// there is a shutdown of some sort in progress.
if (stopped_)
return;
NOTIFICATION_HISTOGRAM_COUNTS(LoadFinishTime, type_,
MonotonicallyIncreasingTimeMS() - start_time_,
1000 * 60 * 60 /* 1 hour max */);
if (data_) {
NOTIFICATION_HISTOGRAM_COUNTS(LoadFileSize, type_, data_->size(),
10000000 /* ~10mb max */);
std::unique_ptr<ImageDecoder> decoder = ImageDecoder::Create(
data_, true /* dataComplete */, ImageDecoder::kAlphaPremultiplied,
ColorBehavior::TransformToGlobalTarget());
if (decoder) {
// The |ImageFrame*| is owned by the decoder.
ImageFrame* image_frame = decoder->FrameBufferAtIndex(0);
if (image_frame) {
(*image_callback_)(image_frame->Bitmap());
return;
}
}
}
RunCallbackWithEmptyBitmap();
}
void NotificationImageLoader::DidFail(const ResourceError& error) {
NOTIFICATION_HISTOGRAM_COUNTS(LoadFailTime, type_,
MonotonicallyIncreasingTimeMS() - start_time_,
1000 * 60 * 60 /* 1 hour max */);
RunCallbackWithEmptyBitmap();
}
void NotificationImageLoader::DidFailRedirectCheck() {
RunCallbackWithEmptyBitmap();
}
void NotificationImageLoader::RunCallbackWithEmptyBitmap() {
// If this has been stopped it is not desirable to trigger further work,
// there is a shutdown of some sort in progress.
if (stopped_)
return;
(*image_callback_)(SkBitmap());
}
} // namespace blink