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