| // Copyright 2015 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 "third_party/blink/renderer/modules/image_downloader/image_downloader_impl.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "skia/ext/image_operations.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/public/platform/interface_registry.h" |
| #include "third_party/blink/public/platform/web_data.h" |
| #include "third_party/blink/public/platform/web_size.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/public/platform/web_vector.h" |
| #include "third_party/blink/public/web/web_image.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/modules/image_downloader/multi_resolution_image_resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/heap/visitor.h" |
| #include "third_party/blink/renderer/platform/network/network_utils.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace { |
| |
| WTF::Vector<SkBitmap> DecodeImageData(const std::string& data, |
| const std::string& mime_type, |
| const blink::WebSize& preferred_size) { |
| // Decode the image using Blink's image decoder. |
| blink::WebData buffer(data.data(), data.size()); |
| WTF::Vector<SkBitmap> bitmaps; |
| if (mime_type == "image/svg+xml") { |
| SkBitmap bitmap = blink::WebImage::DecodeSVG(buffer, preferred_size); |
| if (!bitmap.drawsNothing()) |
| bitmaps.push_back(bitmap); |
| } else { |
| blink::WebVector<SkBitmap> original_bitmaps = |
| blink::WebImage::FramesFromData(buffer); |
| bitmaps.AppendRange(std::make_move_iterator(original_bitmaps.begin()), |
| std::make_move_iterator(original_bitmaps.end())); |
| bitmaps.Reverse(); |
| } |
| return bitmaps; |
| } |
| |
| // Decodes a data: URL into one or more images, or no images in case of failure. |
| WTF::Vector<SkBitmap> ImagesFromDataUrl(const blink::KURL& url, |
| const blink::WebSize& preferred_size) { |
| std::string mime_type, data; |
| if (!blink::network_utils::IsDataURLMimeTypeSupported(url, &data, |
| &mime_type) || |
| data.empty()) |
| return WTF::Vector<SkBitmap>(); |
| return DecodeImageData(data, mime_type, preferred_size); |
| } |
| |
| // Proportionally resizes the |image| to fit in a box of size |
| // |max_image_size|. |
| SkBitmap ResizeImage(const SkBitmap& image, uint32_t max_image_size) { |
| if (max_image_size == 0) |
| return image; |
| uint32_t max_dimension = std::max(image.width(), image.height()); |
| if (max_dimension <= max_image_size) |
| return image; |
| // Proportionally resize the minimal image to fit in a box of size |
| // max_image_size. |
| return skia::ImageOperations::Resize( |
| image, skia::ImageOperations::RESIZE_BEST, |
| static_cast<uint32_t>(image.width()) * max_image_size / max_dimension, |
| static_cast<uint32_t>(image.height()) * max_image_size / max_dimension); |
| } |
| |
| // Filters the array of bitmaps, removing all images that do not fit in a box of |
| // size |max_image_size|. Returns the result if it is not empty. Otherwise, |
| // find the smallest image in the array and resize it proportionally to fit |
| // in a box of size |max_image_size|. |
| // Sets |original_image_sizes| to the sizes of |images| before resizing. Both |
| // output vectors are guaranteed to have the same size. |
| void FilterAndResizeImagesForMaximalSize( |
| const WTF::Vector<SkBitmap>& unfiltered, |
| uint32_t max_image_size, |
| WTF::Vector<SkBitmap>* images, |
| WTF::Vector<gfx::Size>* original_image_sizes) { |
| images->clear(); |
| original_image_sizes->clear(); |
| |
| if (unfiltered.IsEmpty()) |
| return; |
| |
| if (max_image_size == 0) |
| max_image_size = std::numeric_limits<uint32_t>::max(); |
| |
| const SkBitmap* min_image = nullptr; |
| uint32_t min_image_size = std::numeric_limits<uint32_t>::max(); |
| // Filter the images by |max_image_size|, and also identify the smallest image |
| // in case all the images are bigger than |max_image_size|. |
| for (auto* it = unfiltered.begin(); it != unfiltered.end(); ++it) { |
| const SkBitmap& image = *it; |
| uint32_t current_size = std::max(it->width(), it->height()); |
| if (current_size < min_image_size) { |
| min_image = ℑ |
| min_image_size = current_size; |
| } |
| if (static_cast<uint32_t>(image.width()) <= max_image_size && |
| static_cast<uint32_t>(image.height()) <= max_image_size) { |
| images->push_back(image); |
| original_image_sizes->push_back(gfx::Size(image.width(), image.height())); |
| } |
| } |
| DCHECK(min_image); |
| if (images->size()) |
| return; |
| // Proportionally resize the minimal image to fit in a box of size |
| // |max_image_size|. |
| SkBitmap resized = ResizeImage(*min_image, max_image_size); |
| // Drop null or empty SkBitmap. |
| if (resized.drawsNothing()) |
| return; |
| images->push_back(resized); |
| original_image_sizes->push_back( |
| gfx::Size(min_image->width(), min_image->height())); |
| } |
| |
| } // namespace |
| |
| namespace blink { |
| |
| // static |
| const char ImageDownloaderImpl::kSupplementName[] = "ImageDownloader"; |
| |
| // static |
| ImageDownloaderImpl* ImageDownloaderImpl::From(LocalFrame& frame) { |
| return Supplement<LocalFrame>::From<ImageDownloaderImpl>(frame); |
| } |
| |
| // static |
| void ImageDownloaderImpl::ProvideTo(LocalFrame& frame) { |
| if (ImageDownloaderImpl::From(frame)) |
| return; |
| Supplement<LocalFrame>::ProvideTo( |
| frame, MakeGarbageCollected<ImageDownloaderImpl>(frame)); |
| } |
| |
| ImageDownloaderImpl::ImageDownloaderImpl(LocalFrame& frame) |
| : Supplement<LocalFrame>(frame), |
| ExecutionContextLifecycleObserver(frame.DomWindow()), |
| receiver_(this, frame.DomWindow()) { |
| frame.GetInterfaceRegistry()->AddInterface(WTF::BindRepeating( |
| &ImageDownloaderImpl::CreateMojoService, WrapWeakPersistent(this))); |
| } |
| |
| ImageDownloaderImpl::~ImageDownloaderImpl() {} |
| |
| void ImageDownloaderImpl::CreateMojoService( |
| mojo::PendingReceiver<mojom::blink::ImageDownloader> receiver) { |
| receiver_.Bind(std::move(receiver), |
| GetSupplementable()->GetTaskRunner(TaskType::kNetworking)); |
| receiver_.set_disconnect_handler( |
| WTF::Bind(&ImageDownloaderImpl::Dispose, WrapWeakPersistent(this))); |
| } |
| |
| // ImageDownloader methods: |
| void ImageDownloaderImpl::DownloadImage(const KURL& image_url, |
| bool is_favicon, |
| uint32_t preferred_size, |
| uint32_t max_bitmap_size, |
| bool bypass_cache, |
| DownloadImageCallback callback) { |
| // Constrain the preferred size by the max bitmap size. This will prevent |
| // resizing of the resulting image if the preferred size is used. |
| if (max_bitmap_size) |
| preferred_size = std::min(preferred_size, max_bitmap_size); |
| |
| auto download_callback = |
| WTF::Bind(&ImageDownloaderImpl::DidDownloadImage, WrapPersistent(this), |
| max_bitmap_size, std::move(callback)); |
| |
| const WebSize preferred_dimensions(preferred_size, preferred_size); |
| if (!image_url.ProtocolIsData()) { |
| FetchImage(image_url, is_favicon, preferred_dimensions, bypass_cache, |
| std::move(download_callback)); |
| // Will complete asynchronously via ImageDownloaderImpl::DidFetchImage. |
| return; |
| } |
| |
| WTF::Vector<SkBitmap> result_images = |
| ImagesFromDataUrl(image_url, preferred_dimensions); |
| std::move(download_callback).Run(0, result_images); |
| } |
| |
| void ImageDownloaderImpl::DidDownloadImage( |
| uint32_t max_image_size, |
| DownloadImageCallback callback, |
| int32_t http_status_code, |
| const WTF::Vector<SkBitmap>& images) { |
| WTF::Vector<SkBitmap> result_images; |
| WTF::Vector<gfx::Size> result_original_image_sizes; |
| FilterAndResizeImagesForMaximalSize(images, max_image_size, &result_images, |
| &result_original_image_sizes); |
| |
| DCHECK_EQ(result_images.size(), result_original_image_sizes.size()); |
| |
| std::move(callback).Run(http_status_code, result_images, |
| result_original_image_sizes); |
| } |
| |
| void ImageDownloaderImpl::Dispose() { |
| receiver_.reset(); |
| } |
| |
| void ImageDownloaderImpl::FetchImage(const KURL& image_url, |
| bool is_favicon, |
| const WebSize& preferred_size, |
| bool bypass_cache, |
| DownloadCallback callback) { |
| // Create an image resource fetcher and assign it with a call back object. |
| image_fetchers_.push_back( |
| std::make_unique<MultiResolutionImageResourceFetcher>( |
| image_url, GetSupplementable(), is_favicon, |
| bypass_cache ? blink::mojom::FetchCacheMode::kBypassCache |
| : blink::mojom::FetchCacheMode::kDefault, |
| WTF::Bind(&ImageDownloaderImpl::DidFetchImage, WrapPersistent(this), |
| std::move(callback), preferred_size))); |
| } |
| |
| void ImageDownloaderImpl::DidFetchImage( |
| DownloadCallback callback, |
| const WebSize& preferred_size, |
| MultiResolutionImageResourceFetcher* fetcher, |
| const std::string& image_data, |
| const WebString& mime_type) { |
| int32_t http_status_code = fetcher->http_status_code(); |
| |
| Vector<SkBitmap> images = |
| DecodeImageData(image_data, mime_type.Utf8(), preferred_size); |
| |
| // Remove the image fetcher from our pending list. We're in the callback from |
| // MultiResolutionImageResourceFetcher, best to delay deletion. |
| for (auto* it = image_fetchers_.begin(); it != image_fetchers_.end(); ++it) { |
| MultiResolutionImageResourceFetcher* image_fetcher = it->get(); |
| DCHECK(image_fetcher); |
| if (image_fetcher == fetcher) { |
| it = image_fetchers_.erase(it); |
| break; |
| } |
| } |
| |
| // |this| may be destructed after callback is run. |
| std::move(callback).Run(http_status_code, images); |
| } |
| |
| void ImageDownloaderImpl::Trace(Visitor* visitor) const { |
| visitor->Trace(receiver_); |
| Supplement<LocalFrame>::Trace(visitor); |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| } |
| |
| void ImageDownloaderImpl::ContextDestroyed() { |
| for (const auto& fetcher : image_fetchers_) { |
| // Will run callbacks with an empty image vector. |
| fetcher->Dispose(); |
| } |
| image_fetchers_.clear(); |
| } |
| |
| } // namespace blink |