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/favicon/favicon_service.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "chrome/common/chrome_switches.h" 22 #include "chrome/common/extensions/extension_constants.h" 23 #include "chrome/common/url_constants.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 "content/public/common/page_transition_types.h" 31 #include "extensions/browser/extension_registry.h" 32 #include "extensions/browser/image_loader.h" 33 #include "extensions/common/extension.h" 34 #include "extensions/common/extension_icon_set.h" 35 #include "extensions/common/extension_resource.h" 36 #include "extensions/common/manifest_handlers/icons_handler.h" 37 #include "extensions/common/manifest_handlers/incognito_info.h" 38 #include "net/base/file_stream.h" 39 #include "third_party/skia/include/core/SkBitmap.h" 40 #include "ui/gfx/codec/png_codec.h" 41 #include "ui/gfx/favicon_size.h" 42 #include "ui/gfx/image/image_skia.h" 43 44 using content::WebContents; 45 using extensions::Extension; 46 using extensions::URLOverrides; 47 48 namespace { 49 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; 53 54 // Loop backwards as we may be removing items. 55 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { 56 std::string value; 57 if (!list->GetString(i, &value)) { 58 NOTREACHED(); 59 continue; 60 } 61 62 if (seen_values.find(value) == seen_values.end()) 63 seen_values.insert(value); 64 else 65 list->Remove(i, NULL); 66 } 67 } 68 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, 72 Profile* profile, 73 WebContents* web_contents) { 74 if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile) 75 return; 76 77 GURL url = web_contents->GetURL(); 78 if (!url.SchemeIs(content::kChromeUIScheme) || url.host() != page) 79 return; 80 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(url, blink::WebReferrerPolicyDefault), 85 content::PAGE_TRANSITION_RELOAD, std::string()); 86 } 87 88 // Run favicon callbck with image result. If no favicon was available then 89 // |image| will be empty. 90 void RunFaviconCallbackAsync( 91 const favicon_base::FaviconResultsCallback& callback, 92 const gfx::Image& image) { 93 std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results = 94 new std::vector<favicon_base::FaviconRawBitmapResult>(); 95 96 const std::vector<gfx::ImageSkiaRep>& image_reps = 97 image.AsImageSkia().image_reps(); 98 for (size_t i = 0; i < image_reps.size(); ++i) { 99 const gfx::ImageSkiaRep& image_rep = image_reps[i]; 100 scoped_refptr<base::RefCountedBytes> bitmap_data( 101 new base::RefCountedBytes()); 102 if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(), 103 false, 104 &bitmap_data->data())) { 105 favicon_base::FaviconRawBitmapResult bitmap_result; 106 bitmap_result.bitmap_data = bitmap_data; 107 bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(), 108 image_rep.pixel_height()); 109 // Leave |bitmap_result|'s icon URL as the default of GURL(). 110 bitmap_result.icon_type = favicon_base::FAVICON; 111 112 favicon_bitmap_results->push_back(bitmap_result); 113 } else { 114 NOTREACHED() << "Could not encode extension favicon"; 115 } 116 } 117 118 base::MessageLoopProxy::current()->PostTask( 119 FROM_HERE, 120 base::Bind(&FaviconService::FaviconResultsCallbackRunner, 121 callback, 122 base::Owned(favicon_bitmap_results))); 123 } 124 125 bool ValidateOverrideURL(const base::Value* override_url_value, 126 const GURL& source_url, 127 const extensions::ExtensionSet& extensions, 128 GURL* override_url, 129 const Extension** extension) { 130 std::string override; 131 if (!override_url_value || !override_url_value->GetAsString(&override)) { 132 return false; 133 } 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()) { 140 return false; 141 } 142 *extension = extensions.GetByID(override_url->host()); 143 if (!*extension) { 144 return false; 145 } 146 return true; 147 } 148 149 } // namespace 150 151 const char ExtensionWebUI::kExtensionURLOverrides[] = 152 "extensions.chrome_url_overrides"; 153 154 ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url) 155 : WebUIController(web_ui), 156 url_(url) { 157 Profile* profile = Profile::FromWebUI(web_ui); 158 ExtensionService* service = profile->GetExtensionService(); 159 const Extension* extension = 160 service->extensions()->GetExtensionOrAppByURL(url); 161 DCHECK(extension); 162 163 // The base class defaults to enabling WebUI bindings, but we don't need 164 // those (this is also reflected in ChromeWebUIControllerFactory:: 165 // UseWebUIBindingsForURL). 166 int bindings = 0; 167 web_ui->SetBindings(bindings); 168 169 // Hack: A few things we specialize just for the bookmark manager. 170 if (extension->id() == extension_misc::kBookmarkManagerId) { 171 bookmark_manager_private_drag_event_router_.reset( 172 new extensions::BookmarkManagerPrivateDragEventRouter( 173 profile, web_ui->GetWebContents())); 174 175 web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK); 176 } 177 } 178 179 ExtensionWebUI::~ExtensionWebUI() {} 180 181 extensions::BookmarkManagerPrivateDragEventRouter* 182 ExtensionWebUI::bookmark_manager_private_drag_event_router() { 183 return bookmark_manager_private_drag_event_router_.get(); 184 } 185 186 //////////////////////////////////////////////////////////////////////////////// 187 // chrome:// URL overrides 188 189 // static 190 void ExtensionWebUI::RegisterProfilePrefs( 191 user_prefs::PrefRegistrySyncable* registry) { 192 registry->RegisterDictionaryPref( 193 kExtensionURLOverrides, 194 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 195 } 196 197 // static 198 bool ExtensionWebUI::HandleChromeURLOverride( 199 GURL* url, 200 content::BrowserContext* browser_context) { 201 if (!url->SchemeIs(content::kChromeUIScheme)) 202 return false; 203 204 Profile* profile = Profile::FromBrowserContext(browser_context); 205 const base::DictionaryValue* overrides = 206 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 207 208 std::string url_host = url->host(); 209 const base::ListValue* url_list = NULL; 210 if (!overrides || !overrides->GetList(url_host, &url_list)) 211 return false; 212 213 extensions::ExtensionRegistry* registry = 214 extensions::ExtensionRegistry::Get(browser_context); 215 const extensions::ExtensionSet& extensions = registry->enabled_extensions(); 216 217 GURL component_url; 218 bool found_component_override = false; 219 220 // Iterate over the URL list looking for a suitable override. If a 221 // valid non-component override is encountered it is chosen immediately. 222 for (size_t i = 0; i < url_list->GetSize(); ++i) { 223 const base::Value* val = NULL; 224 url_list->Get(i, &val); 225 226 GURL override_url; 227 const Extension* extension; 228 if (!ValidateOverrideURL( 229 val, *url, extensions, &override_url, &extension)) { 230 LOG(WARNING) << "Invalid chrome URL override"; 231 UnregisterChromeURLOverride(url_host, profile, val); 232 // The above Unregister call will remove this item from url_list. 233 --i; 234 continue; 235 } 236 237 // We can't handle chrome-extension URLs in incognito mode unless the 238 // extension uses split mode. 239 bool incognito_override_allowed = 240 extensions::IncognitoInfo::IsSplitMode(extension) && 241 extensions::util::IsIncognitoEnabled(extension->id(), profile); 242 if (profile->IsOffTheRecord() && !incognito_override_allowed) { 243 continue; 244 } 245 246 if (!extensions::Manifest::IsComponentLocation(extension->location())) { 247 *url = override_url; 248 return true; 249 } 250 251 if (!found_component_override) { 252 found_component_override = true; 253 component_url = override_url; 254 } 255 } 256 257 // If no other non-component overrides were found, use the first known 258 // component override, if any. 259 if (found_component_override) { 260 *url = component_url; 261 return true; 262 } 263 264 return false; 265 } 266 267 // static 268 bool ExtensionWebUI::HandleChromeURLOverrideReverse( 269 GURL* url, content::BrowserContext* browser_context) { 270 Profile* profile = Profile::FromBrowserContext(browser_context); 271 const base::DictionaryValue* overrides = 272 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 273 if (!overrides) 274 return false; 275 276 // Find the reverse mapping based on the given URL. For example this maps the 277 // internal URL 278 // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to 279 // chrome://bookmarks/#1 for display in the omnibox. 280 for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd(); 281 it.Advance()) { 282 const base::ListValue* url_list = NULL; 283 if (!it.value().GetAsList(&url_list)) 284 continue; 285 286 for (base::ListValue::const_iterator it2 = url_list->begin(); 287 it2 != url_list->end(); ++it2) { 288 std::string override; 289 if (!(*it2)->GetAsString(&override)) 290 continue; 291 if (StartsWithASCII(url->spec(), override, true)) { 292 GURL original_url(content::kChromeUIScheme + std::string("://") + 293 it.key() + url->spec().substr(override.length())); 294 *url = original_url; 295 return true; 296 } 297 } 298 } 299 300 return false; 301 } 302 303 // static 304 void ExtensionWebUI::RegisterChromeURLOverrides( 305 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 306 if (overrides.empty()) 307 return; 308 309 PrefService* prefs = profile->GetPrefs(); 310 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 311 base::DictionaryValue* all_overrides = update.Get(); 312 313 // For each override provided by the extension, add it to the front of 314 // the override list if it's not already in the list. 315 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); 316 for (; iter != overrides.end(); ++iter) { 317 const std::string& key = iter->first; 318 base::ListValue* page_overrides = NULL; 319 if (!all_overrides->GetList(key, &page_overrides)) { 320 page_overrides = new base::ListValue(); 321 all_overrides->Set(key, page_overrides); 322 } else { 323 CleanUpDuplicates(page_overrides); 324 325 // Verify that the override isn't already in the list. 326 base::ListValue::iterator i = page_overrides->begin(); 327 for (; i != page_overrides->end(); ++i) { 328 std::string override_val; 329 if (!(*i)->GetAsString(&override_val)) { 330 NOTREACHED(); 331 continue; 332 } 333 if (override_val == iter->second.spec()) 334 break; 335 } 336 // This value is already in the list, leave it alone. 337 if (i != page_overrides->end()) 338 continue; 339 } 340 // Insert the override at the front of the list. Last registered override 341 // wins. 342 page_overrides->Insert(0, new base::StringValue(iter->second.spec())); 343 } 344 } 345 346 // static 347 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, 348 Profile* profile, 349 base::ListValue* list, 350 const base::Value* override) { 351 size_t index = 0; 352 bool found = list->Remove(*override, &index); 353 if (found && index == 0) { 354 // This is the active override, so we need to find all existing 355 // tabs for this override and get them to reload the original URL. 356 base::Callback<void(WebContents*)> callback = 357 base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile); 358 extensions::ExtensionTabUtil::ForEachTab(callback); 359 } 360 } 361 362 // static 363 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, 364 Profile* profile, 365 const base::Value* override) { 366 if (!override) 367 return; 368 PrefService* prefs = profile->GetPrefs(); 369 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 370 base::DictionaryValue* all_overrides = update.Get(); 371 base::ListValue* page_overrides = NULL; 372 if (!all_overrides->GetList(page, &page_overrides)) { 373 // If it's being unregistered, it should already be in the list. 374 NOTREACHED(); 375 return; 376 } else { 377 UnregisterAndReplaceOverride(page, profile, page_overrides, override); 378 } 379 } 380 381 // static 382 void ExtensionWebUI::UnregisterChromeURLOverrides( 383 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 384 if (overrides.empty()) 385 return; 386 PrefService* prefs = profile->GetPrefs(); 387 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 388 base::DictionaryValue* all_overrides = update.Get(); 389 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); 390 for (; iter != overrides.end(); ++iter) { 391 const std::string& page = iter->first; 392 base::ListValue* page_overrides = NULL; 393 if (!all_overrides->GetList(page, &page_overrides)) { 394 // If it's being unregistered, it should already be in the list. 395 NOTREACHED(); 396 continue; 397 } else { 398 base::StringValue override(iter->second.spec()); 399 UnregisterAndReplaceOverride(iter->first, profile, 400 page_overrides, &override); 401 } 402 } 403 } 404 405 // static 406 void ExtensionWebUI::GetFaviconForURL( 407 Profile* profile, 408 const GURL& page_url, 409 const favicon_base::FaviconResultsCallback& callback) { 410 // Even when the extensions service is enabled by default, it's still 411 // disabled in incognito mode. 412 ExtensionService* service = profile->GetExtensionService(); 413 if (!service) { 414 RunFaviconCallbackAsync(callback, gfx::Image()); 415 return; 416 } 417 const Extension* extension = service->extensions()->GetByID(page_url.host()); 418 if (!extension) { 419 RunFaviconCallbackAsync(callback, gfx::Image()); 420 return; 421 } 422 423 // Fetch resources for all supported scale factors for which there are 424 // resources. Load image reps for all supported scale factors (in addition to 425 // 1x) immediately instead of in an as needed fashion to be consistent with 426 // how favicons are requested for chrome:// and page URLs. 427 const std::vector<float>& favicon_scales = favicon_base::GetFaviconScales(); 428 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 429 for (size_t i = 0; i < favicon_scales.size(); ++i) { 430 float scale = favicon_scales[i]; 431 int pixel_size = static_cast<int>(gfx::kFaviconSize * scale); 432 extensions::ExtensionResource icon_resource = 433 extensions::IconsInfo::GetIconResource(extension, 434 pixel_size, 435 ExtensionIconSet::MATCH_BIGGER); 436 437 ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale); 438 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 439 icon_resource, 440 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 441 gfx::Size(pixel_size, pixel_size), 442 resource_scale_factor)); 443 } 444 445 // LoadImagesAsync actually can run callback synchronously. We want to force 446 // async. 447 extensions::ImageLoader::Get(profile)->LoadImagesAsync( 448 extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback)); 449 } 450