1 // Copyright (c) 2011 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/string_util.h" 11 #include "base/utf_string_conversions.h" 12 #include "chrome/browser/extensions/extension_bookmark_manager_api.h" 13 #include "chrome/browser/extensions/extension_service.h" 14 #include "chrome/browser/extensions/image_loading_tracker.h" 15 #include "chrome/browser/prefs/pref_service.h" 16 #include "chrome/browser/prefs/scoped_user_pref_update.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/ui/browser.h" 19 #include "chrome/browser/ui/browser_list.h" 20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 21 #include "chrome/common/chrome_switches.h" 22 #include "chrome/common/extensions/extension.h" 23 #include "chrome/common/extensions/extension_constants.h" 24 #include "chrome/common/extensions/extension_icon_set.h" 25 #include "chrome/common/extensions/extension_resource.h" 26 #include "chrome/common/url_constants.h" 27 #include "content/browser/renderer_host/render_widget_host_view.h" 28 #include "content/browser/tab_contents/tab_contents.h" 29 #include "content/common/bindings_policy.h" 30 #include "content/common/page_transition_types.h" 31 #include "net/base/file_stream.h" 32 #include "third_party/skia/include/core/SkBitmap.h" 33 #include "ui/gfx/codec/png_codec.h" 34 #include "ui/gfx/favicon_size.h" 35 36 namespace { 37 38 // De-dupes the items in |list|. Assumes the values are strings. 39 void CleanUpDuplicates(ListValue* list) { 40 std::set<std::string> seen_values; 41 42 // Loop backwards as we may be removing items. 43 for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) { 44 std::string value; 45 if (!list->GetString(i, &value)) { 46 NOTREACHED(); 47 continue; 48 } 49 50 if (seen_values.find(value) == seen_values.end()) 51 seen_values.insert(value); 52 else 53 list->Remove(i, NULL); 54 } 55 } 56 57 // Helper class that is used to track the loading of the favicon of an 58 // extension. 59 class ExtensionWebUIImageLoadingTracker : public ImageLoadingTracker::Observer { 60 public: 61 ExtensionWebUIImageLoadingTracker(Profile* profile, 62 FaviconService::GetFaviconRequest* request, 63 const GURL& page_url) 64 : ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)), 65 request_(request), 66 extension_(NULL) { 67 // Even when the extensions service is enabled by default, it's still 68 // disabled in incognito mode. 69 ExtensionService* service = profile->GetExtensionService(); 70 if (service) 71 extension_ = service->GetExtensionByURL(page_url); 72 } 73 74 void Init() { 75 if (extension_) { 76 ExtensionResource icon_resource = 77 extension_->GetIconResource(Extension::EXTENSION_ICON_BITTY, 78 ExtensionIconSet::MATCH_EXACTLY); 79 80 tracker_.LoadImage(extension_, icon_resource, 81 gfx::Size(kFaviconSize, kFaviconSize), 82 ImageLoadingTracker::DONT_CACHE); 83 } else { 84 ForwardResult(NULL); 85 } 86 } 87 88 virtual void OnImageLoaded(SkBitmap* image, const ExtensionResource& resource, 89 int index) { 90 if (image) { 91 std::vector<unsigned char> image_data; 92 if (!gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_data)) { 93 NOTREACHED() << "Could not encode extension favicon"; 94 } 95 ForwardResult(RefCountedBytes::TakeVector(&image_data)); 96 } else { 97 ForwardResult(NULL); 98 } 99 } 100 101 private: 102 ~ExtensionWebUIImageLoadingTracker() {} 103 104 // Forwards the result on the request. If no favicon was available then 105 // |icon_data| may be backed by NULL. Once the result has been forwarded the 106 // instance is deleted. 107 void ForwardResult(scoped_refptr<RefCountedMemory> icon_data) { 108 history::FaviconData favicon; 109 favicon.known_icon = icon_data.get() != NULL && icon_data->size() > 0; 110 favicon.image_data = icon_data; 111 favicon.icon_type = history::FAVICON; 112 request_->ForwardResultAsync( 113 FaviconService::FaviconDataCallback::TupleType(request_->handle(), 114 favicon)); 115 delete this; 116 } 117 118 ImageLoadingTracker tracker_; 119 scoped_refptr<FaviconService::GetFaviconRequest> request_; 120 const Extension* extension_; 121 122 DISALLOW_COPY_AND_ASSIGN(ExtensionWebUIImageLoadingTracker); 123 }; 124 125 } // namespace 126 127 const char ExtensionWebUI::kExtensionURLOverrides[] = 128 "extensions.chrome_url_overrides"; 129 130 ExtensionWebUI::ExtensionWebUI(TabContents* tab_contents, const GURL& url) 131 : WebUI(tab_contents), 132 url_(url) { 133 ExtensionService* service = tab_contents->profile()->GetExtensionService(); 134 const Extension* extension = service->GetExtensionByURL(url); 135 if (!extension) 136 extension = service->GetExtensionByWebExtent(url); 137 DCHECK(extension); 138 // Only hide the url for internal pages (e.g. chrome-extension or packaged 139 // component apps like bookmark manager. 140 should_hide_url_ = !extension->is_hosted_app(); 141 142 bindings_ = BindingsPolicy::EXTENSION; 143 // Bind externalHost to Extension WebUI loaded in Chrome Frame. 144 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); 145 if (browser_command_line.HasSwitch(switches::kChromeFrame)) 146 bindings_ |= BindingsPolicy::EXTERNAL_HOST; 147 // For chrome:// overrides, some of the defaults are a little different. 148 GURL effective_url = tab_contents->GetURL(); 149 if (effective_url.SchemeIs(chrome::kChromeUIScheme) && 150 effective_url.host() == chrome::kChromeUINewTabHost) { 151 focus_location_bar_by_default_ = true; 152 } 153 } 154 155 ExtensionWebUI::~ExtensionWebUI() {} 156 157 void ExtensionWebUI::ResetExtensionFunctionDispatcher( 158 RenderViewHost* render_view_host) { 159 // TODO(jcivelli): http://crbug.com/60608 we should get the URL out of the 160 // active entry of the navigation controller. 161 extension_function_dispatcher_.reset( 162 ExtensionFunctionDispatcher::Create(render_view_host, this, url_)); 163 DCHECK(extension_function_dispatcher_.get()); 164 } 165 166 void ExtensionWebUI::ResetExtensionBookmarkManagerEventRouter() { 167 // Hack: A few things we specialize just for the bookmark manager. 168 if (extension_function_dispatcher_->extension_id() == 169 extension_misc::kBookmarkManagerId) { 170 extension_bookmark_manager_event_router_.reset( 171 new ExtensionBookmarkManagerEventRouter(GetProfile(), tab_contents())); 172 173 link_transition_type_ = PageTransition::AUTO_BOOKMARK; 174 } 175 } 176 177 void ExtensionWebUI::RenderViewCreated(RenderViewHost* render_view_host) { 178 ResetExtensionFunctionDispatcher(render_view_host); 179 ResetExtensionBookmarkManagerEventRouter(); 180 } 181 182 void ExtensionWebUI::RenderViewReused(RenderViewHost* render_view_host) { 183 ResetExtensionFunctionDispatcher(render_view_host); 184 ResetExtensionBookmarkManagerEventRouter(); 185 } 186 187 void ExtensionWebUI::ProcessWebUIMessage( 188 const ExtensionHostMsg_DomMessage_Params& params) { 189 extension_function_dispatcher_->HandleRequest(params); 190 } 191 192 Browser* ExtensionWebUI::GetBrowser() { 193 TabContents* contents = tab_contents(); 194 TabContentsIterator tab_iterator; 195 for (; !tab_iterator.done(); ++tab_iterator) { 196 if (contents == (*tab_iterator)->tab_contents()) 197 return tab_iterator.browser(); 198 } 199 200 return NULL; 201 } 202 203 TabContents* ExtensionWebUI::associated_tab_contents() const { 204 return tab_contents(); 205 } 206 207 ExtensionBookmarkManagerEventRouter* 208 ExtensionWebUI::extension_bookmark_manager_event_router() { 209 return extension_bookmark_manager_event_router_.get(); 210 } 211 212 gfx::NativeWindow ExtensionWebUI::GetCustomFrameNativeWindow() { 213 if (GetBrowser()) 214 return NULL; 215 216 // If there was no browser associated with the function dispatcher delegate, 217 // then this WebUI may be hosted in an ExternalTabContainer, and a framing 218 // window will be accessible through the tab_contents. 219 TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate(); 220 if (tab_contents_delegate) 221 return tab_contents_delegate->GetFrameNativeWindow(); 222 else 223 return NULL; 224 } 225 226 gfx::NativeView ExtensionWebUI::GetNativeViewOfHost() { 227 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 228 return rwhv ? rwhv->GetNativeView() : NULL; 229 } 230 231 //////////////////////////////////////////////////////////////////////////////// 232 // chrome:// URL overrides 233 234 // static 235 void ExtensionWebUI::RegisterUserPrefs(PrefService* prefs) { 236 prefs->RegisterDictionaryPref(kExtensionURLOverrides); 237 } 238 239 // static 240 bool ExtensionWebUI::HandleChromeURLOverride(GURL* url, Profile* profile) { 241 if (!url->SchemeIs(chrome::kChromeUIScheme)) 242 return false; 243 244 const DictionaryValue* overrides = 245 profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); 246 std::string page = url->host(); 247 ListValue* url_list; 248 if (!overrides || !overrides->GetList(page, &url_list)) 249 return false; 250 251 ExtensionService* service = profile->GetExtensionService(); 252 253 size_t i = 0; 254 while (i < url_list->GetSize()) { 255 Value* val = NULL; 256 url_list->Get(i, &val); 257 258 // Verify that the override value is good. If not, unregister it and find 259 // the next one. 260 std::string override; 261 if (!val->GetAsString(&override)) { 262 NOTREACHED(); 263 UnregisterChromeURLOverride(page, profile, val); 264 continue; 265 } 266 GURL extension_url(override); 267 if (!extension_url.is_valid()) { 268 NOTREACHED(); 269 UnregisterChromeURLOverride(page, profile, val); 270 continue; 271 } 272 273 // Verify that the extension that's being referred to actually exists. 274 const Extension* extension = service->GetExtensionByURL(extension_url); 275 if (!extension) { 276 // This can currently happen if you use --load-extension one run, and 277 // then don't use it the next. It could also happen if an extension 278 // were deleted directly from the filesystem, etc. 279 LOG(WARNING) << "chrome URL override present for non-existant extension"; 280 UnregisterChromeURLOverride(page, profile, val); 281 continue; 282 } 283 284 // We can't handle chrome-extension URLs in incognito mode unless the 285 // extension uses split mode. 286 bool incognito_override_allowed = 287 extension->incognito_split_mode() && 288 service->IsIncognitoEnabled(extension->id()); 289 if (profile->IsOffTheRecord() && !incognito_override_allowed) { 290 ++i; 291 continue; 292 } 293 294 *url = extension_url; 295 return true; 296 } 297 return false; 298 } 299 300 // static 301 void ExtensionWebUI::RegisterChromeURLOverrides( 302 Profile* profile, const Extension::URLOverrideMap& overrides) { 303 if (overrides.empty()) 304 return; 305 306 PrefService* prefs = profile->GetPrefs(); 307 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 308 DictionaryValue* all_overrides = update.Get(); 309 310 // For each override provided by the extension, add it to the front of 311 // the override list if it's not already in the list. 312 Extension::URLOverrideMap::const_iterator iter = overrides.begin(); 313 for (; iter != overrides.end(); ++iter) { 314 const std::string& key = iter->first; 315 ListValue* page_overrides; 316 if (!all_overrides->GetList(key, &page_overrides)) { 317 page_overrides = new ListValue(); 318 all_overrides->Set(key, page_overrides); 319 } else { 320 CleanUpDuplicates(page_overrides); 321 322 // Verify that the override isn't already in the list. 323 ListValue::iterator i = page_overrides->begin(); 324 for (; i != page_overrides->end(); ++i) { 325 std::string override_val; 326 if (!(*i)->GetAsString(&override_val)) { 327 NOTREACHED(); 328 continue; 329 } 330 if (override_val == iter->second.spec()) 331 break; 332 } 333 // This value is already in the list, leave it alone. 334 if (i != page_overrides->end()) 335 continue; 336 } 337 // Insert the override at the front of the list. Last registered override 338 // wins. 339 page_overrides->Insert(0, new StringValue(iter->second.spec())); 340 } 341 } 342 343 // static 344 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page, 345 Profile* profile, ListValue* list, Value* override) { 346 int index = list->Remove(*override); 347 if (index == 0) { 348 // This is the active override, so we need to find all existing 349 // tabs for this override and get them to reload the original URL. 350 for (TabContentsIterator iterator; !iterator.done(); ++iterator) { 351 TabContents* tab = (*iterator)->tab_contents(); 352 if (tab->profile() != profile) 353 continue; 354 355 GURL url = tab->GetURL(); 356 if (!url.SchemeIs(chrome::kChromeUIScheme) || url.host() != page) 357 continue; 358 359 // Don't use Reload() since |url| isn't the same as the internal URL 360 // that NavigationController has. 361 tab->controller().LoadURL(url, url, PageTransition::RELOAD); 362 } 363 } 364 } 365 366 // static 367 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page, 368 Profile* profile, Value* override) { 369 if (!override) 370 return; 371 PrefService* prefs = profile->GetPrefs(); 372 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 373 DictionaryValue* all_overrides = update.Get(); 374 ListValue* page_overrides; 375 if (!all_overrides->GetList(page, &page_overrides)) { 376 // If it's being unregistered, it should already be in the list. 377 NOTREACHED(); 378 return; 379 } else { 380 UnregisterAndReplaceOverride(page, profile, page_overrides, override); 381 } 382 } 383 384 // static 385 void ExtensionWebUI::UnregisterChromeURLOverrides( 386 Profile* profile, const Extension::URLOverrideMap& overrides) { 387 if (overrides.empty()) 388 return; 389 PrefService* prefs = profile->GetPrefs(); 390 DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); 391 DictionaryValue* all_overrides = update.Get(); 392 Extension::URLOverrideMap::const_iterator iter = overrides.begin(); 393 for (; iter != overrides.end(); ++iter) { 394 const std::string& page = iter->first; 395 ListValue* page_overrides; 396 if (!all_overrides->GetList(page, &page_overrides)) { 397 // If it's being unregistered, it should already be in the list. 398 NOTREACHED(); 399 continue; 400 } else { 401 StringValue override(iter->second.spec()); 402 UnregisterAndReplaceOverride(iter->first, profile, 403 page_overrides, &override); 404 } 405 } 406 } 407 408 // static 409 void ExtensionWebUI::GetFaviconForURL(Profile* profile, 410 FaviconService::GetFaviconRequest* request, const GURL& page_url) { 411 // tracker deletes itself when done. 412 ExtensionWebUIImageLoadingTracker* tracker = 413 new ExtensionWebUIImageLoadingTracker(profile, request, page_url); 414 tracker->Init(); 415 } 416