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"
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
;
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
) {
57 if (!list
->GetString(i
, &value
)) {
62 if (seen_values
.find(value
) == seen_values
.end())
63 seen_values
.insert(value
);
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
,
73 WebContents
* web_contents
) {
74 if (Profile::FromBrowserContext(web_contents
->GetBrowserContext()) != profile
)
77 GURL url
= web_contents
->GetURL();
78 if (!url
.SchemeIs(content::kChromeUIScheme
) || url
.host() != page
)
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(),
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
);
115 NOTREACHED() << "Could not encode extension favicon";
119 base::ThreadTaskRunnerHandle::Get()->PostTask(
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
,
129 const Extension
** extension
) {
130 std::string override
;
131 if (!override_url_value
|| !override_url_value
->GetAsString(&override
)) {
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()) {
142 *extension
= extensions
.GetByID(override_url
->host());
151 const char ExtensionWebUI::kExtensionURLOverrides
[] =
152 "extensions.chrome_url_overrides";
154 ExtensionWebUI::ExtensionWebUI(content::WebUI
* web_ui
, const GURL
& url
)
155 : WebUIController(web_ui
),
157 Profile
* profile
= Profile::FromWebUI(web_ui
);
158 const Extension
* extension
= extensions::ExtensionRegistry::Get(
159 profile
)->enabled_extensions().GetExtensionOrAppByURL(url
);
162 // The base class defaults to enabling WebUI bindings, but we don't need
163 // those (this is also reflected in ChromeWebUIControllerFactory::
164 // UseWebUIBindingsForURL).
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
189 void ExtensionWebUI::RegisterProfilePrefs(
190 user_prefs::PrefRegistrySyncable
* registry
) {
191 registry
->RegisterDictionaryPref(kExtensionURLOverrides
);
195 bool ExtensionWebUI::HandleChromeURLOverride(
197 content::BrowserContext
* browser_context
) {
198 if (!url
->SchemeIs(content::kChromeUIScheme
))
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
))
210 extensions::ExtensionRegistry
* registry
=
211 extensions::ExtensionRegistry::Get(browser_context
);
212 const extensions::ExtensionSet
& extensions
= registry
->enabled_extensions();
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
);
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.
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
) {
243 if (!extensions::Manifest::IsComponentLocation(extension
->location())) {
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
;
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
);
273 // Find the reverse mapping based on the given URL. For example this maps the
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();
279 const base::ListValue
* url_list
= NULL
;
280 if (!it
.value().GetAsList(&url_list
))
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
))
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()));
302 void ExtensionWebUI::RegisterChromeURLOverrides(
303 Profile
* profile
, const URLOverrides::URLOverrideMap
& overrides
) {
304 if (overrides
.empty())
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
);
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
)) {
331 if (override_val
== iter
->second
.spec())
334 // This value is already in the list, leave it alone.
335 if (i
!= page_overrides
->end())
338 // Insert the override at the front of the list. Last registered override
340 page_overrides
->Insert(0, new base::StringValue(iter
->second
.spec()));
345 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string
& page
,
347 base::ListValue
* list
,
348 const base::Value
* override
) {
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
);
361 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string
& page
,
363 const base::Value
* override
) {
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.
375 UnregisterAndReplaceOverride(page
, profile
, page_overrides
, override
);
380 void ExtensionWebUI::UnregisterChromeURLOverrides(
381 Profile
* profile
, const URLOverrides::URLOverrideMap
& overrides
) {
382 if (overrides
.empty())
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.
396 base::StringValue
override(iter
->second
.spec());
397 UnregisterAndReplaceOverride(iter
->first
, profile
,
398 page_overrides
, &override
);
404 void ExtensionWebUI::GetFaviconForURL(
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());
411 RunFaviconCallbackAsync(callback
, gfx::Image());
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
,
427 ExtensionIconSet::MATCH_BIGGER
);
429 ui::ScaleFactor resource_scale_factor
= ui::GetSupportedScaleFactor(scale
);
430 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
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
439 extensions::ImageLoader::Get(profile
)->LoadImagesAsync(
440 extension
, info_list
, base::Bind(&RunFaviconCallbackAsync
, callback
));