Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/extensions/extension_web_ui.h"
      6 
      7 #include <set>
      8 #include <vector>
      9 
     10 #include "base/command_line.h"
     11 #include "base/prefs/pref_service.h"
     12 #include "base/prefs/scoped_user_pref_update.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
     16 #include "chrome/browser/extensions/extension_service.h"
     17 #include "chrome/browser/extensions/extension_tab_util.h"
     18 #include "chrome/browser/extensions/extension_util.h"
     19 #include "chrome/browser/extensions/image_loader.h"
     20 #include "chrome/browser/favicon/favicon_util.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/common/chrome_switches.h"
     23 #include "chrome/common/extensions/extension_constants.h"
     24 #include "chrome/common/extensions/extension_icon_set.h"
     25 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     26 #include "chrome/common/url_constants.h"
     27 #include "components/user_prefs/pref_registry_syncable.h"
     28 #include "content/public/browser/navigation_controller.h"
     29 #include "content/public/browser/web_contents.h"
     30 #include "content/public/browser/web_ui.h"
     31 #include "content/public/common/bindings_policy.h"
     32 #include "content/public/common/page_transition_types.h"
     33 #include "extensions/common/extension.h"
     34 #include "extensions/common/extension_resource.h"
     35 #include "extensions/common/manifest_handlers/incognito_info.h"
     36 #include "net/base/file_stream.h"
     37 #include "third_party/skia/include/core/SkBitmap.h"
     38 #include "ui/gfx/codec/png_codec.h"
     39 #include "ui/gfx/favicon_size.h"
     40 #include "ui/gfx/image/image_skia.h"
     41 
     42 using content::WebContents;
     43 using extensions::Extension;
     44 using extensions::URLOverrides;
     45 
     46 namespace {
     47 
     48 // De-dupes the items in |list|. Assumes the values are strings.
     49 void CleanUpDuplicates(base::ListValue* list) {
     50   std::set<std::string> seen_values;
     51 
     52   // Loop backwards as we may be removing items.
     53   for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
     54     std::string value;
     55     if (!list->GetString(i, &value)) {
     56       NOTREACHED();
     57       continue;
     58     }
     59 
     60     if (seen_values.find(value) == seen_values.end())
     61       seen_values.insert(value);
     62     else
     63       list->Remove(i, NULL);
     64   }
     65 }
     66 
     67 // Reloads the page in |web_contents| if it uses the same profile as |profile|
     68 // and if the current URL is a chrome URL.
     69 void UnregisterAndReplaceOverrideForWebContents(
     70     const std::string& page, Profile* profile, WebContents* web_contents) {
     71   if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile)
     72     return;
     73 
     74   GURL url = web_contents->GetURL();
     75   if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page)
     76     return;
     77 
     78   // Don't use Reload() since |url| isn't the same as the internal URL that
     79   // NavigationController has.
     80   web_contents->GetController().LoadURL(
     81       url, content::Referrer(url, blink::WebReferrerPolicyDefault),
     82       content::PAGE_TRANSITION_RELOAD, std::string());
     83 }
     84 
     85 // Run favicon callbck with image result. If no favicon was available then
     86 // |image| will be empty.
     87 void RunFaviconCallbackAsync(
     88     const FaviconService::FaviconResultsCallback& callback,
     89     const gfx::Image& image) {
     90   std::vector<chrome::FaviconBitmapResult>* favicon_bitmap_results =
     91       new std::vector<chrome::FaviconBitmapResult>();
     92 
     93   const std::vector<gfx::ImageSkiaRep>& image_reps =
     94       image.AsImageSkia().image_reps();
     95   for (size_t i = 0; i < image_reps.size(); ++i) {
     96     const gfx::ImageSkiaRep& image_rep = image_reps[i];
     97     scoped_refptr<base::RefCountedBytes> bitmap_data(
     98         new base::RefCountedBytes());
     99     if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(),
    100                                           false,
    101                                           &bitmap_data->data())) {
    102       chrome::FaviconBitmapResult bitmap_result;
    103       bitmap_result.bitmap_data = bitmap_data;
    104       bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(),
    105                                             image_rep.pixel_height());
    106       // Leave |bitmap_result|'s icon URL as the default of GURL().
    107       bitmap_result.icon_type = chrome::FAVICON;
    108 
    109       favicon_bitmap_results->push_back(bitmap_result);
    110     } else {
    111       NOTREACHED() << "Could not encode extension favicon";
    112     }
    113   }
    114 
    115   base::MessageLoopProxy::current()->PostTask(
    116       FROM_HERE,
    117       base::Bind(&FaviconService::FaviconResultsCallbackRunner,
    118                  callback,
    119                  base::Owned(favicon_bitmap_results)));
    120 }
    121 
    122 }  // namespace
    123 
    124 const char ExtensionWebUI::kExtensionURLOverrides[] =
    125     "extensions.chrome_url_overrides";
    126 
    127 ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url)
    128     : WebUIController(web_ui),
    129       url_(url) {
    130   Profile* profile = Profile::FromWebUI(web_ui);
    131   ExtensionService* service = profile->GetExtensionService();
    132   const Extension* extension =
    133       service->extensions()->GetExtensionOrAppByURL(url);
    134   DCHECK(extension);
    135 
    136   // The base class defaults to enabling WebUI bindings, but we don't need
    137   // those (this is also reflected in ChromeWebUIControllerFactory::
    138   // UseWebUIBindingsForURL).
    139   int bindings = 0;
    140 
    141   // Bind externalHost to Extension WebUI loaded in Chrome Frame.
    142   const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
    143   if (browser_command_line.HasSwitch(switches::kChromeFrame))
    144     bindings |= content::BINDINGS_POLICY_EXTERNAL_HOST;
    145   web_ui->SetBindings(bindings);
    146 
    147   // Hack: A few things we specialize just for the bookmark manager.
    148   if (extension->id() == extension_misc::kBookmarkManagerId) {
    149     bookmark_manager_private_event_router_.reset(
    150         new extensions::BookmarkManagerPrivateEventRouter(
    151             profile, web_ui->GetWebContents()));
    152 
    153     web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK);
    154   }
    155 }
    156 
    157 ExtensionWebUI::~ExtensionWebUI() {}
    158 
    159 extensions::BookmarkManagerPrivateEventRouter*
    160 ExtensionWebUI::bookmark_manager_private_event_router() {
    161   return bookmark_manager_private_event_router_.get();
    162 }
    163 
    164 ////////////////////////////////////////////////////////////////////////////////
    165 // chrome:// URL overrides
    166 
    167 // static
    168 void ExtensionWebUI::RegisterProfilePrefs(
    169     user_prefs::PrefRegistrySyncable* registry) {
    170   registry->RegisterDictionaryPref(
    171       kExtensionURLOverrides,
    172       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    173 }
    174 
    175 // static
    176 bool ExtensionWebUI::HandleChromeURLOverride(
    177     GURL* url, content::BrowserContext* browser_context) {
    178   if (!url->SchemeIs(chrome::kChromeUIScheme))
    179     return false;
    180 
    181   Profile* profile = Profile::FromBrowserContext(browser_context);
    182   const base::DictionaryValue* overrides =
    183       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
    184   std::string page = url->host();
    185   const base::ListValue* url_list = NULL;
    186   if (!overrides || !overrides->GetList(page, &url_list))
    187     return false;
    188 
    189   ExtensionService* service = profile->GetExtensionService();
    190 
    191   size_t i = 0;
    192   while (i < url_list->GetSize()) {
    193     const Value* val = NULL;
    194     url_list->Get(i, &val);
    195 
    196     // Verify that the override value is good.  If not, unregister it and find
    197     // the next one.
    198     std::string override;
    199     if (!val->GetAsString(&override)) {
    200       NOTREACHED();
    201       UnregisterChromeURLOverride(page, profile, val);
    202       continue;
    203     }
    204 
    205     if (!url->query().empty())
    206       override += "?" + url->query();
    207     if (!url->ref().empty())
    208       override += "#" + url->ref();
    209     GURL extension_url(override);
    210     if (!extension_url.is_valid()) {
    211       NOTREACHED();
    212       UnregisterChromeURLOverride(page, profile, val);
    213       continue;
    214     }
    215 
    216     // Verify that the extension that's being referred to actually exists.
    217     const Extension* extension =
    218         service->extensions()->GetByID(extension_url.host());
    219     if (!extension) {
    220       // This can currently happen if you use --load-extension one run, and
    221       // then don't use it the next.  It could also happen if an extension
    222       // were deleted directly from the filesystem, etc.
    223       LOG(WARNING) << "chrome URL override present for non-existant extension";
    224       UnregisterChromeURLOverride(page, profile, val);
    225       continue;
    226     }
    227 
    228     // We can't handle chrome-extension URLs in incognito mode unless the
    229     // extension uses split mode.
    230     bool incognito_override_allowed =
    231         extensions::IncognitoInfo::IsSplitMode(extension) &&
    232         extension_util::IsIncognitoEnabled(extension->id(), service);
    233     if (profile->IsOffTheRecord() && !incognito_override_allowed) {
    234       ++i;
    235       continue;
    236     }
    237 
    238     *url = extension_url;
    239     return true;
    240   }
    241   return false;
    242 }
    243 
    244 // static
    245 bool ExtensionWebUI::HandleChromeURLOverrideReverse(
    246     GURL* url, content::BrowserContext* browser_context) {
    247   Profile* profile = Profile::FromBrowserContext(browser_context);
    248   const base::DictionaryValue* overrides =
    249       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
    250   if (!overrides)
    251     return false;
    252 
    253   // Find the reverse mapping based on the given URL. For example this maps the
    254   // internal URL
    255   // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to
    256   // chrome://bookmarks/#1 for display in the omnibox.
    257   for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd();
    258        it.Advance()) {
    259     const base::ListValue* url_list = NULL;
    260     if (!it.value().GetAsList(&url_list))
    261       continue;
    262 
    263     for (base::ListValue::const_iterator it2 = url_list->begin();
    264          it2 != url_list->end(); ++it2) {
    265       std::string override;
    266       if (!(*it2)->GetAsString(&override))
    267         continue;
    268       if (StartsWithASCII(url->spec(), override, true)) {
    269         GURL original_url(chrome::kChromeUIScheme + std::string("://") +
    270                           it.key() + url->spec().substr(override.length()));
    271         *url = original_url;
    272         return true;
    273       }
    274     }
    275   }
    276 
    277   return false;
    278 }
    279 
    280 // static
    281 void ExtensionWebUI::RegisterChromeURLOverrides(
    282     Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
    283   if (overrides.empty())
    284     return;
    285 
    286   PrefService* prefs = profile->GetPrefs();
    287   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    288   base::DictionaryValue* all_overrides = update.Get();
    289 
    290   // For each override provided by the extension, add it to the front of
    291   // the override list if it's not already in the list.
    292   URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
    293   for (; iter != overrides.end(); ++iter) {
    294     const std::string& key = iter->first;
    295     base::ListValue* page_overrides = NULL;
    296     if (!all_overrides->GetList(key, &page_overrides)) {
    297       page_overrides = new base::ListValue();
    298       all_overrides->Set(key, page_overrides);
    299     } else {
    300       CleanUpDuplicates(page_overrides);
    301 
    302       // Verify that the override isn't already in the list.
    303       base::ListValue::iterator i = page_overrides->begin();
    304       for (; i != page_overrides->end(); ++i) {
    305         std::string override_val;
    306         if (!(*i)->GetAsString(&override_val)) {
    307           NOTREACHED();
    308           continue;
    309         }
    310         if (override_val == iter->second.spec())
    311           break;
    312       }
    313       // This value is already in the list, leave it alone.
    314       if (i != page_overrides->end())
    315         continue;
    316     }
    317     // Insert the override at the front of the list.  Last registered override
    318     // wins.
    319     page_overrides->Insert(0, new StringValue(iter->second.spec()));
    320   }
    321 }
    322 
    323 // static
    324 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
    325                                                   Profile* profile,
    326                                                   base::ListValue* list,
    327                                                   const Value* override) {
    328   size_t index = 0;
    329   bool found = list->Remove(*override, &index);
    330   if (found && index == 0) {
    331     // This is the active override, so we need to find all existing
    332     // tabs for this override and get them to reload the original URL.
    333     base::Callback<void(WebContents*)> callback =
    334         base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile);
    335     extensions::ExtensionTabUtil::ForEachTab(callback);
    336   }
    337 }
    338 
    339 // static
    340 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
    341                                                  Profile* profile,
    342                                                  const Value* override) {
    343   if (!override)
    344     return;
    345   PrefService* prefs = profile->GetPrefs();
    346   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    347   base::DictionaryValue* all_overrides = update.Get();
    348   base::ListValue* page_overrides = NULL;
    349   if (!all_overrides->GetList(page, &page_overrides)) {
    350     // If it's being unregistered, it should already be in the list.
    351     NOTREACHED();
    352     return;
    353   } else {
    354     UnregisterAndReplaceOverride(page, profile, page_overrides, override);
    355   }
    356 }
    357 
    358 // static
    359 void ExtensionWebUI::UnregisterChromeURLOverrides(
    360     Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
    361   if (overrides.empty())
    362     return;
    363   PrefService* prefs = profile->GetPrefs();
    364   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    365   base::DictionaryValue* all_overrides = update.Get();
    366   URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
    367   for (; iter != overrides.end(); ++iter) {
    368     const std::string& page = iter->first;
    369     base::ListValue* page_overrides = NULL;
    370     if (!all_overrides->GetList(page, &page_overrides)) {
    371       // If it's being unregistered, it should already be in the list.
    372       NOTREACHED();
    373       continue;
    374     } else {
    375       StringValue override(iter->second.spec());
    376       UnregisterAndReplaceOverride(iter->first, profile,
    377                                    page_overrides, &override);
    378     }
    379   }
    380 }
    381 
    382 // static
    383 void ExtensionWebUI::GetFaviconForURL(
    384     Profile* profile,
    385     const GURL& page_url,
    386     const FaviconService::FaviconResultsCallback& callback) {
    387   // Even when the extensions service is enabled by default, it's still
    388   // disabled in incognito mode.
    389   ExtensionService* service = profile->GetExtensionService();
    390   if (!service) {
    391     RunFaviconCallbackAsync(callback, gfx::Image());
    392     return;
    393   }
    394   const Extension* extension = service->extensions()->GetByID(page_url.host());
    395   if (!extension) {
    396     RunFaviconCallbackAsync(callback, gfx::Image());
    397     return;
    398   }
    399 
    400   // Fetch resources for all supported scale factors for which there are
    401   // resources. Load image reps for all supported scale factors (in addition to
    402   // 1x) immediately instead of in an as needed fashion to be consistent with
    403   // how favicons are requested for chrome:// and page URLs.
    404   const std::vector<ui::ScaleFactor>& scale_factors =
    405       FaviconUtil::GetFaviconScaleFactors();
    406   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
    407   for (size_t i = 0; i < scale_factors.size(); ++i) {
    408     float scale = ui::GetImageScale(scale_factors[i]);
    409     int pixel_size = static_cast<int>(gfx::kFaviconSize * scale);
    410     extensions::ExtensionResource icon_resource =
    411         extensions::IconsInfo::GetIconResource(extension,
    412                                                pixel_size,
    413                                                ExtensionIconSet::MATCH_BIGGER);
    414 
    415     info_list.push_back(
    416         extensions::ImageLoader::ImageRepresentation(
    417             icon_resource,
    418             extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
    419             gfx::Size(pixel_size, pixel_size),
    420             scale_factors[i]));
    421   }
    422 
    423   // LoadImagesAsync actually can run callback synchronously. We want to force
    424   // async.
    425   extensions::ImageLoader::Get(profile)->LoadImagesAsync(
    426       extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback));
    427 }
    428