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