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 "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"
43 using content::WebContents
;
44 using extensions::Extension
;
45 using extensions::URLOverrides
;
49 // De-dupes the items in |list|. Assumes the values are strings.
50 void CleanUpDuplicates(base::ListValue
* list
) {
51 std::set
<std::string
> seen_values
;
53 // Loop backwards as we may be removing items.
54 for (size_t i
= list
->GetSize() - 1; (i
+ 1) > 0; --i
) {
56 if (!list
->GetString(i
, &value
)) {
61 if (seen_values
.find(value
) == seen_values
.end())
62 seen_values
.insert(value
);
64 list
->Remove(i
, NULL
);
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.
70 void UnregisterAndReplaceOverrideForWebContents(const std::string
& page
,
72 WebContents
* web_contents
) {
73 if (Profile::FromBrowserContext(web_contents
->GetBrowserContext()) != profile
)
76 GURL url
= web_contents
->GetURL();
77 if (!url
.SchemeIs(content::kChromeUIScheme
) || url
.host() != page
)
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());
87 // Run favicon callbck with image result. If no favicon was available then
88 // |image| will be empty.
89 void RunFaviconCallbackAsync(
90 const FaviconService::FaviconResultsCallback
& callback
,
91 const gfx::Image
& image
) {
92 std::vector
<favicon_base::FaviconBitmapResult
>* favicon_bitmap_results
=
93 new std::vector
<favicon_base::FaviconBitmapResult
>();
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(),
103 &bitmap_data
->data())) {
104 favicon_base::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
= favicon_base::FAVICON
;
111 favicon_bitmap_results
->push_back(bitmap_result
);
113 NOTREACHED() << "Could not encode extension favicon";
117 base::MessageLoopProxy::current()->PostTask(
119 base::Bind(&FaviconService::FaviconResultsCallbackRunner
,
121 base::Owned(favicon_bitmap_results
)));
124 bool ValidateOverrideURL(const base::Value
* override_url_value
,
125 const GURL
& source_url
,
126 const extensions::ExtensionSet
& extensions
,
128 const Extension
** extension
) {
129 std::string override
;
130 if (!override_url_value
|| !override_url_value
->GetAsString(&override
)) {
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()) {
141 *extension
= extensions
.GetByID(override_url
->host());
150 const char ExtensionWebUI::kExtensionURLOverrides
[] =
151 "extensions.chrome_url_overrides";
153 ExtensionWebUI::ExtensionWebUI(content::WebUI
* web_ui
, const GURL
& url
)
154 : WebUIController(web_ui
),
156 Profile
* profile
= Profile::FromWebUI(web_ui
);
157 ExtensionService
* service
= profile
->GetExtensionService();
158 const Extension
* extension
=
159 service
->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(content::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(
192 kExtensionURLOverrides
,
193 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
197 bool ExtensionWebUI::HandleChromeURLOverride(
199 content::BrowserContext
* browser_context
) {
200 if (!url
->SchemeIs(content::kChromeUIScheme
))
203 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
204 const base::DictionaryValue
* overrides
=
205 profile
->GetPrefs()->GetDictionary(kExtensionURLOverrides
);
207 std::string url_host
= url
->host();
208 const base::ListValue
* url_list
= NULL
;
209 if (!overrides
|| !overrides
->GetList(url_host
, &url_list
))
212 extensions::ExtensionRegistry
* registry
=
213 extensions::ExtensionRegistry::Get(browser_context
);
214 const extensions::ExtensionSet
& extensions
= registry
->enabled_extensions();
217 bool found_component_override
= false;
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
);
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.
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
) {
245 if (!extensions::Manifest::IsComponentLocation(extension
->location())) {
250 if (!found_component_override
) {
251 found_component_override
= true;
252 component_url
= override_url
;
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
;
267 bool 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
);
275 // Find the reverse mapping based on the given URL. For example this maps the
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();
281 const base::ListValue
* url_list
= NULL
;
282 if (!it
.value().GetAsList(&url_list
))
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
))
290 if (StartsWithASCII(url
->spec(), override
, true)) {
291 GURL
original_url(content::kChromeUIScheme
+ std::string("://") +
292 it
.key() + url
->spec().substr(override
.length()));
303 void ExtensionWebUI::RegisterChromeURLOverrides(
304 Profile
* profile
, const URLOverrides::URLOverrideMap
& overrides
) {
305 if (overrides
.empty())
308 PrefService
* prefs
= profile
->GetPrefs();
309 DictionaryPrefUpdate
update(prefs
, kExtensionURLOverrides
);
310 base::DictionaryValue
* all_overrides
= update
.Get();
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
);
322 CleanUpDuplicates(page_overrides
);
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
)) {
332 if (override_val
== iter
->second
.spec())
335 // This value is already in the list, leave it alone.
336 if (i
!= page_overrides
->end())
339 // Insert the override at the front of the list. Last registered override
341 page_overrides
->Insert(0, new base::StringValue(iter
->second
.spec()));
346 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string
& page
,
348 base::ListValue
* list
,
349 const base::Value
* override
) {
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
);
362 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string
& page
,
364 const base::Value
* override
) {
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.
376 UnregisterAndReplaceOverride(page
, profile
, page_overrides
, override
);
381 void ExtensionWebUI::UnregisterChromeURLOverrides(
382 Profile
* profile
, const URLOverrides::URLOverrideMap
& overrides
) {
383 if (overrides
.empty())
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.
397 base::StringValue
override(iter
->second
.spec());
398 UnregisterAndReplaceOverride(iter
->first
, profile
,
399 page_overrides
, &override
);
405 void ExtensionWebUI::GetFaviconForURL(
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();
413 RunFaviconCallbackAsync(callback
, gfx::Image());
416 const Extension
* extension
= service
->extensions()->GetByID(page_url
.host());
418 RunFaviconCallbackAsync(callback
, gfx::Image());
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
,
435 ExtensionIconSet::MATCH_BIGGER
);
438 extensions::ImageLoader::ImageRepresentation(
440 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
441 gfx::Size(pixel_size
, pixel_size
),
445 // LoadImagesAsync actually can run callback synchronously. We want to force
447 extensions::ImageLoader::Get(profile
)->LoadImagesAsync(
448 extension
, info_list
, base::Bind(&RunFaviconCallbackAsync
, callback
));