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