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