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