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