| // Copyright (c) 2012 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 "chrome/browser/ui/webui/theme_source.h" | 
 |  | 
 | #include "base/memory/ref_counted_memory.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/task/post_task.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "chrome/browser/resources_util.h" | 
 | #include "chrome/browser/search/instant_io_context.h" | 
 | #include "chrome/browser/themes/browser_theme_pack.h" | 
 | #include "chrome/browser/themes/theme_properties.h" | 
 | #include "chrome/browser/themes/theme_service.h" | 
 | #include "chrome/browser/themes/theme_service_factory.h" | 
 | #include "chrome/browser/ui/webui/ntp/ntp_resource_cache.h" | 
 | #include "chrome/browser/ui/webui/ntp/ntp_resource_cache_factory.h" | 
 | #include "chrome/common/channel_info.h" | 
 | #include "chrome/common/url_constants.h" | 
 | #include "chrome/grit/theme_resources.h" | 
 | #include "components/version_info/version_info.h" | 
 | #include "content/public/browser/browser_task_traits.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "net/url_request/url_request.h" | 
 | #include "ui/base/layout.h" | 
 | #include "ui/base/resource/resource_bundle.h" | 
 | #include "ui/base/webui/web_ui_util.h" | 
 | #include "ui/gfx/codec/png_codec.h" | 
 | #include "ui/gfx/image/image_skia.h" | 
 | #include "ui/gfx/image/image_skia_rep.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | namespace { | 
 |  | 
 | GURL GetThemeUrl(const std::string& path) { | 
 |   return GURL(std::string(content::kChromeUIScheme) + "://" + | 
 |               std::string(chrome::kChromeUIThemeHost) + "/" + path); | 
 | } | 
 |  | 
 | bool IsNewTabCssPath(const std::string& path) { | 
 |   static const char kNewTabCSSPath[] = "css/new_tab_theme.css"; | 
 |   static const char kIncognitoNewTabCSSPath[] = | 
 |       "css/incognito_new_tab_theme.css"; | 
 |   return (path == kNewTabCSSPath) || (path == kIncognitoNewTabCSSPath); | 
 | } | 
 |  | 
 | void ProcessImageOnUiThread(const gfx::ImageSkia& image, | 
 |                             float scale, | 
 |                             scoped_refptr<base::RefCountedBytes> data) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   const gfx::ImageSkiaRep& rep = image.GetRepresentation(scale); | 
 |   gfx::PNGCodec::EncodeBGRASkBitmap( | 
 |       rep.GetBitmap(), false /* discard transparency */, &data->data()); | 
 | } | 
 |  | 
 | void ProcessResourceOnUiThread(int resource_id, | 
 |                                float scale, | 
 |                                scoped_refptr<base::RefCountedBytes> data) { | 
 |   DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |   ProcessImageOnUiThread( | 
 |       *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id), | 
 |       scale, data); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | //////////////////////////////////////////////////////////////////////////////// | 
 | // ThemeSource, public: | 
 |  | 
 | ThemeSource::ThemeSource(Profile* profile) : profile_(profile) {} | 
 |  | 
 | ThemeSource::~ThemeSource() = default; | 
 |  | 
 | std::string ThemeSource::GetSource() const { | 
 |   return chrome::kChromeUIThemeHost; | 
 | } | 
 |  | 
 | void ThemeSource::StartDataRequest( | 
 |     const std::string& path, | 
 |     const content::ResourceRequestInfo::WebContentsGetter& wc_getter, | 
 |     const content::URLDataSource::GotDataCallback& callback) { | 
 |   // Default scale factor if not specified. | 
 |   float scale = 1.0f; | 
 |   // All frames by default if not specified. | 
 |   int frame = -1; | 
 |   std::string parsed_path; | 
 |   webui::ParsePathAndImageSpec(GetThemeUrl(path), &parsed_path, &scale, &frame); | 
 |  | 
 |   if (IsNewTabCssPath(parsed_path)) { | 
 |     DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |     NTPResourceCache::WindowType type = | 
 |         NTPResourceCache::GetWindowType(profile_, /*render_host=*/nullptr); | 
 |     NTPResourceCache* cache = NTPResourceCacheFactory::GetForProfile(profile_); | 
 |     callback.Run(cache->GetNewTabCSS(type)); | 
 |     return; | 
 |   } | 
 |  | 
 |   int resource_id = -1; | 
 |   if (parsed_path == "current-channel-logo") { | 
 |     switch (chrome::GetChannel()) { | 
 | #if defined(GOOGLE_CHROME_BUILD) | 
 |       case version_info::Channel::CANARY: | 
 |         resource_id = IDR_PRODUCT_LOGO_32_CANARY; | 
 |         break; | 
 |       case version_info::Channel::DEV: | 
 |         resource_id = IDR_PRODUCT_LOGO_32_DEV; | 
 |         break; | 
 |       case version_info::Channel::BETA: | 
 |         resource_id = IDR_PRODUCT_LOGO_32_BETA; | 
 |         break; | 
 |       case version_info::Channel::STABLE: | 
 |         resource_id = IDR_PRODUCT_LOGO_32; | 
 |         break; | 
 | #else | 
 |       case version_info::Channel::CANARY: | 
 |       case version_info::Channel::DEV: | 
 |       case version_info::Channel::BETA: | 
 |       case version_info::Channel::STABLE: | 
 |         NOTREACHED(); | 
 |         FALLTHROUGH; | 
 | #endif | 
 |       case version_info::Channel::UNKNOWN: | 
 |         resource_id = IDR_PRODUCT_LOGO_32; | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     resource_id = ResourcesUtil::GetThemeResourceId(parsed_path); | 
 |   } | 
 |  | 
 |   // Limit the maximum scale we'll respond to.  Very large scale factors can | 
 |   // take significant time to serve or, at worst, crash the browser due to OOM. | 
 |   // We don't want to clamp to the max scale factor, though, for devices that | 
 |   // use 2x scale without 2x data packs, as well as omnibox requests for larger | 
 |   // (but still reasonable) scales (see below). | 
 |   const float max_scale = ui::GetScaleForScaleFactor( | 
 |       ui::ResourceBundle::GetSharedInstance().GetMaxScaleFactor()); | 
 |   const float unreasonable_scale = max_scale * 32; | 
 |   // TODO(reveman): Add support frames beyond 0 (crbug.com/750064). | 
 |   if ((resource_id == -1) || (scale >= unreasonable_scale) || (frame > 0)) { | 
 |     // Either we have no data to send back, or the requested scale is | 
 |     // unreasonably large.  This shouldn't happen normally, as chrome://theme/ | 
 |     // URLs are only used by WebUI pages and component extensions.  However, the | 
 |     // user can also enter these into the omnibox, so we need to fail | 
 |     // gracefully. | 
 |     callback.Run(nullptr); | 
 |   } else if ((GetMimeType(path) == "image/png") && | 
 |              ((scale > max_scale) || (frame != -1))) { | 
 |     // This will extract and scale frame 0 of animated images. | 
 |     // TODO(reveman): Support scaling of animated images and avoid scaling and | 
 |     // re-encode when specific frame is specified (crbug.com/750064). | 
 |     DCHECK_LE(frame, 0); | 
 |     SendThemeImage(callback, resource_id, scale); | 
 |   } else { | 
 |     SendThemeBitmap(callback, resource_id, scale); | 
 |   } | 
 | } | 
 |  | 
 | std::string ThemeSource::GetMimeType(const std::string& path) const { | 
 |   std::string parsed_path; | 
 |   webui::ParsePathAndScale(GetThemeUrl(path), &parsed_path, nullptr); | 
 |   return IsNewTabCssPath(parsed_path) ? "text/css" : "image/png"; | 
 | } | 
 |  | 
 | scoped_refptr<base::SingleThreadTaskRunner> | 
 | ThemeSource::TaskRunnerForRequestPath(const std::string& path) const { | 
 |   std::string parsed_path; | 
 |   webui::ParsePathAndScale(GetThemeUrl(path), &parsed_path, nullptr); | 
 |  | 
 |   if (IsNewTabCssPath(parsed_path)) { | 
 |     // We'll get this data from the NTPResourceCache, which must be accessed on | 
 |     // the UI thread. | 
 |     return content::URLDataSource::TaskRunnerForRequestPath(path); | 
 |   } | 
 |  | 
 |   // If it's not a themeable image, we don't need to go to the UI thread. | 
 |   int resource_id = ResourcesUtil::GetThemeResourceId(parsed_path); | 
 |   return BrowserThemePack::IsPersistentImageID(resource_id) | 
 |              ? content::URLDataSource::TaskRunnerForRequestPath(path) | 
 |              : nullptr; | 
 | } | 
 |  | 
 | bool ThemeSource::AllowCaching() const { | 
 |   return false; | 
 | } | 
 |  | 
 | bool ThemeSource::ShouldServiceRequest( | 
 |     const GURL& url, | 
 |     content::ResourceContext* resource_context, | 
 |     int render_process_id) const { | 
 |   return url.SchemeIs(chrome::kChromeSearchScheme) | 
 |              ? InstantIOContext::ShouldServiceRequest(url, resource_context, | 
 |                                                       render_process_id) | 
 |              : URLDataSource::ShouldServiceRequest(url, resource_context, | 
 |                                                    render_process_id); | 
 | } | 
 |  | 
 | //////////////////////////////////////////////////////////////////////////////// | 
 | // ThemeSource, private: | 
 |  | 
 | void ThemeSource::SendThemeBitmap( | 
 |     const content::URLDataSource::GotDataCallback& callback, | 
 |     int resource_id, | 
 |     float scale) { | 
 |   ui::ScaleFactor scale_factor = ui::GetSupportedScaleFactor(scale); | 
 |   if (BrowserThemePack::IsPersistentImageID(resource_id)) { | 
 |     DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |     scoped_refptr<base::RefCountedMemory> image_data( | 
 |         ThemeService::GetThemeProviderForProfile(profile_->GetOriginalProfile()) | 
 |             .GetRawData(resource_id, scale_factor)); | 
 |     callback.Run(image_data.get()); | 
 |   } else { | 
 |     DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | 
 |     const ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 
 |     callback.Run(rb.LoadDataResourceBytesForScale(resource_id, scale_factor)); | 
 |   } | 
 | } | 
 |  | 
 | void ThemeSource::SendThemeImage( | 
 |     const content::URLDataSource::GotDataCallback& callback, | 
 |     int resource_id, | 
 |     float scale) { | 
 |   scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); | 
 |   if (BrowserThemePack::IsPersistentImageID(resource_id)) { | 
 |     DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
 |     const ui::ThemeProvider& tp = ThemeService::GetThemeProviderForProfile( | 
 |         profile_->GetOriginalProfile()); | 
 |     ProcessImageOnUiThread(*tp.GetImageSkiaNamed(resource_id), scale, data); | 
 |     callback.Run(data.get()); | 
 |   } else { | 
 |     DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | 
 |     // Fetching image data in ResourceBundle should happen on the UI thread. See | 
 |     // crbug.com/449277 | 
 |     base::PostTaskWithTraitsAndReply( | 
 |         FROM_HERE, {content::BrowserThread::UI}, | 
 |         base::BindOnce(&ProcessResourceOnUiThread, resource_id, scale, data), | 
 |         base::BindOnce(callback, data)); | 
 |   } | 
 | } |