extension_web_ui.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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/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" 42 43using content::WebContents; 44using extensions::Extension; 45using extensions::URLOverrides; 46 47namespace { 48 49// De-dupes the items in |list|. Assumes the values are strings. 50void CleanUpDuplicates(base::ListValue* list) { 51 std::set<std::string> seen_values; 52 53 // Loop backwards as we may be removing items. 54 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { 55 std::string value; 56 if (!list->GetString(i, &value)) { 57 NOTREACHED(); 58 continue; 59 } 60 61 if (seen_values.find(value) == seen_values.end()) 62 seen_values.insert(value); 63 else 64 list->Remove(i, NULL); 65 } 66} 67 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. 70void UnregisterAndReplaceOverrideForWebContents(const std::string& page, 71 Profile* profile, 72 WebContents* web_contents) { 73 if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile) 74 return; 75 76 GURL url = web_contents->GetURL(); 77 if (!url.SchemeIs(content::kChromeUIScheme) || url.host() != page) 78 return; 79 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()); 85} 86 87// Run favicon callbck with image result. If no favicon was available then 88// |image| will be empty. 89void RunFaviconCallbackAsync( 90 const FaviconService::FaviconResultsCallback& callback, 91 const gfx::Image& image) { 92 std::vector<chrome::FaviconBitmapResult>* favicon_bitmap_results = 93 new std::vector<chrome::FaviconBitmapResult>(); 94 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(), 102 false, 103 &bitmap_data->data())) { 104 chrome::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 = chrome::FAVICON; 110 111 favicon_bitmap_results->push_back(bitmap_result); 112 } else { 113 NOTREACHED() << "Could not encode extension favicon"; 114 } 115 } 116 117 base::MessageLoopProxy::current()->PostTask( 118 FROM_HERE, 119 base::Bind(&FaviconService::FaviconResultsCallbackRunner, 120 callback, 121 base::Owned(favicon_bitmap_results))); 122} 123 124bool ValidateOverrideURL(const base::Value* override_url_value, 125 const GURL& source_url, 126 const extensions::ExtensionSet& extensions, 127 GURL* override_url, 128 const Extension** extension) { 129 std::string override; 130 if (!override_url_value || !override_url_value->GetAsString(&override)) { 131 return false; 132 } 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()) { 139 return false; 140 } 141 *extension = extensions.GetByID(override_url->host()); 142 if (!*extension) { 143 return false; 144 } 145 return true; 146} 147 148} // namespace 149 150const char ExtensionWebUI::kExtensionURLOverrides[] = 151 "extensions.chrome_url_overrides"; 152 153ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url) 154 : WebUIController(web_ui), 155 url_(url) { 156 Profile* profile = Profile::FromWebUI(web_ui); 157 ExtensionService* service = profile->GetExtensionService(); 158 const Extension* extension = 159 service->extensions()->GetExtensionOrAppByURL(url); 160 DCHECK(extension); 161 162 // The base class defaults to enabling WebUI bindings, but we don't need 163 // those (this is also reflected in ChromeWebUIControllerFactory:: 164 // UseWebUIBindingsForURL). 165 int bindings = 0; 166 web_ui->SetBindings(bindings); 167 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())); 173 174 web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK); 175 } 176} 177 178ExtensionWebUI::~ExtensionWebUI() {} 179 180extensions::BookmarkManagerPrivateDragEventRouter* 181ExtensionWebUI::bookmark_manager_private_drag_event_router() { 182 return bookmark_manager_private_drag_event_router_.get(); 183} 184 185//////////////////////////////////////////////////////////////////////////////// 186// chrome:// URL overrides 187 188// static 189void ExtensionWebUI::RegisterProfilePrefs( 190 user_prefs::PrefRegistrySyncable* registry) { 191 registry->RegisterDictionaryPref( 192 kExtensionURLOverrides, 193 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 194} 195 196// static 197bool ExtensionWebUI::HandleChromeURLOverride( 198 GURL* url, 199 content::BrowserContext* browser_context) { 200 if (!url->SchemeIs(content::kChromeUIScheme)) 201 return false; 202 203 Profile* profile = Profile::FromBrowserContext(browser_context); 204 const base::DictionaryValue* overrides = 205 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 206 207 std::string url_host = url->host(); 208 const base::ListValue* url_list = NULL; 209 if (!overrides || !overrides->GetList(url_host, &url_list)) 210 return false; 211 212 extensions::ExtensionRegistry* registry = 213 extensions::ExtensionRegistry::Get(browser_context); 214 const extensions::ExtensionSet& extensions = registry->enabled_extensions(); 215 216 GURL component_url; 217 bool found_component_override = false; 218 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); 224 225 GURL override_url; 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. 232 --i; 233 continue; 234 } 235 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) { 242 continue; 243 } 244 245 if (!extensions::Manifest::IsComponentLocation(extension->location())) { 246 *url = override_url; 247 return true; 248 } 249 250 if (!found_component_override) { 251 found_component_override = true; 252 component_url = override_url; 253 } 254 } 255 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; 260 return true; 261 } 262 263 return false; 264} 265 266// static 267bool 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); 272 if (!overrides) 273 return false; 274 275 // Find the reverse mapping based on the given URL. For example this maps the 276 // internal URL 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(); 280 it.Advance()) { 281 const base::ListValue* url_list = NULL; 282 if (!it.value().GetAsList(&url_list)) 283 continue; 284 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)) 289 continue; 290 if (StartsWithASCII(url->spec(), override, true)) { 291 GURL original_url(content::kChromeUIScheme + std::string("://") + 292 it.key() + url->spec().substr(override.length())); 293 *url = original_url; 294 return true; 295 } 296 } 297 } 298 299 return false; 300} 301 302// static 303void ExtensionWebUI::RegisterChromeURLOverrides( 304 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 305 if (overrides.empty()) 306 return; 307 308 PrefService* prefs = profile->GetPrefs(); 309 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 310 base::DictionaryValue* all_overrides = update.Get(); 311 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); 321 } else { 322 CleanUpDuplicates(page_overrides); 323 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)) { 329 NOTREACHED(); 330 continue; 331 } 332 if (override_val == iter->second.spec()) 333 break; 334 } 335 // This value is already in the list, leave it alone. 336 if (i != page_overrides->end()) 337 continue; 338 } 339 // Insert the override at the front of the list. Last registered override 340 // wins. 341 page_overrides->Insert(0, new base::StringValue(iter->second.spec())); 342 } 343} 344 345// static 346void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, 347 Profile* profile, 348 base::ListValue* list, 349 const base::Value* override) { 350 size_t index = 0; 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); 358 } 359} 360 361// static 362void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, 363 Profile* profile, 364 const base::Value* override) { 365 if (!override) 366 return; 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. 373 NOTREACHED(); 374 return; 375 } else { 376 UnregisterAndReplaceOverride(page, profile, page_overrides, override); 377 } 378} 379 380// static 381void ExtensionWebUI::UnregisterChromeURLOverrides( 382 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 383 if (overrides.empty()) 384 return; 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. 394 NOTREACHED(); 395 continue; 396 } else { 397 base::StringValue override(iter->second.spec()); 398 UnregisterAndReplaceOverride(iter->first, profile, 399 page_overrides, &override); 400 } 401 } 402} 403 404// static 405void ExtensionWebUI::GetFaviconForURL( 406 Profile* profile, 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(); 412 if (!service) { 413 RunFaviconCallbackAsync(callback, gfx::Image()); 414 return; 415 } 416 const Extension* extension = service->extensions()->GetByID(page_url.host()); 417 if (!extension) { 418 RunFaviconCallbackAsync(callback, gfx::Image()); 419 return; 420 } 421 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, 434 pixel_size, 435 ExtensionIconSet::MATCH_BIGGER); 436 437 info_list.push_back( 438 extensions::ImageLoader::ImageRepresentation( 439 icon_resource, 440 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 441 gfx::Size(pixel_size, pixel_size), 442 scale_factors[i])); 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