extension_web_ui.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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/strings/string_util.h"
13#include "base/strings/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/extension_util.h"
18#include "chrome/browser/extensions/image_loader.h"
19#include "chrome/browser/favicon/favicon_util.h"
20#include "chrome/browser/prefs/scoped_user_pref_update.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/common/chrome_switches.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/extensions/manifest_handlers/icons_handler.h"
28#include "chrome/common/url_constants.h"
29#include "components/user_prefs/pref_registry_syncable.h"
30#include "content/public/browser/navigation_controller.h"
31#include "content/public/browser/web_contents.h"
32#include "content/public/browser/web_ui.h"
33#include "content/public/common/bindings_policy.h"
34#include "content/public/common/page_transition_types.h"
35#include "extensions/common/extension_resource.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
42using content::WebContents;
43using extensions::Extension;
44using extensions::URLOverrides;
45
46namespace {
47
48// De-dupes the items in |list|. Assumes the values are strings.
49void 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.
69void 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, WebKit::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.
87void 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
124const char ExtensionWebUI::kExtensionURLOverrides[] =
125    "extensions.chrome_url_overrides";
126
127ExtensionWebUI::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
157ExtensionWebUI::~ExtensionWebUI() {}
158
159extensions::BookmarkManagerPrivateEventRouter*
160ExtensionWebUI::bookmark_manager_private_event_router() {
161  return bookmark_manager_private_event_router_.get();
162}
163
164////////////////////////////////////////////////////////////////////////////////
165// chrome:// URL overrides
166
167// static
168void ExtensionWebUI::RegisterProfilePrefs(
169    user_prefs::PrefRegistrySyncable* registry) {
170  registry->RegisterDictionaryPref(
171      kExtensionURLOverrides,
172      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
173}
174
175// static
176bool 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
245bool 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
281void 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
324void 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    ExtensionTabUtil::ForEachTab(callback);
336  }
337}
338
339// static
340void 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
359void 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
383void 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