| // Copyright 2014 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 "base/json/json_parser.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h" |
| #include "chrome/browser/enhanced_bookmarks/android/bookmark_image_service_android.h" |
| #include "chrome/browser/enhanced_bookmarks/enhanced_bookmark_model_factory.h" |
| #include "chrome/grit/browser_resources.h" |
| #include "chrome/renderer/chrome_isolated_world_ids.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/enhanced_bookmarks/enhanced_bookmark_model.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "grit/enhanced_bookmarks_resources.h" |
| #include "net/base/load_flags.h" |
| #include "skia/ext/image_operations.h" |
| #include "ui/base/device_form_factor.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/android/device_display_info.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| using content::Referrer; |
| using bookmarks::BookmarkNode; |
| |
| namespace enhanced_bookmarks { |
| |
| BookmarkImageServiceAndroid::BookmarkImageServiceAndroid( |
| content::BrowserContext* browserContext) |
| : BookmarkImageService( |
| browserContext->GetPath(), |
| EnhancedBookmarkModelFactory::GetForBrowserContext(browserContext), |
| make_scoped_refptr(content::BrowserThread::GetBlockingPool())), |
| browser_context_(browserContext) { |
| // The images we're saving will be used locally. So it's wasteful to store |
| // images larger than the device resolution. |
| gfx::DeviceDisplayInfo display_info; |
| int max_length = std::min(display_info.GetPhysicalDisplayWidth(), |
| display_info.GetPhysicalDisplayHeight()); |
| // GetPhysicalDisplay*() returns 0 for pre-JB MR1. If so, fall back to the |
| // second best option we have. |
| if (max_length == 0) { |
| max_length = std::min(display_info.GetDisplayWidth(), |
| display_info.GetDisplayHeight()); |
| } |
| |
| if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET) { |
| max_length = max_length / 2; |
| } else if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_PHONE) { |
| max_length = max_length * 3 / 4; |
| } |
| |
| DCHECK(max_length > 0); |
| max_size_.set_height(max_length); |
| max_size_.set_width(max_length); |
| } |
| |
| BookmarkImageServiceAndroid::~BookmarkImageServiceAndroid() { |
| } |
| |
| void BookmarkImageServiceAndroid::RetrieveSalientImage( |
| const GURL& page_url, |
| const GURL& image_url, |
| const std::string& referrer, |
| net::URLRequest::ReferrerPolicy referrer_policy, |
| bool update_bookmark) { |
| const BookmarkNode* bookmark = |
| enhanced_bookmark_model_->bookmark_model() |
| ->GetMostRecentlyAddedUserNodeForURL(page_url); |
| if (!bookmark || !image_url.is_valid()) { |
| ProcessNewImage(page_url, update_bookmark, image_url, |
| scoped_ptr<gfx::Image>(new gfx::Image())); |
| return; |
| } |
| |
| BitmapFetcherHandler* bitmap_fetcher_handler = |
| new BitmapFetcherHandler(this, image_url); |
| bitmap_fetcher_handler->Start( |
| browser_context_, referrer, referrer_policy, |
| net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES, |
| update_bookmark, page_url); |
| } |
| |
| void BookmarkImageServiceAndroid::RetrieveSalientImageFromContext( |
| content::WebContents* web_contents, |
| const GURL& page_url, |
| bool update_bookmark) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (IsPageUrlInProgress(page_url)) |
| return; // A request for this URL is already in progress. |
| |
| const BookmarkNode* bookmark = enhanced_bookmark_model_->bookmark_model() |
| ->GetMostRecentlyAddedUserNodeForURL(page_url); |
| if (!bookmark) |
| return; |
| |
| // Stop the image extraction if there is already an image present. |
| GURL url; |
| int height, width; |
| if (enhanced_bookmark_model_->GetOriginalImage(bookmark, &url, &width, |
| &height) || |
| enhanced_bookmark_model_->GetThumbnailImage(bookmark, &url, &width, |
| &height)) { |
| return; |
| } |
| |
| if (dom_initializer_script_.empty()) { |
| dom_initializer_script_ = |
| base::UTF8ToUTF16( |
| ResourceBundle::GetSharedInstance() |
| .GetRawDataResource(IDR_DOM_INITIALIZER_GEN_JS) |
| .as_string()); |
| } |
| |
| blink::WebReferrerPolicy policy = |
| web_contents->GetController().GetVisibleEntry()->GetReferrer().policy; |
| |
| content::RenderFrameHost* render_frame_host = web_contents->GetMainFrame(); |
| |
| render_frame_host->ExecuteJavaScriptInIsolatedWorld( |
| dom_initializer_script_, |
| base::Bind( |
| &BookmarkImageServiceAndroid::InitializeDomCallback, |
| base::Unretained(this), policy, render_frame_host, page_url, |
| update_bookmark), |
| chrome::ISOLATED_WORLD_ID_CHROME_INTERNAL); |
| } |
| |
| void BookmarkImageServiceAndroid::FinishSuccessfulPageLoadForTab( |
| content::WebContents* web_contents, bool update_bookmark) { |
| content::NavigationEntry* entry = |
| web_contents->GetController().GetVisibleEntry(); |
| |
| // If the navigation is a simple back or forward, do not extract images, those |
| // were extracted already. |
| if (!entry || (entry->GetTransitionType() & ui::PAGE_TRANSITION_FORWARD_BACK)) |
| return; |
| const GURL& entry_url = entry->GetURL(); |
| const GURL& entry_original_url = entry->GetOriginalRequestURL(); |
| std::vector<GURL> urls; |
| urls.push_back(entry_url); |
| if (entry_url != entry_original_url) |
| urls.push_back(entry_original_url); |
| for (GURL url : urls) { |
| if (enhanced_bookmark_model_->bookmark_model()->IsBookmarked(url)) { |
| RetrieveSalientImageFromContext(web_contents, url, |
| update_bookmark); |
| } |
| } |
| } |
| |
| void BookmarkImageServiceAndroid::InitializeDomCallback( |
| blink::WebReferrerPolicy policy, |
| content::RenderFrameHost* render_frame_host, |
| const GURL& page_url, |
| bool update_bookmark, |
| const base::Value* result) { |
| if (get_salient_image_url_script_.empty()) { |
| get_salient_image_url_script_ = |
| base::UTF8ToUTF16( |
| ResourceBundle::GetSharedInstance() |
| .GetRawDataResource(IDR_GET_SALIENT_IMAGE_URL_GEN_JS) |
| .as_string()); |
| } |
| |
| render_frame_host->ExecuteJavaScriptInIsolatedWorld( |
| get_salient_image_url_script_, |
| base::Bind( |
| &BookmarkImageServiceAndroid::RetrieveSalientImageFromContextCallback, |
| base::Unretained(this), policy, page_url, update_bookmark), |
| chrome::ISOLATED_WORLD_ID_CHROME_INTERNAL); |
| } |
| |
| void BookmarkImageServiceAndroid::RetrieveSalientImageFromContextCallback( |
| blink::WebReferrerPolicy policy, |
| const GURL& page_url, |
| bool update_bookmark, |
| const base::Value* result) { |
| if (!result) |
| return; |
| |
| std::string json; |
| if (!result->GetAsString(&json)) { |
| LOG(WARNING) |
| << "Salient image extracting script returned non-string result."; |
| return; |
| } |
| |
| int error_code = 0; |
| std::string error_message; |
| scoped_ptr<base::Value> json_data = base::JSONReader::ReadAndReturnError( |
| json, base::JSON_PARSE_RFC, &error_code, &error_message); |
| if (error_code || !json_data) { |
| LOG(WARNING) << "JSON parse error: " << error_message << json; |
| return; |
| } |
| |
| base::DictionaryValue* dict; |
| if (!json_data->GetAsDictionary(&dict)) { |
| LOG(WARNING) << "JSON parse error, not a dict: " << json; |
| return; |
| } |
| |
| std::string referrerPolicy; |
| std::string image_url; |
| dict->GetString("referrerPolicy", &referrerPolicy); |
| dict->GetString("imageUrl", &image_url); |
| |
| in_progress_page_urls_.insert(page_url); |
| |
| Referrer referrer = |
| Referrer::SanitizeForRequest(GURL(image_url), Referrer(page_url, policy)); |
| net::URLRequest::ReferrerPolicy referrer_policy = |
| net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE; |
| if (!referrer.url.is_empty()) { |
| switch (policy) { |
| case blink::WebReferrerPolicyDefault: |
| break; |
| case blink::WebReferrerPolicyAlways: |
| case blink::WebReferrerPolicyNever: |
| case blink::WebReferrerPolicyOrigin: |
| referrer_policy = net::URLRequest::NEVER_CLEAR_REFERRER; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| RetrieveSalientImage(page_url, GURL(image_url), referrer.url.spec(), |
| referrer_policy, update_bookmark); |
| } |
| |
| scoped_ptr<gfx::Image> BookmarkImageServiceAndroid::ResizeImage( |
| const gfx::Image& image) { |
| DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| scoped_ptr<gfx::Image> result(new gfx::Image()); |
| |
| if (image.Width() > max_size_.width() && |
| image.Height() > max_size_.height()) { |
| float resize_ratio = std::min( |
| ((float)max_size_.width()) / image.Width(), |
| ((float)max_size_.height()) / image.Height()); |
| // +0.5f is for correct rounding. Without it, it's possible that dest_width |
| // is smaller than max_size_.Width() by one. |
| int dest_width = static_cast<int>(resize_ratio * image.Width() + 0.5f); |
| int dest_height = static_cast<int>( |
| resize_ratio * image.Height() + 0.5f); |
| |
| *result = gfx::Image::CreateFrom1xBitmap( |
| skia::ImageOperations::Resize(image.AsBitmap(), |
| skia::ImageOperations::RESIZE_BEST, |
| dest_width, |
| dest_height)); |
| result->AsImageSkia().MakeThreadSafe(); |
| } else { |
| *result = image; |
| } |
| |
| return result.Pass(); |
| } |
| |
| void BookmarkImageServiceAndroid::BitmapFetcherHandler::Start( |
| content::BrowserContext* browser_context, |
| const std::string& referrer, |
| net::URLRequest::ReferrerPolicy referrer_policy, |
| int load_flags, |
| bool update_bookmark, |
| const GURL& page_url) { |
| update_bookmark_ = update_bookmark; |
| page_url_ = page_url; |
| |
| bitmap_fetcher_.Init(browser_context->GetRequestContext(), referrer, |
| referrer_policy, load_flags); |
| bitmap_fetcher_.Start(); |
| } |
| |
| void BookmarkImageServiceAndroid::BitmapFetcherHandler::OnFetchComplete( |
| const GURL& url, |
| const SkBitmap* bitmap) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| scoped_ptr<gfx::Image> image; |
| if (bitmap) { |
| gfx::ImageSkia imageSkia = gfx::ImageSkia::CreateFrom1xBitmap(*bitmap); |
| imageSkia.MakeThreadSafe(); |
| image.reset(new gfx::Image(imageSkia)); |
| } else { |
| image.reset(new gfx::Image()); |
| } |
| service_->ProcessNewImage(page_url_, update_bookmark_, url, image.Pass()); |
| |
| delete this; |
| } |
| |
| } // namespace enhanced_bookmarks |