Make default apps cache multiprofile friendly
[chromium-blink-merge.git] / chrome / browser / extensions / extension_web_ui.cc
blob40a4debf14c8d985867b159d5126481399df7ba5
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/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/extensions/extension_tab_util.h"
17 #include "chrome/browser/extensions/image_loader.h"
18 #include "chrome/browser/favicon/favicon_util.h"
19 #include "chrome/browser/prefs/scoped_user_pref_update.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/extensions/extension.h"
23 #include "chrome/common/extensions/extension_constants.h"
24 #include "chrome/common/extensions/extension_icon_set.h"
25 #include "chrome/common/extensions/incognito_handler.h"
26 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
27 #include "chrome/common/url_constants.h"
28 #include "components/user_prefs/pref_registry_syncable.h"
29 #include "content/public/browser/navigation_controller.h"
30 #include "content/public/browser/web_contents.h"
31 #include "content/public/browser/web_ui.h"
32 #include "content/public/common/bindings_policy.h"
33 #include "content/public/common/page_transition_types.h"
34 #include "extensions/common/extension_resource.h"
35 #include "net/base/file_stream.h"
36 #include "third_party/skia/include/core/SkBitmap.h"
37 #include "ui/gfx/codec/png_codec.h"
38 #include "ui/gfx/favicon_size.h"
39 #include "ui/gfx/image/image_skia.h"
41 using content::WebContents;
42 using extensions::Extension;
43 using extensions::URLOverrides;
45 namespace {
47 // De-dupes the items in |list|. Assumes the values are strings.
48 void CleanUpDuplicates(base::ListValue* list) {
49 std::set<std::string> seen_values;
51 // Loop backwards as we may be removing items.
52 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
53 std::string value;
54 if (!list->GetString(i, &value)) {
55 NOTREACHED();
56 continue;
59 if (seen_values.find(value) == seen_values.end())
60 seen_values.insert(value);
61 else
62 list->Remove(i, NULL);
66 // Reloads the page in |web_contents| if it uses the same profile as |profile|
67 // and if the current URL is a chrome URL.
68 void UnregisterAndReplaceOverrideForWebContents(
69 const std::string& page, Profile* profile, WebContents* web_contents) {
70 if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile)
71 return;
73 GURL url = web_contents->GetURL();
74 if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page)
75 return;
77 // Don't use Reload() since |url| isn't the same as the internal URL that
78 // NavigationController has.
79 web_contents->GetController().LoadURL(
80 url, content::Referrer(url, WebKit::WebReferrerPolicyDefault),
81 content::PAGE_TRANSITION_RELOAD, std::string());
84 // Run favicon callbck with image result. If no favicon was available then
85 // |image| will be empty.
86 void RunFaviconCallbackAsync(
87 const FaviconService::FaviconResultsCallback& callback,
88 const gfx::Image& image) {
89 std::vector<chrome::FaviconBitmapResult>* favicon_bitmap_results =
90 new std::vector<chrome::FaviconBitmapResult>();
92 const std::vector<gfx::ImageSkiaRep>& image_reps =
93 image.AsImageSkia().image_reps();
94 for (size_t i = 0; i < image_reps.size(); ++i) {
95 const gfx::ImageSkiaRep& image_rep = image_reps[i];
96 scoped_refptr<base::RefCountedBytes> bitmap_data(
97 new base::RefCountedBytes());
98 if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(),
99 false,
100 &bitmap_data->data())) {
101 chrome::FaviconBitmapResult bitmap_result;
102 bitmap_result.bitmap_data = bitmap_data;
103 bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(),
104 image_rep.pixel_height());
105 // Leave |bitmap_result|'s icon URL as the default of GURL().
106 bitmap_result.icon_type = chrome::FAVICON;
108 favicon_bitmap_results->push_back(bitmap_result);
109 } else {
110 NOTREACHED() << "Could not encode extension favicon";
114 base::MessageLoopProxy::current()->PostTask(
115 FROM_HERE,
116 base::Bind(&FaviconService::FaviconResultsCallbackRunner,
117 callback,
118 base::Owned(favicon_bitmap_results)));
121 } // namespace
123 const char ExtensionWebUI::kExtensionURLOverrides[] =
124 "extensions.chrome_url_overrides";
126 ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url)
127 : WebUIController(web_ui),
128 url_(url) {
129 Profile* profile = Profile::FromWebUI(web_ui);
130 ExtensionService* service = profile->GetExtensionService();
131 const Extension* extension =
132 service->extensions()->GetExtensionOrAppByURL(url);
133 DCHECK(extension);
135 // The base class defaults to enabling WebUI bindings, but we don't need
136 // those (this is also reflected in ChromeWebUIControllerFactory::
137 // UseWebUIBindingsForURL).
138 int bindings = 0;
140 // Bind externalHost to Extension WebUI loaded in Chrome Frame.
141 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
142 if (browser_command_line.HasSwitch(switches::kChromeFrame))
143 bindings |= content::BINDINGS_POLICY_EXTERNAL_HOST;
144 web_ui->SetBindings(bindings);
146 // Hack: A few things we specialize just for the bookmark manager.
147 if (extension->id() == extension_misc::kBookmarkManagerId) {
148 bookmark_manager_private_event_router_.reset(
149 new extensions::BookmarkManagerPrivateEventRouter(
150 profile, web_ui->GetWebContents()));
152 web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK);
156 ExtensionWebUI::~ExtensionWebUI() {}
158 extensions::BookmarkManagerPrivateEventRouter*
159 ExtensionWebUI::bookmark_manager_private_event_router() {
160 return bookmark_manager_private_event_router_.get();
163 ////////////////////////////////////////////////////////////////////////////////
164 // chrome:// URL overrides
166 // static
167 void ExtensionWebUI::RegisterProfilePrefs(
168 user_prefs::PrefRegistrySyncable* registry) {
169 registry->RegisterDictionaryPref(
170 kExtensionURLOverrides,
171 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
174 // static
175 bool ExtensionWebUI::HandleChromeURLOverride(
176 GURL* url, content::BrowserContext* browser_context) {
177 if (!url->SchemeIs(chrome::kChromeUIScheme))
178 return false;
180 Profile* profile = Profile::FromBrowserContext(browser_context);
181 const base::DictionaryValue* overrides =
182 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
183 std::string page = url->host();
184 const base::ListValue* url_list = NULL;
185 if (!overrides || !overrides->GetList(page, &url_list))
186 return false;
188 ExtensionService* service = profile->GetExtensionService();
190 size_t i = 0;
191 while (i < url_list->GetSize()) {
192 const Value* val = NULL;
193 url_list->Get(i, &val);
195 // Verify that the override value is good. If not, unregister it and find
196 // the next one.
197 std::string override;
198 if (!val->GetAsString(&override)) {
199 NOTREACHED();
200 UnregisterChromeURLOverride(page, profile, val);
201 continue;
204 if (!url->query().empty())
205 override += "?" + url->query();
206 if (!url->ref().empty())
207 override += "#" + url->ref();
208 GURL extension_url(override);
209 if (!extension_url.is_valid()) {
210 NOTREACHED();
211 UnregisterChromeURLOverride(page, profile, val);
212 continue;
215 // Verify that the extension that's being referred to actually exists.
216 const Extension* extension =
217 service->extensions()->GetByID(extension_url.host());
218 if (!extension) {
219 // This can currently happen if you use --load-extension one run, and
220 // then don't use it the next. It could also happen if an extension
221 // were deleted directly from the filesystem, etc.
222 LOG(WARNING) << "chrome URL override present for non-existant extension";
223 UnregisterChromeURLOverride(page, profile, val);
224 continue;
227 // We can't handle chrome-extension URLs in incognito mode unless the
228 // extension uses split mode.
229 bool incognito_override_allowed =
230 extensions::IncognitoInfo::IsSplitMode(extension) &&
231 service->IsIncognitoEnabled(extension->id());
232 if (profile->IsOffTheRecord() && !incognito_override_allowed) {
233 ++i;
234 continue;
237 *url = extension_url;
238 return true;
240 return false;
243 // static
244 bool ExtensionWebUI::HandleChromeURLOverrideReverse(
245 GURL* url, content::BrowserContext* browser_context) {
246 Profile* profile = Profile::FromBrowserContext(browser_context);
247 const base::DictionaryValue* overrides =
248 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
249 if (!overrides)
250 return false;
252 // Find the reverse mapping based on the given URL. For example this maps the
253 // internal URL
254 // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to
255 // chrome://bookmarks/#1 for display in the omnibox.
256 for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd();
257 it.Advance()) {
258 const base::ListValue* url_list = NULL;
259 if (!it.value().GetAsList(&url_list))
260 continue;
262 for (base::ListValue::const_iterator it2 = url_list->begin();
263 it2 != url_list->end(); ++it2) {
264 std::string override;
265 if (!(*it2)->GetAsString(&override))
266 continue;
267 if (StartsWithASCII(url->spec(), override, true)) {
268 GURL original_url(chrome::kChromeUIScheme + std::string("://") +
269 it.key() + url->spec().substr(override.length()));
270 *url = original_url;
271 return true;
276 return false;
279 // static
280 void ExtensionWebUI::RegisterChromeURLOverrides(
281 Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
282 if (overrides.empty())
283 return;
285 PrefService* prefs = profile->GetPrefs();
286 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
287 base::DictionaryValue* all_overrides = update.Get();
289 // For each override provided by the extension, add it to the front of
290 // the override list if it's not already in the list.
291 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
292 for (; iter != overrides.end(); ++iter) {
293 const std::string& key = iter->first;
294 base::ListValue* page_overrides = NULL;
295 if (!all_overrides->GetList(key, &page_overrides)) {
296 page_overrides = new base::ListValue();
297 all_overrides->Set(key, page_overrides);
298 } else {
299 CleanUpDuplicates(page_overrides);
301 // Verify that the override isn't already in the list.
302 base::ListValue::iterator i = page_overrides->begin();
303 for (; i != page_overrides->end(); ++i) {
304 std::string override_val;
305 if (!(*i)->GetAsString(&override_val)) {
306 NOTREACHED();
307 continue;
309 if (override_val == iter->second.spec())
310 break;
312 // This value is already in the list, leave it alone.
313 if (i != page_overrides->end())
314 continue;
316 // Insert the override at the front of the list. Last registered override
317 // wins.
318 page_overrides->Insert(0, new StringValue(iter->second.spec()));
322 // static
323 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
324 Profile* profile,
325 base::ListValue* list,
326 const Value* override) {
327 size_t index = 0;
328 bool found = list->Remove(*override, &index);
329 if (found && index == 0) {
330 // This is the active override, so we need to find all existing
331 // tabs for this override and get them to reload the original URL.
332 base::Callback<void(WebContents*)> callback =
333 base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile);
334 ExtensionTabUtil::ForEachTab(callback);
338 // static
339 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
340 Profile* profile,
341 const Value* override) {
342 if (!override)
343 return;
344 PrefService* prefs = profile->GetPrefs();
345 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
346 base::DictionaryValue* all_overrides = update.Get();
347 base::ListValue* page_overrides = NULL;
348 if (!all_overrides->GetList(page, &page_overrides)) {
349 // If it's being unregistered, it should already be in the list.
350 NOTREACHED();
351 return;
352 } else {
353 UnregisterAndReplaceOverride(page, profile, page_overrides, override);
357 // static
358 void ExtensionWebUI::UnregisterChromeURLOverrides(
359 Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
360 if (overrides.empty())
361 return;
362 PrefService* prefs = profile->GetPrefs();
363 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
364 base::DictionaryValue* all_overrides = update.Get();
365 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
366 for (; iter != overrides.end(); ++iter) {
367 const std::string& page = iter->first;
368 base::ListValue* page_overrides = NULL;
369 if (!all_overrides->GetList(page, &page_overrides)) {
370 // If it's being unregistered, it should already be in the list.
371 NOTREACHED();
372 continue;
373 } else {
374 StringValue override(iter->second.spec());
375 UnregisterAndReplaceOverride(iter->first, profile,
376 page_overrides, &override);
381 // static
382 void ExtensionWebUI::GetFaviconForURL(
383 Profile* profile,
384 const GURL& page_url,
385 const FaviconService::FaviconResultsCallback& callback) {
386 // Even when the extensions service is enabled by default, it's still
387 // disabled in incognito mode.
388 ExtensionService* service = profile->GetExtensionService();
389 if (!service) {
390 RunFaviconCallbackAsync(callback, gfx::Image());
391 return;
393 const Extension* extension = service->extensions()->GetByID(page_url.host());
394 if (!extension) {
395 RunFaviconCallbackAsync(callback, gfx::Image());
396 return;
399 // Fetch resources for all supported scale factors for which there are
400 // resources. Load image reps for all supported scale factors (in addition to
401 // 1x) immediately instead of in an as needed fashion to be consistent with
402 // how favicons are requested for chrome:// and page URLs.
403 const std::vector<ui::ScaleFactor>& scale_factors =
404 FaviconUtil::GetFaviconScaleFactors();
405 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
406 for (size_t i = 0; i < scale_factors.size(); ++i) {
407 float scale = ui::GetScaleFactorScale(scale_factors[i]);
408 int pixel_size = static_cast<int>(gfx::kFaviconSize * scale);
409 extensions::ExtensionResource icon_resource =
410 extensions::IconsInfo::GetIconResource(extension,
411 pixel_size,
412 ExtensionIconSet::MATCH_BIGGER);
414 info_list.push_back(
415 extensions::ImageLoader::ImageRepresentation(
416 icon_resource,
417 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
418 gfx::Size(pixel_size, pixel_size),
419 scale_factors[i]));
422 // LoadImagesAsync actually can run callback synchronously. We want to force
423 // async.
424 extensions::ImageLoader::Get(profile)->LoadImagesAsync(
425 extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback));