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_tab_util.h" 17#include "chrome/browser/extensions/extension_util.h" 18#include "chrome/browser/favicon/favicon_service.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/common/chrome_switches.h" 21#include "chrome/common/extensions/extension_constants.h" 22#include "chrome/common/url_constants.h" 23#include "components/favicon_base/favicon_util.h" 24#include "components/pref_registry/pref_registry_syncable.h" 25#include "content/public/browser/navigation_controller.h" 26#include "content/public/browser/web_contents.h" 27#include "content/public/browser/web_ui.h" 28#include "content/public/common/bindings_policy.h" 29#include "extensions/browser/extension_registry.h" 30#include "extensions/browser/image_loader.h" 31#include "extensions/common/extension.h" 32#include "extensions/common/extension_icon_set.h" 33#include "extensions/common/extension_resource.h" 34#include "extensions/common/manifest_handlers/icons_handler.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/base/page_transition_types.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 ui::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 favicon_base::FaviconResultsCallback& callback, 91 const gfx::Image& image) { 92 std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results = 93 new std::vector<favicon_base::FaviconRawBitmapResult>(); 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 favicon_base::FaviconRawBitmapResult 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 = favicon_base::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 const Extension* extension = extensions::ExtensionRegistry::Get( 158 profile)->enabled_extensions().GetExtensionOrAppByURL(url); 159 DCHECK(extension); 160 161 // The base class defaults to enabling WebUI bindings, but we don't need 162 // those (this is also reflected in ChromeWebUIControllerFactory:: 163 // UseWebUIBindingsForURL). 164 int bindings = 0; 165 web_ui->SetBindings(bindings); 166 167 // Hack: A few things we specialize just for the bookmark manager. 168 if (extension->id() == extension_misc::kBookmarkManagerId) { 169 bookmark_manager_private_drag_event_router_.reset( 170 new extensions::BookmarkManagerPrivateDragEventRouter( 171 profile, web_ui->GetWebContents())); 172 173 web_ui->SetLinkTransitionType(ui::PAGE_TRANSITION_AUTO_BOOKMARK); 174 } 175} 176 177ExtensionWebUI::~ExtensionWebUI() {} 178 179extensions::BookmarkManagerPrivateDragEventRouter* 180ExtensionWebUI::bookmark_manager_private_drag_event_router() { 181 return bookmark_manager_private_drag_event_router_.get(); 182} 183 184//////////////////////////////////////////////////////////////////////////////// 185// chrome:// URL overrides 186 187// static 188void ExtensionWebUI::RegisterProfilePrefs( 189 user_prefs::PrefRegistrySyncable* registry) { 190 registry->RegisterDictionaryPref( 191 kExtensionURLOverrides, 192 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 193} 194 195// static 196bool ExtensionWebUI::HandleChromeURLOverride( 197 GURL* url, 198 content::BrowserContext* browser_context) { 199 if (!url->SchemeIs(content::kChromeUIScheme)) 200 return false; 201 202 Profile* profile = Profile::FromBrowserContext(browser_context); 203 const base::DictionaryValue* overrides = 204 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 205 206 std::string url_host = url->host(); 207 const base::ListValue* url_list = NULL; 208 if (!overrides || !overrides->GetList(url_host, &url_list)) 209 return false; 210 211 extensions::ExtensionRegistry* registry = 212 extensions::ExtensionRegistry::Get(browser_context); 213 const extensions::ExtensionSet& extensions = registry->enabled_extensions(); 214 215 GURL component_url; 216 bool found_component_override = false; 217 218 // Iterate over the URL list looking for a suitable override. If a 219 // valid non-component override is encountered it is chosen immediately. 220 for (size_t i = 0; i < url_list->GetSize(); ++i) { 221 const base::Value* val = NULL; 222 url_list->Get(i, &val); 223 224 GURL override_url; 225 const Extension* extension; 226 if (!ValidateOverrideURL( 227 val, *url, extensions, &override_url, &extension)) { 228 LOG(WARNING) << "Invalid chrome URL override"; 229 UnregisterChromeURLOverride(url_host, profile, val); 230 // The above Unregister call will remove this item from url_list. 231 --i; 232 continue; 233 } 234 235 // We can't handle chrome-extension URLs in incognito mode unless the 236 // extension uses split mode. 237 bool incognito_override_allowed = 238 extensions::IncognitoInfo::IsSplitMode(extension) && 239 extensions::util::IsIncognitoEnabled(extension->id(), profile); 240 if (profile->IsOffTheRecord() && !incognito_override_allowed) { 241 continue; 242 } 243 244 if (!extensions::Manifest::IsComponentLocation(extension->location())) { 245 *url = override_url; 246 return true; 247 } 248 249 if (!found_component_override) { 250 found_component_override = true; 251 component_url = override_url; 252 } 253 } 254 255 // If no other non-component overrides were found, use the first known 256 // component override, if any. 257 if (found_component_override) { 258 *url = component_url; 259 return true; 260 } 261 262 return false; 263} 264 265// static 266bool ExtensionWebUI::HandleChromeURLOverrideReverse( 267 GURL* url, content::BrowserContext* browser_context) { 268 Profile* profile = Profile::FromBrowserContext(browser_context); 269 const base::DictionaryValue* overrides = 270 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 271 if (!overrides) 272 return false; 273 274 // Find the reverse mapping based on the given URL. For example this maps the 275 // internal URL 276 // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to 277 // chrome://bookmarks/#1 for display in the omnibox. 278 for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd(); 279 it.Advance()) { 280 const base::ListValue* url_list = NULL; 281 if (!it.value().GetAsList(&url_list)) 282 continue; 283 284 for (base::ListValue::const_iterator it2 = url_list->begin(); 285 it2 != url_list->end(); ++it2) { 286 std::string override; 287 if (!(*it2)->GetAsString(&override)) 288 continue; 289 if (StartsWithASCII(url->spec(), override, true)) { 290 GURL original_url(content::kChromeUIScheme + std::string("://") + 291 it.key() + url->spec().substr(override.length())); 292 *url = original_url; 293 return true; 294 } 295 } 296 } 297 298 return false; 299} 300 301// static 302void ExtensionWebUI::RegisterChromeURLOverrides( 303 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 304 if (overrides.empty()) 305 return; 306 307 PrefService* prefs = profile->GetPrefs(); 308 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 309 base::DictionaryValue* all_overrides = update.Get(); 310 311 // For each override provided by the extension, add it to the front of 312 // the override list if it's not already in the list. 313 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); 314 for (; iter != overrides.end(); ++iter) { 315 const std::string& key = iter->first; 316 base::ListValue* page_overrides = NULL; 317 if (!all_overrides->GetList(key, &page_overrides)) { 318 page_overrides = new base::ListValue(); 319 all_overrides->Set(key, page_overrides); 320 } else { 321 CleanUpDuplicates(page_overrides); 322 323 // Verify that the override isn't already in the list. 324 base::ListValue::iterator i = page_overrides->begin(); 325 for (; i != page_overrides->end(); ++i) { 326 std::string override_val; 327 if (!(*i)->GetAsString(&override_val)) { 328 NOTREACHED(); 329 continue; 330 } 331 if (override_val == iter->second.spec()) 332 break; 333 } 334 // This value is already in the list, leave it alone. 335 if (i != page_overrides->end()) 336 continue; 337 } 338 // Insert the override at the front of the list. Last registered override 339 // wins. 340 page_overrides->Insert(0, new base::StringValue(iter->second.spec())); 341 } 342} 343 344// static 345void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, 346 Profile* profile, 347 base::ListValue* list, 348 const base::Value* override) { 349 size_t index = 0; 350 bool found = list->Remove(*override, &index); 351 if (found && index == 0) { 352 // This is the active override, so we need to find all existing 353 // tabs for this override and get them to reload the original URL. 354 base::Callback<void(WebContents*)> callback = 355 base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile); 356 extensions::ExtensionTabUtil::ForEachTab(callback); 357 } 358} 359 360// static 361void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, 362 Profile* profile, 363 const base::Value* override) { 364 if (!override) 365 return; 366 PrefService* prefs = profile->GetPrefs(); 367 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 368 base::DictionaryValue* all_overrides = update.Get(); 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 return; 374 } else { 375 UnregisterAndReplaceOverride(page, profile, page_overrides, override); 376 } 377} 378 379// static 380void ExtensionWebUI::UnregisterChromeURLOverrides( 381 Profile* profile, const URLOverrides::URLOverrideMap& overrides) { 382 if (overrides.empty()) 383 return; 384 PrefService* prefs = profile->GetPrefs(); 385 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 386 base::DictionaryValue* all_overrides = update.Get(); 387 URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin(); 388 for (; iter != overrides.end(); ++iter) { 389 const std::string& page = iter->first; 390 base::ListValue* page_overrides = NULL; 391 if (!all_overrides->GetList(page, &page_overrides)) { 392 // If it's being unregistered, it should already be in the list. 393 NOTREACHED(); 394 continue; 395 } else { 396 base::StringValue override(iter->second.spec()); 397 UnregisterAndReplaceOverride(iter->first, profile, 398 page_overrides, &override); 399 } 400 } 401} 402 403// static 404void ExtensionWebUI::GetFaviconForURL( 405 Profile* profile, 406 const GURL& page_url, 407 const favicon_base::FaviconResultsCallback& callback) { 408 const Extension* extension = extensions::ExtensionRegistry::Get( 409 profile)->enabled_extensions().GetByID(page_url.host()); 410 if (!extension) { 411 RunFaviconCallbackAsync(callback, gfx::Image()); 412 return; 413 } 414 415 // Fetch resources for all supported scale factors for which there are 416 // resources. Load image reps for all supported scale factors (in addition to 417 // 1x) immediately instead of in an as needed fashion to be consistent with 418 // how favicons are requested for chrome:// and page URLs. 419 const std::vector<float>& favicon_scales = favicon_base::GetFaviconScales(); 420 std::vector<extensions::ImageLoader::ImageRepresentation> info_list; 421 for (size_t i = 0; i < favicon_scales.size(); ++i) { 422 float scale = favicon_scales[i]; 423 int pixel_size = static_cast<int>(gfx::kFaviconSize * scale); 424 extensions::ExtensionResource icon_resource = 425 extensions::IconsInfo::GetIconResource(extension, 426 pixel_size, 427 ExtensionIconSet::MATCH_BIGGER); 428 429 ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale); 430 info_list.push_back(extensions::ImageLoader::ImageRepresentation( 431 icon_resource, 432 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, 433 gfx::Size(pixel_size, pixel_size), 434 resource_scale_factor)); 435 } 436 437 // LoadImagesAsync actually can run callback synchronously. We want to force 438 // async. 439 extensions::ImageLoader::Get(profile)->LoadImagesAsync( 440 extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback)); 441} 442