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. 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. 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. 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 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 157 ExtensionWebUI::~ExtensionWebUI() {} 158 159 extensions::BookmarkManagerPrivateEventRouter* 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 168 void ExtensionWebUI::RegisterProfilePrefs( 169 user_prefs::PrefRegistrySyncable* registry) { 170 registry->RegisterDictionaryPref( 171 kExtensionURLOverrides, 172 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 173 } 174 175 // static 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 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 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 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 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 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 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