Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / extensions / extension_web_ui.cc
blob2ad1696c8d72c03149370a701b1ed71f4bb37787
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.
5 #include "chrome/browser/extensions/extension_web_ui.h"
7 #include <set>
8 #include <vector>
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 "base/thread_task_runner_handle.h"
16 #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
17 #include "chrome/browser/extensions/extension_tab_util.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/extensions/extension_constants.h"
22 #include "chrome/common/url_constants.h"
23 #include "components/favicon/core/favicon_service.h"
24 #include "components/favicon_base/favicon_util.h"
25 #include "components/pref_registry/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 "extensions/browser/extension_registry.h"
31 #include "extensions/browser/image_loader.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/base/page_transition_types.h"
40 #include "ui/gfx/codec/png_codec.h"
41 #include "ui/gfx/favicon_size.h"
42 #include "ui/gfx/image/image_skia.h"
44 using content::WebContents;
45 using extensions::Extension;
46 using extensions::URLOverrides;
48 namespace {
50 // De-dupes the items in |list|. Assumes the values are strings.
51 void CleanUpDuplicates(base::ListValue* list) {
52 std::set<std::string> seen_values;
54 // Loop backwards as we may be removing items.
55 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
56 std::string value;
57 if (!list->GetString(i, &value)) {
58 NOTREACHED();
59 continue;
62 if (seen_values.find(value) == seen_values.end())
63 seen_values.insert(value);
64 else
65 list->Remove(i, NULL);
69 // Reloads the page in |web_contents| if it uses the same profile as |profile|
70 // and if the current URL is a chrome URL.
71 void UnregisterAndReplaceOverrideForWebContents(const std::string& page,
72 Profile* profile,
73 WebContents* web_contents) {
74 if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile)
75 return;
77 GURL url = web_contents->GetURL();
78 if (!url.SchemeIs(content::kChromeUIScheme) || url.host() != page)
79 return;
81 // Don't use Reload() since |url| isn't the same as the internal URL that
82 // NavigationController has.
83 web_contents->GetController().LoadURL(
84 url, content::Referrer::SanitizeForRequest(
85 url, content::Referrer(url, blink::WebReferrerPolicyDefault)),
86 ui::PAGE_TRANSITION_RELOAD, std::string());
89 // Run favicon callbck with image result. If no favicon was available then
90 // |image| will be empty.
91 void RunFaviconCallbackAsync(
92 const favicon_base::FaviconResultsCallback& callback,
93 const gfx::Image& image) {
94 std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results =
95 new std::vector<favicon_base::FaviconRawBitmapResult>();
97 const std::vector<gfx::ImageSkiaRep>& image_reps =
98 image.AsImageSkia().image_reps();
99 for (size_t i = 0; i < image_reps.size(); ++i) {
100 const gfx::ImageSkiaRep& image_rep = image_reps[i];
101 scoped_refptr<base::RefCountedBytes> bitmap_data(
102 new base::RefCountedBytes());
103 if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(),
104 false,
105 &bitmap_data->data())) {
106 favicon_base::FaviconRawBitmapResult bitmap_result;
107 bitmap_result.bitmap_data = bitmap_data;
108 bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(),
109 image_rep.pixel_height());
110 // Leave |bitmap_result|'s icon URL as the default of GURL().
111 bitmap_result.icon_type = favicon_base::FAVICON;
113 favicon_bitmap_results->push_back(bitmap_result);
114 } else {
115 NOTREACHED() << "Could not encode extension favicon";
119 base::ThreadTaskRunnerHandle::Get()->PostTask(
120 FROM_HERE,
121 base::Bind(&favicon::FaviconService::FaviconResultsCallbackRunner,
122 callback, base::Owned(favicon_bitmap_results)));
125 bool ValidateOverrideURL(const base::Value* override_url_value,
126 const GURL& source_url,
127 const extensions::ExtensionSet& extensions,
128 GURL* override_url,
129 const Extension** extension) {
130 std::string override;
131 if (!override_url_value || !override_url_value->GetAsString(&override)) {
132 return false;
134 if (!source_url.query().empty())
135 override += "?" + source_url.query();
136 if (!source_url.ref().empty())
137 override += "#" + source_url.ref();
138 *override_url = GURL(override);
139 if (!override_url->is_valid()) {
140 return false;
142 *extension = extensions.GetByID(override_url->host());
143 if (!*extension) {
144 return false;
146 return true;
149 } // namespace
151 const char ExtensionWebUI::kExtensionURLOverrides[] =
152 "extensions.chrome_url_overrides";
154 ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url)
155 : WebUIController(web_ui),
156 url_(url) {
157 Profile* profile = Profile::FromWebUI(web_ui);
158 const Extension* extension = extensions::ExtensionRegistry::Get(
159 profile)->enabled_extensions().GetExtensionOrAppByURL(url);
160 DCHECK(extension);
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);
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()));
174 web_ui->SetLinkTransitionType(ui::PAGE_TRANSITION_AUTO_BOOKMARK);
178 ExtensionWebUI::~ExtensionWebUI() {}
180 extensions::BookmarkManagerPrivateDragEventRouter*
181 ExtensionWebUI::bookmark_manager_private_drag_event_router() {
182 return bookmark_manager_private_drag_event_router_.get();
185 ////////////////////////////////////////////////////////////////////////////////
186 // chrome:// URL overrides
188 // static
189 void ExtensionWebUI::RegisterProfilePrefs(
190 user_prefs::PrefRegistrySyncable* registry) {
191 registry->RegisterDictionaryPref(kExtensionURLOverrides);
194 // static
195 bool ExtensionWebUI::HandleChromeURLOverride(
196 GURL* url,
197 content::BrowserContext* browser_context) {
198 if (!url->SchemeIs(content::kChromeUIScheme))
199 return false;
201 Profile* profile = Profile::FromBrowserContext(browser_context);
202 const base::DictionaryValue* overrides =
203 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
205 std::string url_host = url->host();
206 const base::ListValue* url_list = NULL;
207 if (!overrides || !overrides->GetList(url_host, &url_list))
208 return false;
210 extensions::ExtensionRegistry* registry =
211 extensions::ExtensionRegistry::Get(browser_context);
212 const extensions::ExtensionSet& extensions = registry->enabled_extensions();
214 GURL component_url;
215 bool found_component_override = false;
217 // Iterate over the URL list looking for a suitable override. If a
218 // valid non-component override is encountered it is chosen immediately.
219 for (size_t i = 0; i < url_list->GetSize(); ++i) {
220 const base::Value* val = NULL;
221 url_list->Get(i, &val);
223 GURL override_url;
224 const Extension* extension;
225 if (!ValidateOverrideURL(
226 val, *url, extensions, &override_url, &extension)) {
227 LOG(WARNING) << "Invalid chrome URL override";
228 UnregisterChromeURLOverride(url_host, profile, val);
229 // The above Unregister call will remove this item from url_list.
230 --i;
231 continue;
234 // We can't handle chrome-extension URLs in incognito mode unless the
235 // extension uses split mode.
236 bool incognito_override_allowed =
237 extensions::IncognitoInfo::IsSplitMode(extension) &&
238 extensions::util::IsIncognitoEnabled(extension->id(), profile);
239 if (profile->IsOffTheRecord() && !incognito_override_allowed) {
240 continue;
243 if (!extensions::Manifest::IsComponentLocation(extension->location())) {
244 *url = override_url;
245 return true;
248 if (!found_component_override) {
249 found_component_override = true;
250 component_url = override_url;
254 // If no other non-component overrides were found, use the first known
255 // component override, if any.
256 if (found_component_override) {
257 *url = component_url;
258 return true;
261 return false;
264 // static
265 bool ExtensionWebUI::HandleChromeURLOverrideReverse(
266 GURL* url, content::BrowserContext* browser_context) {
267 Profile* profile = Profile::FromBrowserContext(browser_context);
268 const base::DictionaryValue* overrides =
269 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
270 if (!overrides)
271 return false;
273 // Find the reverse mapping based on the given URL. For example this maps the
274 // internal URL
275 // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to
276 // chrome://bookmarks/#1 for display in the omnibox.
277 for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd();
278 it.Advance()) {
279 const base::ListValue* url_list = NULL;
280 if (!it.value().GetAsList(&url_list))
281 continue;
283 for (base::ListValue::const_iterator it2 = url_list->begin();
284 it2 != url_list->end(); ++it2) {
285 std::string override;
286 if (!(*it2)->GetAsString(&override))
287 continue;
288 if (base::StartsWith(url->spec(), override,
289 base::CompareCase::SENSITIVE)) {
290 GURL original_url(content::kChromeUIScheme + std::string("://") +
291 it.key() + url->spec().substr(override.length()));
292 *url = original_url;
293 return true;
298 return false;
301 // static
302 void ExtensionWebUI::RegisterChromeURLOverrides(
303 Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
304 if (overrides.empty())
305 return;
307 PrefService* prefs = profile->GetPrefs();
308 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
309 base::DictionaryValue* all_overrides = update.Get();
311 // For each override provided by the extension, add it to the front of
312 // the override list if it's not already in the list.
313 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
314 for (; iter != overrides.end(); ++iter) {
315 const std::string& key = iter->first;
316 base::ListValue* page_overrides = NULL;
317 if (!all_overrides->GetList(key, &page_overrides)) {
318 page_overrides = new base::ListValue();
319 all_overrides->Set(key, page_overrides);
320 } else {
321 CleanUpDuplicates(page_overrides);
323 // Verify that the override isn't already in the list.
324 base::ListValue::iterator i = page_overrides->begin();
325 for (; i != page_overrides->end(); ++i) {
326 std::string override_val;
327 if (!(*i)->GetAsString(&override_val)) {
328 NOTREACHED();
329 continue;
331 if (override_val == iter->second.spec())
332 break;
334 // This value is already in the list, leave it alone.
335 if (i != page_overrides->end())
336 continue;
338 // Insert the override at the front of the list. Last registered override
339 // wins.
340 page_overrides->Insert(0, new base::StringValue(iter->second.spec()));
344 // static
345 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
346 Profile* profile,
347 base::ListValue* list,
348 const base::Value* override) {
349 size_t index = 0;
350 bool found = list->Remove(*override, &index);
351 if (found && index == 0) {
352 // This is the active override, so we need to find all existing
353 // tabs for this override and get them to reload the original URL.
354 base::Callback<void(WebContents*)> callback =
355 base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile);
356 extensions::ExtensionTabUtil::ForEachTab(callback);
360 // static
361 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
362 Profile* profile,
363 const base::Value* override) {
364 if (!override)
365 return;
366 PrefService* prefs = profile->GetPrefs();
367 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
368 base::DictionaryValue* all_overrides = update.Get();
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 return;
374 } else {
375 UnregisterAndReplaceOverride(page, profile, page_overrides, override);
379 // static
380 void ExtensionWebUI::UnregisterChromeURLOverrides(
381 Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
382 if (overrides.empty())
383 return;
384 PrefService* prefs = profile->GetPrefs();
385 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
386 base::DictionaryValue* all_overrides = update.Get();
387 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
388 for (; iter != overrides.end(); ++iter) {
389 const std::string& page = iter->first;
390 base::ListValue* page_overrides = NULL;
391 if (!all_overrides->GetList(page, &page_overrides)) {
392 // If it's being unregistered, it should already be in the list.
393 NOTREACHED();
394 continue;
395 } else {
396 base::StringValue override(iter->second.spec());
397 UnregisterAndReplaceOverride(iter->first, profile,
398 page_overrides, &override);
403 // static
404 void ExtensionWebUI::GetFaviconForURL(
405 Profile* profile,
406 const GURL& page_url,
407 const favicon_base::FaviconResultsCallback& callback) {
408 const Extension* extension = extensions::ExtensionRegistry::Get(
409 profile)->enabled_extensions().GetByID(page_url.host());
410 if (!extension) {
411 RunFaviconCallbackAsync(callback, gfx::Image());
412 return;
415 // Fetch resources for all supported scale factors for which there are
416 // resources. Load image reps for all supported scale factors (in addition to
417 // 1x) immediately instead of in an as needed fashion to be consistent with
418 // how favicons are requested for chrome:// and page URLs.
419 const std::vector<float>& favicon_scales = favicon_base::GetFaviconScales();
420 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
421 for (size_t i = 0; i < favicon_scales.size(); ++i) {
422 float scale = favicon_scales[i];
423 int pixel_size = static_cast<int>(gfx::kFaviconSize * scale);
424 extensions::ExtensionResource icon_resource =
425 extensions::IconsInfo::GetIconResource(extension,
426 pixel_size,
427 ExtensionIconSet::MATCH_BIGGER);
429 ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale);
430 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
431 icon_resource,
432 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
433 gfx::Size(pixel_size, pixel_size),
434 resource_scale_factor));
437 // LoadImagesAsync actually can run callback synchronously. We want to force
438 // async.
439 extensions::ImageLoader::Get(profile)->LoadImagesAsync(
440 extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback));