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