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