| // 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 "content/public/browser/manifest_icon_downloader.h" |
| |
| #include <stddef.h> |
| |
| #include <limits> |
| |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/manifest_icon_selector.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/console_message_level.h" |
| #include "skia/ext/image_operations.h" |
| |
| namespace content { |
| |
| // DevToolsConsoleHelper is a class that holds a WebContents in order to be able |
| // to send a message to the WebContents' main frame. It is used so |
| // ManifestIconDownloader and the callers do not have to worry about |
| // |web_contents| lifetime. If the |web_contents| is invalidated before the |
| // message can be sent, the message will simply be ignored. |
| class ManifestIconDownloader::DevToolsConsoleHelper |
| : public WebContentsObserver { |
| public: |
| explicit DevToolsConsoleHelper(WebContents* web_contents); |
| ~DevToolsConsoleHelper() override = default; |
| |
| void AddMessage(ConsoleMessageLevel level, const std::string& message); |
| }; |
| |
| ManifestIconDownloader::DevToolsConsoleHelper::DevToolsConsoleHelper( |
| WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| void ManifestIconDownloader::DevToolsConsoleHelper::AddMessage( |
| ConsoleMessageLevel level, |
| const std::string& message) { |
| if (!web_contents()) |
| return; |
| web_contents()->GetMainFrame()->AddMessageToConsole(level, message); |
| } |
| |
| bool ManifestIconDownloader::Download( |
| WebContents* web_contents, |
| const GURL& icon_url, |
| int ideal_icon_size_in_px, |
| int minimum_icon_size_in_px, |
| const ManifestIconDownloader::IconFetchCallback& callback) { |
| DCHECK(minimum_icon_size_in_px <= ideal_icon_size_in_px); |
| if (!web_contents || !icon_url.is_valid()) |
| return false; |
| |
| web_contents->DownloadImage( |
| icon_url, |
| false, // is_favicon |
| 0, // max_bitmap_size - 0 means no maximum size. |
| false, // bypass_cache |
| base::BindOnce(&ManifestIconDownloader::OnIconFetched, |
| ideal_icon_size_in_px, minimum_icon_size_in_px, |
| base::Owned(new DevToolsConsoleHelper(web_contents)), |
| callback)); |
| return true; |
| } |
| |
| void ManifestIconDownloader::OnIconFetched( |
| int ideal_icon_size_in_px, |
| int minimum_icon_size_in_px, |
| DevToolsConsoleHelper* console_helper, |
| const ManifestIconDownloader::IconFetchCallback& callback, |
| int id, |
| int http_status_code, |
| const GURL& url, |
| const std::vector<SkBitmap>& bitmaps, |
| const std::vector<gfx::Size>& sizes) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (bitmaps.empty()) { |
| console_helper->AddMessage( |
| CONSOLE_MESSAGE_LEVEL_ERROR, |
| "Error while trying to use the following icon from the Manifest: " + |
| url.spec() + " (Download error or resource isn't a valid image)"); |
| |
| callback.Run(SkBitmap()); |
| return; |
| } |
| |
| const int closest_index = FindClosestBitmapIndex( |
| ideal_icon_size_in_px, minimum_icon_size_in_px, bitmaps); |
| |
| if (closest_index == -1) { |
| console_helper->AddMessage( |
| CONSOLE_MESSAGE_LEVEL_ERROR, |
| "Error while trying to use the following icon from the Manifest: " + |
| url.spec() + |
| " (Resource size is not correct - typo in the Manifest?)"); |
| |
| callback.Run(SkBitmap()); |
| return; |
| } |
| |
| const SkBitmap& chosen = bitmaps[closest_index]; |
| |
| // Only scale if we need to scale down. For scaling up we will let the system |
| // handle that when it is required to display it. This saves space in the |
| // webapp storage system as well. |
| if (chosen.height() > ideal_icon_size_in_px || |
| chosen.width() > ideal_icon_size_in_px) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&ManifestIconDownloader::ScaleIcon, |
| ideal_icon_size_in_px, chosen, callback)); |
| return; |
| } |
| |
| callback.Run(chosen); |
| } |
| |
| void ManifestIconDownloader::ScaleIcon( |
| int ideal_icon_size_in_px, |
| const SkBitmap& bitmap, |
| const ManifestIconDownloader::IconFetchCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const SkBitmap& scaled = skia::ImageOperations::Resize( |
| bitmap, skia::ImageOperations::RESIZE_BEST, ideal_icon_size_in_px, |
| ideal_icon_size_in_px); |
| |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::BindOnce(callback, scaled)); |
| } |
| |
| int ManifestIconDownloader::FindClosestBitmapIndex( |
| int ideal_icon_size_in_px, |
| int minimum_icon_size_in_px, |
| const std::vector<SkBitmap>& bitmaps) { |
| int best_index = -1; |
| int best_delta = std::numeric_limits<int>::min(); |
| const int max_negative_delta = |
| minimum_icon_size_in_px - ideal_icon_size_in_px; |
| |
| for (size_t i = 0; i < bitmaps.size(); ++i) { |
| if (bitmaps[i].height() != bitmaps[i].width()) |
| continue; |
| |
| int delta = bitmaps[i].width() - ideal_icon_size_in_px; |
| if (delta == 0) |
| return i; |
| |
| if (best_delta > 0 && delta < 0) |
| continue; |
| |
| if ((best_delta > 0 && delta < best_delta) || |
| (best_delta < 0 && delta > best_delta && delta >= max_negative_delta)) { |
| best_index = i; |
| best_delta = delta; |
| } |
| } |
| |
| if (best_index != -1) |
| return best_index; |
| |
| // There was no square icon of a correct size found. Try to find the most |
| // square-like icon which has both dimensions greater than the minimum size. |
| float best_ratio_difference = std::numeric_limits<float>::infinity(); |
| for (size_t i = 0; i < bitmaps.size(); ++i) { |
| if (bitmaps[i].height() < minimum_icon_size_in_px || |
| bitmaps[i].width() < minimum_icon_size_in_px) { |
| continue; |
| } |
| |
| float height = static_cast<float>(bitmaps[i].height()); |
| float width = static_cast<float>(bitmaps[i].width()); |
| float ratio = height / width; |
| float ratio_difference = fabs(ratio - 1); |
| if (ratio_difference < best_ratio_difference) { |
| best_index = i; |
| best_ratio_difference = ratio_difference; |
| } |
| } |
| |
| return best_index; |
| } |
| |
| } // namespace content |