extension_web_ui.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/string_util.h" 12#include "base/utf_string_conversions.h" 13#include "chrome/browser/bookmarks/bookmark_manager_extension_api.h" 14#include "chrome/browser/extensions/extension_service.h" 15#include "chrome/browser/extensions/extension_tab_util.h" 16#include "chrome/browser/extensions/image_loading_tracker.h" 17#include "chrome/browser/prefs/pref_service.h" 18#include "chrome/browser/prefs/scoped_user_pref_update.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/browser/ui/browser.h" 21#include "chrome/browser/ui/tab_contents/tab_contents.h" 22#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 23#include "chrome/common/chrome_switches.h" 24#include "chrome/common/extensions/extension.h" 25#include "chrome/common/extensions/extension_constants.h" 26#include "chrome/common/extensions/extension_icon_set.h" 27#include "chrome/common/extensions/extension_resource.h" 28#include "chrome/common/url_constants.h" 29#include "content/public/browser/navigation_controller.h" 30#include "content/public/browser/web_contents.h" 31#include "content/public/browser/web_ui.h" 32#include "content/public/common/bindings_policy.h" 33#include "content/public/common/page_transition_types.h" 34#include "net/base/file_stream.h" 35#include "third_party/skia/include/core/SkBitmap.h" 36#include "ui/gfx/codec/png_codec.h" 37#include "ui/gfx/favicon_size.h" 38 39using content::WebContents; 40using extensions::Extension; 41 42namespace { 43 44// De-dupes the items in |list|. Assumes the values are strings. 45void CleanUpDuplicates(ListValue* list) { 46 std::set<std::string> seen_values; 47 48 // Loop backwards as we may be removing items. 49 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { 50 std::string value; 51 if (!list->GetString(i, &value)) { 52 NOTREACHED(); 53 continue; 54 } 55 56 if (seen_values.find(value) == seen_values.end()) 57 seen_values.insert(value); 58 else 59 list->Remove(i, NULL); 60 } 61} 62 63// Reloads the page in |web_contents| if it uses the same profile as |profile| 64// and if the current URL is a chrome URL. 65void UnregisterAndReplaceOverrideForWebContents( 66 const std::string& page, Profile* profile, WebContents* web_contents) { 67 if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile) 68 return; 69 70 GURL url = web_contents->GetURL(); 71 if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page) 72 return; 73 74 // Don't use Reload() since |url| isn't the same as the internal URL that 75 // NavigationController has. 76 web_contents->GetController().LoadURL( 77 url, content::Referrer(url, WebKit::WebReferrerPolicyDefault), 78 content::PAGE_TRANSITION_RELOAD, std::string()); 79} 80 81// Helper class that is used to track the loading of the favicon of an 82// extension. 83class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer { 84 public: 85 ExtensionWebUIImageLoadingTracker(Profile* profile, 86 FaviconService::GetFaviconRequest* request, 87 const GURL& page_url) 88 : ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)), 89 request_(request), 90 extension_(NULL) { 91 // Even when the extensions service is enabled by default, it's still 92 // disabled in incognito mode. 93 ExtensionService* service = profile->GetExtensionService(); 94 if (service) 95 extension_ = service->extensions()->GetByID(page_url.host()); 96 } 97 98 void Init() { 99 if (extension_) { 100 // Fetch resources for all supported scale factors for which there are 101 // resources. Load image reps for all supported scale factors immediately 102 // instead of in an as needed fashion to be consistent with how favicons 103 // are requested for chrome:// and page URLs. 104 const std::vector<ui::ScaleFactor>& scale_factors = 105 ui::GetSupportedScaleFactors(); 106 std::vector<ImageLoadingTracker::ImageRepresentation> info_list; 107 for (size_t i = 0; i < scale_factors.size(); ++i) { 108 float scale = ui::GetScaleFactorScale(scale_factors[i]); 109 int pixel_size = static_cast<int>(gfx::kFaviconSize * scale); 110 ExtensionResource icon_resource = 111 extension_->GetIconResource(pixel_size, 112 ExtensionIconSet::MATCH_BIGGER); 113 114 info_list.push_back( 115 ImageLoadingTracker::ImageRepresentation( 116 icon_resource, 117 ImageLoadingTracker::ImageRepresentation::ALWAYS_RESIZE, 118 gfx::Size(pixel_size, pixel_size), 119 scale_factors[i])); 120 } 121 122 tracker_.LoadImages(extension_, info_list, 123 ImageLoadingTracker::DONT_CACHE); 124 } else { 125 ForwardResult(gfx::Image()); 126 } 127 } 128 129 virtual void OnImageLoaded(const gfx::Image& image, 130 const std::string& extension_id, 131 int index) OVERRIDE { 132 ForwardResult(image); 133 } 134 135 private: 136 ~ExtensionWebUIImageLoadingTracker() {} 137 138 // Forwards the result of the request. If no favicon was available then 139 // |image| will be empty. Once the result has been forwarded the instance is 140 // deleted. 141 void ForwardResult(const gfx::Image& image) { 142 std::vector<history::FaviconBitmapResult> favicon_bitmap_results; 143 const std::vector<gfx::ImageSkiaRep>& image_reps = 144 image.AsImageSkia().image_reps(); 145 for (size_t i = 0; i < image_reps.size(); ++i) { 146 const gfx::ImageSkiaRep& image_rep = image_reps[i]; 147 scoped_refptr<base::RefCountedBytes> bitmap_data( 148 new base::RefCountedBytes()); 149 if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(), 150 false, 151 &bitmap_data->data())) { 152 history::FaviconBitmapResult bitmap_result; 153 bitmap_result.bitmap_data = bitmap_data; 154 bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(), 155 image_rep.pixel_height()); 156 // Leave |bitmap_result|'s icon URL as the default of GURL(). 157 bitmap_result.icon_type = history::FAVICON; 158 159 favicon_bitmap_results.push_back(bitmap_result); 160 } else { 161 NOTREACHED() << "Could not encode extension favicon"; 162 } 163 } 164 165 // Populate IconURLSizesMap such that all the icon URLs in 166 // |favicon_bitmap_results| are present in |icon_url_sizes|. 167 // Populate the favicon sizes with the relevant pixel sizes in the 168 // extension's icon set. 169 history::IconURLSizesMap icon_url_sizes; 170 for (size_t i = 0; i < favicon_bitmap_results.size(); ++i) { 171 const history::FaviconBitmapResult& bitmap_result = 172 favicon_bitmap_results[i]; 173 const GURL& icon_url = bitmap_result.icon_url; 174 icon_url_sizes[icon_url].push_back(bitmap_result.pixel_size); 175 } 176 177 request_->ForwardResultAsync(request_->handle(), favicon_bitmap_results, 178 icon_url_sizes); 179 delete this; 180 } 181 182 ImageLoadingTracker tracker_; 183 scoped_refptr<FaviconService::GetFaviconRequest> request_; 184 const Extension* extension_; 185 186 DISALLOW_COPY_AND_ASSIGN(ExtensionWebUIImageLoadingTracker); 187}; 188 189} // namespace 190 191const char ExtensionWebUI::kExtensionURLOverrides[] = 192 "extensions.chrome_url_overrides"; 193 194ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url) 195 : WebUIController(web_ui), 196 url_(url) { 197 Profile* profile = Profile::FromWebUI(web_ui); 198 ExtensionService* service = profile->GetExtensionService(); 199 const Extension* extension = 200 service->extensions()->GetExtensionOrAppByURL(ExtensionURLInfo(url)); 201 DCHECK(extension); 202 // Only hide the url for internal pages (e.g. chrome-extension or packaged 203 // component apps like bookmark manager. 204 bool should_hide_url = !extension->is_hosted_app(); 205 206 // The base class defaults to enabling WebUI bindings, but we don't need 207 // those (this is also reflected in ChromeWebUIControllerFactory:: 208 // UseWebUIBindingsForURL). 209 int bindings = 0; 210 211 // Bind externalHost to Extension WebUI loaded in Chrome Frame. 212 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); 213 if (browser_command_line.HasSwitch(switches::kChromeFrame)) 214 bindings |= content::BINDINGS_POLICY_EXTERNAL_HOST; 215 // For chrome:// overrides, some of the defaults are a little different. 216 GURL effective_url = web_ui->GetWebContents()->GetURL(); 217 if (effective_url.SchemeIs(chrome::kChromeUIScheme)) { 218 if (effective_url.host() == chrome::kChromeUINewTabHost) { 219 web_ui->FocusLocationBarByDefault(); 220 } else { 221 // Current behavior of other chrome:// pages is to display the URL. 222 should_hide_url = false; 223 } 224 } 225 226 if (should_hide_url) 227 web_ui->HideURL(); 228 229 web_ui->SetBindings(bindings); 230 231 // Hack: A few things we specialize just for the bookmark manager. 232 if (extension->id() == extension_misc::kBookmarkManagerId) { 233 bookmark_manager_extension_event_router_.reset( 234 new BookmarkManagerExtensionEventRouter(profile, 235 web_ui->GetWebContents())); 236 237 web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK); 238 } 239} 240 241ExtensionWebUI::~ExtensionWebUI() {} 242 243BookmarkManagerExtensionEventRouter* 244ExtensionWebUI::bookmark_manager_extension_event_router() { 245 return bookmark_manager_extension_event_router_.get(); 246} 247 248//////////////////////////////////////////////////////////////////////////////// 249// chrome:// URL overrides 250 251// static 252void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) { 253 prefs->RegisterDictionaryPref(kExtensionURLOverrides, 254 PrefService::UNSYNCABLE_PREF); 255} 256 257// static 258bool ExtensionWebUI::HandleChromeURLOverride( 259 GURL* url, content::BrowserContext* browser_context) { 260 if (!url->SchemeIs(chrome::kChromeUIScheme)) 261 return false; 262 263 Profile* profile = Profile::FromBrowserContext(browser_context); 264 const DictionaryValue* overrides = 265 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 266 std::string page = url->host(); 267 const ListValue* url_list; 268 if (!overrides || !overrides->GetList(page, &url_list)) 269 return false; 270 271 ExtensionService* service = profile->GetExtensionService(); 272 273 size_t i = 0; 274 while (i < url_list->GetSize()) { 275 const Value* val = NULL; 276 url_list->Get(i, &val); 277 278 // Verify that the override value is good. If not, unregister it and find 279 // the next one. 280 std::string override; 281 if (!val->GetAsString(&override)) { 282 NOTREACHED(); 283 UnregisterChromeURLOverride(page, profile, val); 284 continue; 285 } 286 287 if (!url->query().empty()) 288 override += "?" + url->query(); 289 if (!url->ref().empty()) 290 override += "#" + url->ref(); 291 GURL extension_url(override); 292 if (!extension_url.is_valid()) { 293 NOTREACHED(); 294 UnregisterChromeURLOverride(page, profile, val); 295 continue; 296 } 297 298 // Verify that the extension that's being referred to actually exists. 299 const Extension* extension = 300 service->extensions()->GetByID(extension_url.host()); 301 if (!extension) { 302 // This can currently happen if you use --load-extension one run, and 303 // then don't use it the next. It could also happen if an extension 304 // were deleted directly from the filesystem, etc. 305 LOG(WARNING) << "chrome URL override present for non-existant extension"; 306 UnregisterChromeURLOverride(page, profile, val); 307 continue; 308 } 309 310 // We can't handle chrome-extension URLs in incognito mode unless the 311 // extension uses split mode. 312 bool incognito_override_allowed = 313 extension->incognito_split_mode() && 314 service->IsIncognitoEnabled(extension->id()); 315 if (profile->IsOffTheRecord() && !incognito_override_allowed) { 316 ++i; 317 continue; 318 } 319 320 *url = extension_url; 321 return true; 322 } 323 return false; 324} 325 326// static 327bool ExtensionWebUI::HandleChromeURLOverrideReverse( 328 GURL* url, content::BrowserContext* browser_context) { 329 Profile* profile = Profile::FromBrowserContext(browser_context); 330 const DictionaryValue* overrides = 331 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 332 if (!overrides) 333 return false; 334 335 // Find the reverse mapping based on the given URL. For example this maps the 336 // internal URL 337 // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to 338 // chrome://bookmarks/#1 for display in the omnibox. 339 for (DictionaryValue::key_iterator it = overrides->begin_keys(), 340 end = overrides->end_keys(); it != end; ++it) { 341 const ListValue* url_list; 342 if (!overrides->GetList(*it, &url_list)) 343 continue; 344 345 for (ListValue::const_iterator it2 = url_list->begin(), 346 end2 = url_list->end(); it2 != end2; ++it2) { 347 std::string override; 348 if (!(*it2)->GetAsString(&override)) 349 continue; 350 if (StartsWithASCII(url->spec(), override, true)) { 351 GURL original_url(chrome::kChromeUIScheme + std::string("://") + *it + 352 url->spec().substr(override.length())); 353 *url = original_url; 354 return true; 355 } 356 } 357 } 358 359 return false; 360} 361 362// static 363void ExtensionWebUI::RegisterChromeURLOverrides( 364 Profile* profile, const Extension::URLOverrideMap& overrides) { 365 if (overrides.empty()) 366 return; 367 368 PrefService* prefs = profile->GetPrefs(); 369 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 370 DictionaryValue* all_overrides = update.Get(); 371 372 // For each override provided by the extension, add it to the front of 373 // the override list if it's not already in the list. 374 Extension::URLOverrideMap::const_iterator iter = overrides.begin(); 375 for (; iter != overrides.end(); ++iter) { 376 const std::string& key = iter->first; 377 ListValue* page_overrides; 378 if (!all_overrides->GetList(key, &page_overrides)) { 379 page_overrides = new ListValue(); 380 all_overrides->Set(key, page_overrides); 381 } else { 382 CleanUpDuplicates(page_overrides); 383 384 // Verify that the override isn't already in the list. 385 ListValue::iterator i = page_overrides->begin(); 386 for (; i != page_overrides->end(); ++i) { 387 std::string override_val; 388 if (!(*i)->GetAsString(&override_val)) { 389 NOTREACHED(); 390 continue; 391 } 392 if (override_val == iter->second.spec()) 393 break; 394 } 395 // This value is already in the list, leave it alone. 396 if (i != page_overrides->end()) 397 continue; 398 } 399 // Insert the override at the front of the list. Last registered override 400 // wins. 401 page_overrides->Insert(0, new StringValue(iter->second.spec())); 402 } 403} 404 405// static 406void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, 407 Profile* profile, 408 ListValue* list, 409 const Value* override) { 410 size_t index = 0; 411 bool found = list->Remove(*override, &index); 412 if (found && index == 0) { 413 // This is the active override, so we need to find all existing 414 // tabs for this override and get them to reload the original URL. 415 base::Callback<void(WebContents*)> callback = 416 base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile); 417 ExtensionTabUtil::ForEachTab(callback); 418 } 419} 420 421// static 422void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, 423 Profile* profile, 424 const Value* override) { 425 if (!override) 426 return; 427 PrefService* prefs = profile->GetPrefs(); 428 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 429 DictionaryValue* all_overrides = update.Get(); 430 ListValue* page_overrides; 431 if (!all_overrides->GetList(page, &page_overrides)) { 432 // If it's being unregistered, it should already be in the list. 433 NOTREACHED(); 434 return; 435 } else { 436 UnregisterAndReplaceOverride(page, profile, page_overrides, override); 437 } 438} 439 440// static 441void ExtensionWebUI::UnregisterChromeURLOverrides( 442 Profile* profile, const Extension::URLOverrideMap& overrides) { 443 if (overrides.empty()) 444 return; 445 PrefService* prefs = profile->GetPrefs(); 446 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 447 DictionaryValue* all_overrides = update.Get(); 448 Extension::URLOverrideMap::const_iterator iter = overrides.begin(); 449 for (; iter != overrides.end(); ++iter) { 450 const std::string& page = iter->first; 451 ListValue* page_overrides; 452 if (!all_overrides->GetList(page, &page_overrides)) { 453 // If it's being unregistered, it should already be in the list. 454 NOTREACHED(); 455 continue; 456 } else { 457 StringValue override(iter->second.spec()); 458 UnregisterAndReplaceOverride(iter->first, profile, 459 page_overrides, &override); 460 } 461 } 462} 463 464// static 465void ExtensionWebUI::GetFaviconForURL(Profile* profile, 466 FaviconService::GetFaviconRequest* request, const GURL& page_url) { 467 // tracker deletes itself when done. 468 ExtensionWebUIImageLoadingTracker* tracker = 469 new ExtensionWebUIImageLoadingTracker(profile, request, page_url); 470 tracker->Init(); 471} 472