Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 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/string_util.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/extensions/extension_bookmark_manager_api.h"
     13 #include "chrome/browser/extensions/extension_service.h"
     14 #include "chrome/browser/extensions/image_loading_tracker.h"
     15 #include "chrome/browser/prefs/pref_service.h"
     16 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/browser.h"
     19 #include "chrome/browser/ui/browser_list.h"
     20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/common/extensions/extension.h"
     23 #include "chrome/common/extensions/extension_constants.h"
     24 #include "chrome/common/extensions/extension_icon_set.h"
     25 #include "chrome/common/extensions/extension_resource.h"
     26 #include "chrome/common/url_constants.h"
     27 #include "content/browser/renderer_host/render_widget_host_view.h"
     28 #include "content/browser/tab_contents/tab_contents.h"
     29 #include "content/common/bindings_policy.h"
     30 #include "content/common/page_transition_types.h"
     31 #include "net/base/file_stream.h"
     32 #include "third_party/skia/include/core/SkBitmap.h"
     33 #include "ui/gfx/codec/png_codec.h"
     34 #include "ui/gfx/favicon_size.h"
     35 
     36 namespace {
     37 
     38 // De-dupes the items in |list|. Assumes the values are strings.
     39 void CleanUpDuplicates(ListValue* list) {
     40   std::set<std::string> seen_values;
     41 
     42   // Loop backwards as we may be removing items.
     43   for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
     44     std::string value;
     45     if (!list->GetString(i, &value)) {
     46       NOTREACHED();
     47       continue;
     48     }
     49 
     50     if (seen_values.find(value) == seen_values.end())
     51       seen_values.insert(value);
     52     else
     53       list->Remove(i, NULL);
     54   }
     55 }
     56 
     57 // Helper class that is used to track the loading of the favicon of an
     58 // extension.
     59 class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer {
     60  public:
     61   ExtensionWebUIImageLoadingTracker(Profile* profile,
     62                                     FaviconService::GetFaviconRequest* request,
     63                                     const GURL& page_url)
     64       : ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)),
     65         request_(request),
     66         extension_(NULL) {
     67     // Even when the extensions service is enabled by default, it's still
     68     // disabled in incognito mode.
     69     ExtensionService* service = profile->GetExtensionService();
     70     if (service)
     71       extension_ = service->GetExtensionByURL(page_url);
     72   }
     73 
     74   void Init() {
     75     if (extension_) {
     76       ExtensionResource icon_resource =
     77           extension_->GetIconResource(Extension::EXTENSION_ICON_BITTY,
     78                                       ExtensionIconSet::MATCH_EXACTLY);
     79 
     80       tracker_.LoadImage(extension_, icon_resource,
     81                          gfx::Size(kFaviconSize, kFaviconSize),
     82                          ImageLoadingTracker::DONT_CACHE);
     83     } else {
     84       ForwardResult(NULL);
     85     }
     86   }
     87 
     88   virtual void OnImageLoaded(SkBitmap* image, const ExtensionResource& resource,
     89                              int index) {
     90     if (image) {
     91       std::vector<unsigned char> image_data;
     92       if (!gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_data)) {
     93         NOTREACHED() << "Could not encode extension favicon";
     94       }
     95       ForwardResult(RefCountedBytes::TakeVector(&image_data));
     96     } else {
     97       ForwardResult(NULL);
     98     }
     99   }
    100 
    101  private:
    102   ~ExtensionWebUIImageLoadingTracker() {}
    103 
    104   // Forwards the result on the request. If no favicon was available then
    105   // |icon_data| may be backed by NULL. Once the result has been forwarded the
    106   // instance is deleted.
    107   void ForwardResult(scoped_refptr<RefCountedMemory> icon_data) {
    108     history::FaviconData favicon;
    109     favicon.known_icon = icon_data.get() != NULL && icon_data->size() > 0;
    110     favicon.image_data = icon_data;
    111     favicon.icon_type = history::FAVICON;
    112     request_->ForwardResultAsync(
    113         FaviconService::FaviconDataCallback::TupleType(request_->handle(),
    114                                                        favicon));
    115     delete this;
    116   }
    117 
    118   ImageLoadingTracker tracker_;
    119   scoped_refptr<FaviconService::GetFaviconRequest> request_;
    120   const Extension* extension_;
    121 
    122   DISALLOW_COPY_AND_ASSIGN(ExtensionWebUIImageLoadingTracker);
    123 };
    124 
    125 }  // namespace
    126 
    127 const char ExtensionWebUI::kExtensionURLOverrides[] =
    128     "extensions.chrome_url_overrides";
    129 
    130 ExtensionWebUI::ExtensionWebUI(TabContents* tab_contents, const GURL& url)
    131     : WebUI(tab_contents),
    132       url_(url) {
    133   ExtensionService* service = tab_contents->profile()->GetExtensionService();
    134   const Extension* extension = service->GetExtensionByURL(url);
    135   if (!extension)
    136     extension = service->GetExtensionByWebExtent(url);
    137   DCHECK(extension);
    138   // Only hide the url for internal pages (e.g. chrome-extension or packaged
    139   // component apps like bookmark manager.
    140   should_hide_url_ = !extension->is_hosted_app();
    141 
    142   bindings_ = BindingsPolicy::EXTENSION;
    143   // Bind externalHost to Extension WebUI loaded in Chrome Frame.
    144   const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
    145   if (browser_command_line.HasSwitch(switches::kChromeFrame))
    146     bindings_ |= BindingsPolicy::EXTERNAL_HOST;
    147   // For chrome:// overrides, some of the defaults are a little different.
    148   GURL effective_url = tab_contents->GetURL();
    149   if (effective_url.SchemeIs(chrome::kChromeUIScheme) &&
    150       effective_url.host() == chrome::kChromeUINewTabHost) {
    151     focus_location_bar_by_default_ = true;
    152   }
    153 }
    154 
    155 ExtensionWebUI::~ExtensionWebUI() {}
    156 
    157 void ExtensionWebUI::ResetExtensionFunctionDispatcher(
    158     RenderViewHost* render_view_host) {
    159   // TODO(jcivelli): http://crbug.com/60608 we should get the URL out of the
    160   //                 active entry of the navigation controller.
    161   extension_function_dispatcher_.reset(
    162       ExtensionFunctionDispatcher::Create(render_view_host, this, url_));
    163   DCHECK(extension_function_dispatcher_.get());
    164 }
    165 
    166 void ExtensionWebUI::ResetExtensionBookmarkManagerEventRouter() {
    167   // Hack: A few things we specialize just for the bookmark manager.
    168   if (extension_function_dispatcher_->extension_id() ==
    169       extension_misc::kBookmarkManagerId) {
    170     extension_bookmark_manager_event_router_.reset(
    171         new ExtensionBookmarkManagerEventRouter(GetProfile(), tab_contents()));
    172 
    173     link_transition_type_ = PageTransition::AUTO_BOOKMARK;
    174   }
    175 }
    176 
    177 void ExtensionWebUI::RenderViewCreated(RenderViewHost* render_view_host) {
    178   ResetExtensionFunctionDispatcher(render_view_host);
    179   ResetExtensionBookmarkManagerEventRouter();
    180 }
    181 
    182 void ExtensionWebUI::RenderViewReused(RenderViewHost* render_view_host) {
    183   ResetExtensionFunctionDispatcher(render_view_host);
    184   ResetExtensionBookmarkManagerEventRouter();
    185 }
    186 
    187 void ExtensionWebUI::ProcessWebUIMessage(
    188     const ExtensionHostMsg_DomMessage_Params& params) {
    189   extension_function_dispatcher_->HandleRequest(params);
    190 }
    191 
    192 Browser* ExtensionWebUI::GetBrowser() {
    193   TabContents* contents = tab_contents();
    194   TabContentsIterator tab_iterator;
    195   for (; !tab_iterator.done(); ++tab_iterator) {
    196     if (contents == (*tab_iterator)->tab_contents())
    197       return tab_iterator.browser();
    198   }
    199 
    200   return NULL;
    201 }
    202 
    203 TabContents* ExtensionWebUI::associated_tab_contents() const {
    204   return tab_contents();
    205 }
    206 
    207 ExtensionBookmarkManagerEventRouter*
    208 ExtensionWebUI::extension_bookmark_manager_event_router() {
    209   return extension_bookmark_manager_event_router_.get();
    210 }
    211 
    212 gfx::NativeWindow ExtensionWebUI::GetCustomFrameNativeWindow() {
    213   if (GetBrowser())
    214     return NULL;
    215 
    216   // If there was no browser associated with the function dispatcher delegate,
    217   // then this WebUI may be hosted in an ExternalTabContainer, and a framing
    218   // window will be accessible through the tab_contents.
    219   TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate();
    220   if (tab_contents_delegate)
    221     return tab_contents_delegate->GetFrameNativeWindow();
    222   else
    223     return NULL;
    224 }
    225 
    226 gfx::NativeView ExtensionWebUI::GetNativeViewOfHost() {
    227   RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView();
    228   return rwhv ? rwhv->GetNativeView() : NULL;
    229 }
    230 
    231 ////////////////////////////////////////////////////////////////////////////////
    232 // chrome:// URL overrides
    233 
    234 // static
    235 void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) {
    236   prefs->RegisterDictionaryPref(kExtensionURLOverrides);
    237 }
    238 
    239 // static
    240 bool ExtensionWebUI::HandleChromeURLOverride(GURL* url, Profile* profile) {
    241   if (!url->SchemeIs(chrome::kChromeUIScheme))
    242     return false;
    243 
    244   const DictionaryValue* overrides =
    245       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
    246   std::string page = url->host();
    247   ListValue* url_list;
    248   if (!overrides || !overrides->GetList(page, &url_list))
    249     return false;
    250 
    251   ExtensionService* service = profile->GetExtensionService();
    252 
    253   size_t i = 0;
    254   while (i < url_list->GetSize()) {
    255     Value* val = NULL;
    256     url_list->Get(i, &val);
    257 
    258     // Verify that the override value is good.  If not, unregister it and find
    259     // the next one.
    260     std::string override;
    261     if (!val->GetAsString(&override)) {
    262       NOTREACHED();
    263       UnregisterChromeURLOverride(page, profile, val);
    264       continue;
    265     }
    266     GURL extension_url(override);
    267     if (!extension_url.is_valid()) {
    268       NOTREACHED();
    269       UnregisterChromeURLOverride(page, profile, val);
    270       continue;
    271     }
    272 
    273     // Verify that the extension that's being referred to actually exists.
    274     const Extension* extension = service->GetExtensionByURL(extension_url);
    275     if (!extension) {
    276       // This can currently happen if you use --load-extension one run, and
    277       // then don't use it the next.  It could also happen if an extension
    278       // were deleted directly from the filesystem, etc.
    279       LOG(WARNING) << "chrome URL override present for non-existant extension";
    280       UnregisterChromeURLOverride(page, profile, val);
    281       continue;
    282     }
    283 
    284     // We can't handle chrome-extension URLs in incognito mode unless the
    285     // extension uses split mode.
    286     bool incognito_override_allowed =
    287         extension->incognito_split_mode() &&
    288         service->IsIncognitoEnabled(extension->id());
    289     if (profile->IsOffTheRecord() && !incognito_override_allowed) {
    290       ++i;
    291       continue;
    292     }
    293 
    294     *url = extension_url;
    295     return true;
    296   }
    297   return false;
    298 }
    299 
    300 // static
    301 void ExtensionWebUI::RegisterChromeURLOverrides(
    302     Profile* profile, const Extension::URLOverrideMap& overrides) {
    303   if (overrides.empty())
    304     return;
    305 
    306   PrefService* prefs = profile->GetPrefs();
    307   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    308   DictionaryValue* all_overrides = update.Get();
    309 
    310   // For each override provided by the extension, add it to the front of
    311   // the override list if it's not already in the list.
    312   Extension::URLOverrideMap::const_iterator iter = overrides.begin();
    313   for (; iter != overrides.end(); ++iter) {
    314     const std::string& key = iter->first;
    315     ListValue* page_overrides;
    316     if (!all_overrides->GetList(key, &page_overrides)) {
    317       page_overrides = new ListValue();
    318       all_overrides->Set(key, page_overrides);
    319     } else {
    320       CleanUpDuplicates(page_overrides);
    321 
    322       // Verify that the override isn't already in the list.
    323       ListValue::iterator i = page_overrides->begin();
    324       for (; i != page_overrides->end(); ++i) {
    325         std::string override_val;
    326         if (!(*i)->GetAsString(&override_val)) {
    327           NOTREACHED();
    328           continue;
    329         }
    330         if (override_val == iter->second.spec())
    331           break;
    332       }
    333       // This value is already in the list, leave it alone.
    334       if (i != page_overrides->end())
    335         continue;
    336     }
    337     // Insert the override at the front of the list.  Last registered override
    338     // wins.
    339     page_overrides->Insert(0, new StringValue(iter->second.spec()));
    340   }
    341 }
    342 
    343 // static
    344 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
    345     Profile* profile, ListValue* list, Value* override) {
    346   int index = list->Remove(*override);
    347   if (index == 0) {
    348     // This is the active override, so we need to find all existing
    349     // tabs for this override and get them to reload the original URL.
    350     for (TabContentsIterator iterator; !iterator.done(); ++iterator) {
    351       TabContents* tab = (*iterator)->tab_contents();
    352       if (tab->profile() != profile)
    353         continue;
    354 
    355       GURL url = tab->GetURL();
    356       if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page)
    357         continue;
    358 
    359       // Don't use Reload() since |url| isn't the same as the internal URL
    360       // that NavigationController has.
    361       tab->controller().LoadURL(url, url, PageTransition::RELOAD);
    362     }
    363   }
    364 }
    365 
    366 // static
    367 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
    368     Profile* profile, Value* override) {
    369   if (!override)
    370     return;
    371   PrefService* prefs = profile->GetPrefs();
    372   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    373   DictionaryValue* all_overrides = update.Get();
    374   ListValue* page_overrides;
    375   if (!all_overrides->GetList(page, &page_overrides)) {
    376     // If it's being unregistered, it should already be in the list.
    377     NOTREACHED();
    378     return;
    379   } else {
    380     UnregisterAndReplaceOverride(page, profile, page_overrides, override);
    381   }
    382 }
    383 
    384 // static
    385 void ExtensionWebUI::UnregisterChromeURLOverrides(
    386     Profile* profile, const Extension::URLOverrideMap& overrides) {
    387   if (overrides.empty())
    388     return;
    389   PrefService* prefs = profile->GetPrefs();
    390   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
    391   DictionaryValue* all_overrides = update.Get();
    392   Extension::URLOverrideMap::const_iterator iter = overrides.begin();
    393   for (; iter != overrides.end(); ++iter) {
    394     const std::string& page = iter->first;
    395     ListValue* page_overrides;
    396     if (!all_overrides->GetList(page, &page_overrides)) {
    397       // If it's being unregistered, it should already be in the list.
    398       NOTREACHED();
    399       continue;
    400     } else {
    401       StringValue override(iter->second.spec());
    402       UnregisterAndReplaceOverride(iter->first, profile,
    403                                    page_overrides, &override);
    404     }
    405   }
    406 }
    407 
    408 // static
    409 void ExtensionWebUI::GetFaviconForURL(Profile* profile,
    410     FaviconService::GetFaviconRequest* request, const GURL& page_url) {
    411   // tracker deletes itself when done.
    412   ExtensionWebUIImageLoadingTracker* tracker =
    413       new ExtensionWebUIImageLoadingTracker(profile, request, page_url);
    414   tracker->Init();
    415 }
    416