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/extensions_ui.h" 6 7 #include <algorithm> 8 9 #include "base/base64.h" 10 #include "base/callback.h" 11 #include "base/file_util.h" 12 #include "base/memory/singleton.h" 13 #include "base/string_number_conversions.h" 14 #include "base/string_util.h" 15 #include "base/threading/thread.h" 16 #include "base/utf_string_conversions.h" 17 #include "base/version.h" 18 #include "chrome/browser/debugger/devtools_manager.h" 19 #include "chrome/browser/debugger/devtools_toggle_action.h" 20 #include "chrome/browser/extensions/crx_installer.h" 21 #include "chrome/browser/extensions/extension_disabled_infobar_delegate.h" 22 #include "chrome/browser/extensions/extension_error_reporter.h" 23 #include "chrome/browser/extensions/extension_function_dispatcher.h" 24 #include "chrome/browser/extensions/extension_host.h" 25 #include "chrome/browser/extensions/extension_message_service.h" 26 #include "chrome/browser/extensions/extension_service.h" 27 #include "chrome/browser/extensions/extension_updater.h" 28 #include "chrome/browser/google/google_util.h" 29 #include "chrome/browser/prefs/pref_service.h" 30 #include "chrome/browser/profiles/profile.h" 31 #include "chrome/browser/tab_contents/background_contents.h" 32 #include "chrome/browser/ui/browser_list.h" 33 #include "chrome/common/extensions/extension.h" 34 #include "chrome/common/extensions/extension_icon_set.h" 35 #include "chrome/common/extensions/url_pattern.h" 36 #include "chrome/common/extensions/user_script.h" 37 #include "chrome/common/jstemplate_builder.h" 38 #include "chrome/common/pref_names.h" 39 #include "chrome/common/url_constants.h" 40 #include "content/browser/renderer_host/render_process_host.h" 41 #include "content/browser/renderer_host/render_view_host.h" 42 #include "content/browser/renderer_host/render_widget_host.h" 43 #include "content/browser/tab_contents/tab_contents.h" 44 #include "content/browser/tab_contents/tab_contents_view.h" 45 #include "content/common/notification_service.h" 46 #include "content/common/notification_type.h" 47 #include "grit/browser_resources.h" 48 #include "grit/chromium_strings.h" 49 #include "grit/generated_resources.h" 50 #include "grit/theme_resources.h" 51 #include "net/base/net_util.h" 52 #include "ui/base/l10n/l10n_util.h" 53 #include "ui/base/resource/resource_bundle.h" 54 #include "ui/gfx/codec/png_codec.h" 55 #include "ui/gfx/color_utils.h" 56 #include "ui/gfx/skbitmap_operations.h" 57 #include "webkit/glue/image_decoder.h" 58 59 namespace { 60 61 bool ShouldShowExtension(const Extension* extension) { 62 // Don't show themes since this page's UI isn't really useful for themes. 63 if (extension->is_theme()) 64 return false; 65 66 // Don't show component extensions because they are only extensions as an 67 // implementation detail of Chrome. 68 if (extension->location() == Extension::COMPONENT) 69 return false; 70 71 // Always show unpacked extensions and apps. 72 if (extension->location() == Extension::LOAD) 73 return true; 74 75 // Unless they are unpacked, never show hosted apps. 76 if (extension->is_hosted_app()) 77 return false; 78 79 return true; 80 } 81 82 } // namespace 83 84 //////////////////////////////////////////////////////////////////////////////// 85 // 86 // ExtensionsHTMLSource 87 // 88 //////////////////////////////////////////////////////////////////////////////// 89 90 ExtensionsUIHTMLSource::ExtensionsUIHTMLSource() 91 : DataSource(chrome::kChromeUIExtensionsHost, MessageLoop::current()) { 92 } 93 94 void ExtensionsUIHTMLSource::StartDataRequest(const std::string& path, 95 bool is_incognito, 96 int request_id) { 97 DictionaryValue localized_strings; 98 localized_strings.SetString("title", 99 l10n_util::GetStringUTF16(IDS_EXTENSIONS_TITLE)); 100 localized_strings.SetString("devModeLink", 101 l10n_util::GetStringUTF16(IDS_EXTENSIONS_DEVELOPER_MODE_LINK)); 102 localized_strings.SetString("devModePrefix", 103 l10n_util::GetStringUTF16(IDS_EXTENSIONS_DEVELOPER_MODE_PREFIX)); 104 localized_strings.SetString("loadUnpackedButton", 105 l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOAD_UNPACKED_BUTTON)); 106 localized_strings.SetString("packButton", 107 l10n_util::GetStringUTF16(IDS_EXTENSIONS_PACK_BUTTON)); 108 localized_strings.SetString("updateButton", 109 l10n_util::GetStringUTF16(IDS_EXTENSIONS_UPDATE_BUTTON)); 110 localized_strings.SetString("noExtensions", 111 l10n_util::GetStringUTF16(IDS_EXTENSIONS_NONE_INSTALLED)); 112 localized_strings.SetString("suggestGallery", 113 l10n_util::GetStringFUTF16(IDS_EXTENSIONS_NONE_INSTALLED_SUGGEST_GALLERY, 114 ASCIIToUTF16("<a href='") + 115 ASCIIToUTF16(google_util::AppendGoogleLocaleParam( 116 GURL(Extension::ChromeStoreLaunchURL())).spec()) + 117 ASCIIToUTF16("'>"), 118 ASCIIToUTF16("</a>"))); 119 localized_strings.SetString("getMoreExtensions", 120 ASCIIToUTF16("<a href='") + 121 ASCIIToUTF16(google_util::AppendGoogleLocaleParam( 122 GURL(Extension::ChromeStoreLaunchURL())).spec()) + 123 ASCIIToUTF16("'>") + 124 l10n_util::GetStringUTF16(IDS_GET_MORE_EXTENSIONS) + 125 ASCIIToUTF16("</a>")); 126 localized_strings.SetString("extensionCrashed", 127 l10n_util::GetStringUTF16(IDS_EXTENSIONS_CRASHED_EXTENSION)); 128 localized_strings.SetString("extensionDisabled", 129 l10n_util::GetStringUTF16(IDS_EXTENSIONS_DISABLED_EXTENSION)); 130 localized_strings.SetString("inDevelopment", 131 l10n_util::GetStringUTF16(IDS_EXTENSIONS_IN_DEVELOPMENT)); 132 localized_strings.SetString("viewIncognito", 133 l10n_util::GetStringUTF16(IDS_EXTENSIONS_VIEW_INCOGNITO)); 134 localized_strings.SetString("extensionId", 135 l10n_util::GetStringUTF16(IDS_EXTENSIONS_ID)); 136 localized_strings.SetString("extensionVersion", 137 l10n_util::GetStringUTF16(IDS_EXTENSIONS_VERSION)); 138 localized_strings.SetString("inspectViews", 139 l10n_util::GetStringUTF16(IDS_EXTENSIONS_INSPECT_VIEWS)); 140 localized_strings.SetString("inspectPopupsInstructions", 141 l10n_util::GetStringUTF16(IDS_EXTENSIONS_INSPECT_POPUPS_INSTRUCTIONS)); 142 localized_strings.SetString("disable", 143 l10n_util::GetStringUTF16(IDS_EXTENSIONS_DISABLE)); 144 localized_strings.SetString("enable", 145 l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE)); 146 localized_strings.SetString("enableIncognito", 147 l10n_util::GetStringUTF16(IDS_EXTENSIONS_ENABLE_INCOGNITO)); 148 localized_strings.SetString("allowFileAccess", 149 l10n_util::GetStringUTF16(IDS_EXTENSIONS_ALLOW_FILE_ACCESS)); 150 localized_strings.SetString("incognitoWarning", 151 l10n_util::GetStringFUTF16(IDS_EXTENSIONS_INCOGNITO_WARNING, 152 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); 153 localized_strings.SetString("reload", 154 l10n_util::GetStringUTF16(IDS_EXTENSIONS_RELOAD)); 155 localized_strings.SetString("uninstall", 156 l10n_util::GetStringUTF16(IDS_EXTENSIONS_UNINSTALL)); 157 localized_strings.SetString("options", 158 l10n_util::GetStringUTF16(IDS_EXTENSIONS_OPTIONS)); 159 localized_strings.SetString("packDialogTitle", 160 l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_TITLE)); 161 localized_strings.SetString("packDialogHeading", 162 l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_HEADING)); 163 localized_strings.SetString("rootDirectoryLabel", 164 l10n_util::GetStringUTF16( 165 IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL)); 166 localized_strings.SetString("packDialogBrowse", 167 l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_BROWSE)); 168 localized_strings.SetString("privateKeyLabel", 169 l10n_util::GetStringUTF16(IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL)); 170 localized_strings.SetString("okButton", 171 l10n_util::GetStringUTF16(IDS_OK)); 172 localized_strings.SetString("cancelButton", 173 l10n_util::GetStringUTF16(IDS_CANCEL)); 174 localized_strings.SetString("showButton", 175 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_BUTTON)); 176 177 SetFontAndTextDirection(&localized_strings); 178 179 static const base::StringPiece extensions_html( 180 ResourceBundle::GetSharedInstance().GetRawDataResource( 181 IDR_EXTENSIONS_UI_HTML)); 182 std::string full_html(extensions_html.data(), extensions_html.size()); 183 jstemplate_builder::AppendJsonHtml(&localized_strings, &full_html); 184 jstemplate_builder::AppendI18nTemplateSourceHtml(&full_html); 185 jstemplate_builder::AppendI18nTemplateProcessHtml(&full_html); 186 jstemplate_builder::AppendJsTemplateSourceHtml(&full_html); 187 188 scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); 189 html_bytes->data.resize(full_html.size()); 190 std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); 191 192 SendResponse(request_id, html_bytes); 193 } 194 195 std::string ExtensionsUIHTMLSource::GetMimeType(const std::string&) const { 196 return "text/html"; 197 } 198 199 //////////////////////////////////////////////////////////////////////////////// 200 // 201 // ExtensionsDOMHandler::IconLoader 202 // 203 //////////////////////////////////////////////////////////////////////////////// 204 205 ExtensionsDOMHandler::IconLoader::IconLoader(ExtensionsDOMHandler* handler) 206 : handler_(handler) { 207 } 208 209 void ExtensionsDOMHandler::IconLoader::LoadIcons( 210 std::vector<ExtensionResource>* icons, DictionaryValue* json) { 211 BrowserThread::PostTask( 212 BrowserThread::FILE, FROM_HERE, 213 NewRunnableMethod(this, 214 &IconLoader::LoadIconsOnFileThread, icons, json)); 215 } 216 217 void ExtensionsDOMHandler::IconLoader::Cancel() { 218 handler_ = NULL; 219 } 220 221 void ExtensionsDOMHandler::IconLoader::LoadIconsOnFileThread( 222 std::vector<ExtensionResource>* icons, DictionaryValue* json) { 223 scoped_ptr<std::vector<ExtensionResource> > icons_deleter(icons); 224 scoped_ptr<DictionaryValue> json_deleter(json); 225 226 ListValue* extensions = NULL; 227 CHECK(json->GetList("extensions", &extensions)); 228 229 for (size_t i = 0; i < icons->size(); ++i) { 230 DictionaryValue* extension = NULL; 231 CHECK(extensions->GetDictionary(static_cast<int>(i), &extension)); 232 233 // Read the file. 234 std::string file_contents; 235 if (icons->at(i).relative_path().empty() || 236 !file_util::ReadFileToString(icons->at(i).GetFilePath(), 237 &file_contents)) { 238 // If there's no icon, use the default icon. This is safe to do from 239 // the file thread. 240 // TODO(erikkay) Assuming we're going to keep showing apps in this list, 241 // then we need to figure out when we should use the app default icon. 242 file_contents = ResourceBundle::GetSharedInstance().GetRawDataResource( 243 IDR_EXTENSION_DEFAULT_ICON).as_string(); 244 } 245 246 // If the extension is disabled, we desaturate the icon to add to the 247 // disabledness effect. 248 bool enabled = false; 249 CHECK(extension->GetBoolean("enabled", &enabled)); 250 if (!enabled) { 251 const unsigned char* data = 252 reinterpret_cast<const unsigned char*>(file_contents.data()); 253 webkit_glue::ImageDecoder decoder; 254 scoped_ptr<SkBitmap> decoded(new SkBitmap()); 255 *decoded = decoder.Decode(data, file_contents.length()); 256 257 // Desaturate the icon and lighten it a bit. 258 color_utils::HSL shift = {-1, 0, 0.6}; 259 *decoded = SkBitmapOperations::CreateHSLShiftedBitmap(*decoded, shift); 260 261 std::vector<unsigned char> output; 262 gfx::PNGCodec::EncodeBGRASkBitmap(*decoded, false, &output); 263 264 // Lame, but we must make a copy of this now, because base64 doesn't take 265 // the same input type. 266 file_contents.assign(reinterpret_cast<char*>(&output.front()), 267 output.size()); 268 } 269 270 // Create a data URL (all icons are converted to PNGs during unpacking). 271 std::string base64_encoded; 272 base::Base64Encode(file_contents, &base64_encoded); 273 GURL icon_url("data:image/png;base64," + base64_encoded); 274 275 extension->SetString("icon", icon_url.spec()); 276 } 277 278 BrowserThread::PostTask( 279 BrowserThread::UI, FROM_HERE, 280 NewRunnableMethod(this, &IconLoader::ReportResultOnUIThread, 281 json_deleter.release())); 282 } 283 284 void ExtensionsDOMHandler::IconLoader::ReportResultOnUIThread( 285 DictionaryValue* json) { 286 if (handler_) 287 handler_->OnIconsLoaded(json); 288 } 289 290 291 /////////////////////////////////////////////////////////////////////////////// 292 // 293 // ExtensionsDOMHandler 294 // 295 /////////////////////////////////////////////////////////////////////////////// 296 297 ExtensionsDOMHandler::ExtensionsDOMHandler(ExtensionService* extension_service) 298 : extensions_service_(extension_service), 299 ignore_notifications_(false), 300 deleting_rvh_(NULL) { 301 } 302 303 void ExtensionsDOMHandler::RegisterMessages() { 304 web_ui_->RegisterMessageCallback("requestExtensionsData", 305 NewCallback(this, &ExtensionsDOMHandler::HandleRequestExtensionsData)); 306 web_ui_->RegisterMessageCallback("toggleDeveloperMode", 307 NewCallback(this, &ExtensionsDOMHandler::HandleToggleDeveloperMode)); 308 web_ui_->RegisterMessageCallback("inspect", 309 NewCallback(this, &ExtensionsDOMHandler::HandleInspectMessage)); 310 web_ui_->RegisterMessageCallback("reload", 311 NewCallback(this, &ExtensionsDOMHandler::HandleReloadMessage)); 312 web_ui_->RegisterMessageCallback("enable", 313 NewCallback(this, &ExtensionsDOMHandler::HandleEnableMessage)); 314 web_ui_->RegisterMessageCallback("enableIncognito", 315 NewCallback(this, &ExtensionsDOMHandler::HandleEnableIncognitoMessage)); 316 web_ui_->RegisterMessageCallback("allowFileAccess", 317 NewCallback(this, &ExtensionsDOMHandler::HandleAllowFileAccessMessage)); 318 web_ui_->RegisterMessageCallback("uninstall", 319 NewCallback(this, &ExtensionsDOMHandler::HandleUninstallMessage)); 320 web_ui_->RegisterMessageCallback("options", 321 NewCallback(this, &ExtensionsDOMHandler::HandleOptionsMessage)); 322 web_ui_->RegisterMessageCallback("showButton", 323 NewCallback(this, &ExtensionsDOMHandler::HandleShowButtonMessage)); 324 web_ui_->RegisterMessageCallback("load", 325 NewCallback(this, &ExtensionsDOMHandler::HandleLoadMessage)); 326 web_ui_->RegisterMessageCallback("pack", 327 NewCallback(this, &ExtensionsDOMHandler::HandlePackMessage)); 328 web_ui_->RegisterMessageCallback("autoupdate", 329 NewCallback(this, &ExtensionsDOMHandler::HandleAutoUpdateMessage)); 330 web_ui_->RegisterMessageCallback("selectFilePath", 331 NewCallback(this, &ExtensionsDOMHandler::HandleSelectFilePathMessage)); 332 } 333 334 void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) { 335 DictionaryValue* results = new DictionaryValue(); 336 337 // Add the extensions to the results structure. 338 ListValue *extensions_list = new ListValue(); 339 340 // Stores the icon resource for each of the extensions in extensions_list. We 341 // build up a list of them here, then load them on the file thread in 342 // ::LoadIcons(). 343 std::vector<ExtensionResource>* extension_icons = 344 new std::vector<ExtensionResource>(); 345 346 const ExtensionList* extensions = extensions_service_->extensions(); 347 for (ExtensionList::const_iterator extension = extensions->begin(); 348 extension != extensions->end(); ++extension) { 349 if (ShouldShowExtension(*extension)) { 350 extensions_list->Append(CreateExtensionDetailValue( 351 extensions_service_.get(), 352 *extension, 353 GetActivePagesForExtension(*extension), 354 true, false)); // enabled, terminated 355 extension_icons->push_back(PickExtensionIcon(*extension)); 356 } 357 } 358 extensions = extensions_service_->disabled_extensions(); 359 for (ExtensionList::const_iterator extension = extensions->begin(); 360 extension != extensions->end(); ++extension) { 361 if (ShouldShowExtension(*extension)) { 362 extensions_list->Append(CreateExtensionDetailValue( 363 extensions_service_.get(), 364 *extension, 365 GetActivePagesForExtension(*extension), 366 false, false)); // enabled, terminated 367 extension_icons->push_back(PickExtensionIcon(*extension)); 368 } 369 } 370 extensions = extensions_service_->terminated_extensions(); 371 std::vector<ExtensionPage> empty_pages; 372 for (ExtensionList::const_iterator extension = extensions->begin(); 373 extension != extensions->end(); ++extension) { 374 if (ShouldShowExtension(*extension)) { 375 extensions_list->Append(CreateExtensionDetailValue( 376 extensions_service_.get(), 377 *extension, 378 empty_pages, // Terminated process has no active pages. 379 false, true)); // enabled, terminated 380 extension_icons->push_back(PickExtensionIcon(*extension)); 381 } 382 } 383 results->Set("extensions", extensions_list); 384 385 bool developer_mode = web_ui_->GetProfile()->GetPrefs() 386 ->GetBoolean(prefs::kExtensionsUIDeveloperMode); 387 results->SetBoolean("developerMode", developer_mode); 388 389 if (icon_loader_.get()) 390 icon_loader_->Cancel(); 391 392 icon_loader_ = new IconLoader(this); 393 icon_loader_->LoadIcons(extension_icons, results); 394 } 395 396 void ExtensionsDOMHandler::OnIconsLoaded(DictionaryValue* json) { 397 web_ui_->CallJavascriptFunction(L"returnExtensionsData", *json); 398 delete json; 399 400 // Register for notifications that we need to reload the page. 401 registrar_.RemoveAll(); 402 registrar_.Add(this, NotificationType::EXTENSION_LOADED, 403 NotificationService::AllSources()); 404 registrar_.Add(this, NotificationType::EXTENSION_PROCESS_CREATED, 405 NotificationService::AllSources()); 406 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 407 NotificationService::AllSources()); 408 registrar_.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, 409 NotificationService::AllSources()); 410 registrar_.Add(this, NotificationType::EXTENSION_FUNCTION_DISPATCHER_CREATED, 411 NotificationService::AllSources()); 412 registrar_.Add(this, 413 NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED, 414 NotificationService::AllSources()); 415 registrar_.Add(this, 416 NotificationType::NAV_ENTRY_COMMITTED, 417 NotificationService::AllSources()); 418 registrar_.Add(this, 419 NotificationType::RENDER_VIEW_HOST_DELETED, 420 NotificationService::AllSources()); 421 registrar_.Add(this, 422 NotificationType::BACKGROUND_CONTENTS_NAVIGATED, 423 NotificationService::AllSources()); 424 registrar_.Add(this, 425 NotificationType::BACKGROUND_CONTENTS_DELETED, 426 NotificationService::AllSources()); 427 registrar_.Add(this, 428 NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, 429 NotificationService::AllSources()); 430 } 431 432 ExtensionResource ExtensionsDOMHandler::PickExtensionIcon( 433 const Extension* extension) { 434 return extension->GetIconResource(Extension::EXTENSION_ICON_MEDIUM, 435 ExtensionIconSet::MATCH_BIGGER); 436 } 437 438 ExtensionUninstallDialog* ExtensionsDOMHandler::GetExtensionUninstallDialog() { 439 if (!extension_uninstall_dialog_.get()) { 440 extension_uninstall_dialog_.reset( 441 new ExtensionUninstallDialog(web_ui_->GetProfile())); 442 } 443 return extension_uninstall_dialog_.get(); 444 } 445 446 void ExtensionsDOMHandler::HandleToggleDeveloperMode(const ListValue* args) { 447 bool developer_mode = web_ui_->GetProfile()->GetPrefs() 448 ->GetBoolean(prefs::kExtensionsUIDeveloperMode); 449 web_ui_->GetProfile()->GetPrefs()->SetBoolean( 450 prefs::kExtensionsUIDeveloperMode, !developer_mode); 451 } 452 453 void ExtensionsDOMHandler::HandleInspectMessage(const ListValue* args) { 454 std::string render_process_id_str; 455 std::string render_view_id_str; 456 int render_process_id; 457 int render_view_id; 458 CHECK(args->GetSize() == 2); 459 CHECK(args->GetString(0, &render_process_id_str)); 460 CHECK(args->GetString(1, &render_view_id_str)); 461 CHECK(base::StringToInt(render_process_id_str, &render_process_id)); 462 CHECK(base::StringToInt(render_view_id_str, &render_view_id)); 463 RenderViewHost* host = RenderViewHost::FromID(render_process_id, 464 render_view_id); 465 if (!host) { 466 // This can happen if the host has gone away since the page was displayed. 467 return; 468 } 469 470 DevToolsManager::GetInstance()->OpenDevToolsWindow(host); 471 } 472 473 void ExtensionsDOMHandler::HandleReloadMessage(const ListValue* args) { 474 std::string extension_id = WideToASCII(ExtractStringValue(args)); 475 CHECK(!extension_id.empty()); 476 extensions_service_->ReloadExtension(extension_id); 477 } 478 479 void ExtensionsDOMHandler::HandleEnableMessage(const ListValue* args) { 480 CHECK(args->GetSize() == 2); 481 std::string extension_id, enable_str; 482 CHECK(args->GetString(0, &extension_id)); 483 CHECK(args->GetString(1, &enable_str)); 484 if (enable_str == "true") { 485 ExtensionPrefs* prefs = extensions_service_->extension_prefs(); 486 if (prefs->DidExtensionEscalatePermissions(extension_id)) { 487 const Extension* extension = 488 extensions_service_->GetExtensionById(extension_id, true); 489 ShowExtensionDisabledDialog(extensions_service_, 490 web_ui_->GetProfile(), extension); 491 } else { 492 extensions_service_->EnableExtension(extension_id); 493 } 494 } else { 495 extensions_service_->DisableExtension(extension_id); 496 } 497 } 498 499 void ExtensionsDOMHandler::HandleEnableIncognitoMessage(const ListValue* args) { 500 CHECK(args->GetSize() == 2); 501 std::string extension_id, enable_str; 502 CHECK(args->GetString(0, &extension_id)); 503 CHECK(args->GetString(1, &enable_str)); 504 const Extension* extension = 505 extensions_service_->GetExtensionById(extension_id, true); 506 DCHECK(extension); 507 508 // Flipping the incognito bit will generate unload/load notifications for the 509 // extension, but we don't want to reload the page, because a) we've already 510 // updated the UI to reflect the change, and b) we want the yellow warning 511 // text to stay until the user has left the page. 512 // 513 // TODO(aa): This creates crapiness in some cases. For example, in a main 514 // window, when toggling this, the browser action will flicker because it gets 515 // unloaded, then reloaded. It would be better to have a dedicated 516 // notification for this case. 517 // 518 // Bug: http://crbug.com/41384 519 ignore_notifications_ = true; 520 extensions_service_->SetIsIncognitoEnabled(extension, enable_str == "true"); 521 ignore_notifications_ = false; 522 } 523 524 void ExtensionsDOMHandler::HandleAllowFileAccessMessage(const ListValue* args) { 525 CHECK(args->GetSize() == 2); 526 std::string extension_id, allow_str; 527 CHECK(args->GetString(0, &extension_id)); 528 CHECK(args->GetString(1, &allow_str)); 529 const Extension* extension = 530 extensions_service_->GetExtensionById(extension_id, true); 531 DCHECK(extension); 532 533 extensions_service_->SetAllowFileAccess(extension, allow_str == "true"); 534 } 535 536 void ExtensionsDOMHandler::HandleUninstallMessage(const ListValue* args) { 537 std::string extension_id = WideToASCII(ExtractStringValue(args)); 538 CHECK(!extension_id.empty()); 539 const Extension* extension = 540 extensions_service_->GetExtensionById(extension_id, true); 541 if (!extension) 542 extension = extensions_service_->GetTerminatedExtension(extension_id); 543 if (!extension) 544 return; 545 546 if (!extension_id_prompting_.empty()) 547 return; // Only one prompt at a time. 548 549 extension_id_prompting_ = extension_id; 550 551 GetExtensionUninstallDialog()->ConfirmUninstall(this, extension); 552 } 553 554 void ExtensionsDOMHandler::ExtensionDialogAccepted() { 555 DCHECK(!extension_id_prompting_.empty()); 556 557 bool was_terminated = false; 558 559 // The extension can be uninstalled in another window while the UI was 560 // showing. Do nothing in that case. 561 const Extension* extension = 562 extensions_service_->GetExtensionById(extension_id_prompting_, true); 563 if (!extension) { 564 extension = extensions_service_->GetTerminatedExtension( 565 extension_id_prompting_); 566 was_terminated = true; 567 } 568 if (!extension) 569 return; 570 571 extensions_service_->UninstallExtension(extension_id_prompting_, 572 false /* external_uninstall */, NULL); 573 extension_id_prompting_ = ""; 574 575 // There will be no EXTENSION_UNLOADED notification for terminated 576 // extensions as they were already unloaded. 577 if (was_terminated) 578 HandleRequestExtensionsData(NULL); 579 } 580 581 void ExtensionsDOMHandler::ExtensionDialogCanceled() { 582 extension_id_prompting_ = ""; 583 } 584 585 void ExtensionsDOMHandler::HandleOptionsMessage(const ListValue* args) { 586 const Extension* extension = GetExtension(args); 587 if (!extension || extension->options_url().is_empty()) 588 return; 589 web_ui_->GetProfile()->GetExtensionProcessManager()->OpenOptionsPage( 590 extension, NULL); 591 } 592 593 void ExtensionsDOMHandler::HandleShowButtonMessage(const ListValue* args) { 594 const Extension* extension = GetExtension(args); 595 extensions_service_->SetBrowserActionVisibility(extension, true); 596 } 597 598 void ExtensionsDOMHandler::HandleLoadMessage(const ListValue* args) { 599 FilePath::StringType string_path; 600 CHECK(args->GetSize() == 1) << args->GetSize(); 601 CHECK(args->GetString(0, &string_path)); 602 extensions_service_->LoadExtension(FilePath(string_path)); 603 } 604 605 void ExtensionsDOMHandler::ShowAlert(const std::string& message) { 606 ListValue arguments; 607 arguments.Append(Value::CreateStringValue(message)); 608 web_ui_->CallJavascriptFunction(L"alert", arguments); 609 } 610 611 void ExtensionsDOMHandler::HandlePackMessage(const ListValue* args) { 612 std::string extension_path; 613 std::string private_key_path; 614 CHECK(args->GetSize() == 2); 615 CHECK(args->GetString(0, &extension_path)); 616 CHECK(args->GetString(1, &private_key_path)); 617 618 FilePath root_directory = 619 FilePath::FromWStringHack(UTF8ToWide(extension_path)); 620 FilePath key_file = FilePath::FromWStringHack(UTF8ToWide(private_key_path)); 621 622 if (root_directory.empty()) { 623 if (extension_path.empty()) { 624 ShowAlert(l10n_util::GetStringUTF8( 625 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED)); 626 } else { 627 ShowAlert(l10n_util::GetStringUTF8( 628 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID)); 629 } 630 631 return; 632 } 633 634 if (!private_key_path.empty() && key_file.empty()) { 635 ShowAlert(l10n_util::GetStringUTF8( 636 IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID)); 637 return; 638 } 639 640 pack_job_ = new PackExtensionJob(this, root_directory, key_file); 641 pack_job_->Start(); 642 } 643 644 void ExtensionsDOMHandler::OnPackSuccess(const FilePath& crx_file, 645 const FilePath& pem_file) { 646 ShowAlert(UTF16ToUTF8(PackExtensionJob::StandardSuccessMessage(crx_file, 647 pem_file))); 648 649 ListValue results; 650 web_ui_->CallJavascriptFunction(L"hidePackDialog", results); 651 } 652 653 void ExtensionsDOMHandler::OnPackFailure(const std::string& error) { 654 ShowAlert(error); 655 } 656 657 void ExtensionsDOMHandler::HandleAutoUpdateMessage(const ListValue* args) { 658 ExtensionUpdater* updater = extensions_service_->updater(); 659 if (updater) 660 updater->CheckNow(); 661 } 662 663 void ExtensionsDOMHandler::HandleSelectFilePathMessage(const ListValue* args) { 664 std::string select_type; 665 std::string operation; 666 CHECK(args->GetSize() == 2); 667 CHECK(args->GetString(0, &select_type)); 668 CHECK(args->GetString(1, &operation)); 669 670 SelectFileDialog::Type type = SelectFileDialog::SELECT_FOLDER; 671 static SelectFileDialog::FileTypeInfo info; 672 int file_type_index = 0; 673 if (select_type == "file") 674 type = SelectFileDialog::SELECT_OPEN_FILE; 675 676 string16 select_title; 677 if (operation == "load") { 678 select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY); 679 } else if (operation == "packRoot") { 680 select_title = l10n_util::GetStringUTF16( 681 IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT); 682 } else if (operation == "pem") { 683 select_title = l10n_util::GetStringUTF16( 684 IDS_EXTENSION_PACK_DIALOG_SELECT_KEY); 685 info.extensions.push_back(std::vector<FilePath::StringType>()); 686 info.extensions.front().push_back(FILE_PATH_LITERAL("pem")); 687 info.extension_description_overrides.push_back( 688 l10n_util::GetStringUTF16( 689 IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION)); 690 info.include_all_files = true; 691 file_type_index = 1; 692 } else { 693 NOTREACHED(); 694 return; 695 } 696 697 load_extension_dialog_ = SelectFileDialog::Create(this); 698 load_extension_dialog_->SelectFile(type, select_title, FilePath(), &info, 699 file_type_index, FILE_PATH_LITERAL(""), web_ui_->tab_contents(), 700 web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL); 701 } 702 703 704 void ExtensionsDOMHandler::FileSelected(const FilePath& path, int index, 705 void* params) { 706 // Add the extensions to the results structure. 707 ListValue results; 708 results.Append(Value::CreateStringValue(path.value())); 709 web_ui_->CallJavascriptFunction(L"window.handleFilePathSelected", results); 710 } 711 712 void ExtensionsDOMHandler::MultiFilesSelected( 713 const std::vector<FilePath>& files, void* params) { 714 NOTREACHED(); 715 } 716 717 void ExtensionsDOMHandler::Observe(NotificationType type, 718 const NotificationSource& source, 719 const NotificationDetails& details) { 720 switch (type.value) { 721 // We listen for notifications that will result in the page being 722 // repopulated with data twice for the same event in certain cases. 723 // For instance, EXTENSION_LOADED & EXTENSION_PROCESS_CREATED because 724 // we don't know about the views for an extension at EXTENSION_LOADED, but 725 // if we only listen to EXTENSION_PROCESS_CREATED, we'll miss extensions 726 // that don't have a process at startup. Similarly, NAV_ENTRY_COMMITTED & 727 // EXTENSION_FUNCTION_DISPATCHER_CREATED because we want to handle both 728 // the case of live app pages (which don't have an EFD) and 729 // chrome-extension:// urls which are served in a TabContents. 730 // 731 // Doing it this way gets everything but causes the page to be rendered 732 // more than we need. It doesn't seem to result in any noticeable flicker. 733 case NotificationType::RENDER_VIEW_HOST_DELETED: 734 deleting_rvh_ = Details<RenderViewHost>(details).ptr(); 735 MaybeUpdateAfterNotification(); 736 break; 737 case NotificationType::BACKGROUND_CONTENTS_DELETED: 738 deleting_rvh_ = Details<BackgroundContents>(details)->render_view_host(); 739 MaybeUpdateAfterNotification(); 740 break; 741 case NotificationType::EXTENSION_LOADED: 742 case NotificationType::EXTENSION_PROCESS_CREATED: 743 case NotificationType::EXTENSION_UNLOADED: 744 case NotificationType::EXTENSION_UPDATE_DISABLED: 745 case NotificationType::EXTENSION_FUNCTION_DISPATCHER_CREATED: 746 case NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED: 747 case NotificationType::NAV_ENTRY_COMMITTED: 748 case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: 749 case NotificationType::EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: 750 MaybeUpdateAfterNotification(); 751 break; 752 default: 753 NOTREACHED(); 754 } 755 } 756 757 const Extension* ExtensionsDOMHandler::GetExtension(const ListValue* args) { 758 std::string extension_id = WideToASCII(ExtractStringValue(args)); 759 CHECK(!extension_id.empty()); 760 return extensions_service_->GetExtensionById(extension_id, true); 761 } 762 763 void ExtensionsDOMHandler::MaybeUpdateAfterNotification() { 764 if (!ignore_notifications_ && web_ui_->tab_contents()) 765 HandleRequestExtensionsData(NULL); 766 deleting_rvh_ = NULL; 767 } 768 769 // Static 770 DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( 771 ExtensionService* service, const Extension* extension, 772 const std::vector<ExtensionPage>& pages, bool enabled, bool terminated) { 773 DictionaryValue* extension_data = new DictionaryValue(); 774 775 extension_data->SetString("id", extension->id()); 776 extension_data->SetString("name", extension->name()); 777 extension_data->SetString("description", extension->description()); 778 extension_data->SetString("version", extension->version()->GetString()); 779 extension_data->SetBoolean("enabled", enabled); 780 extension_data->SetBoolean("terminated", terminated); 781 extension_data->SetBoolean("enabledIncognito", 782 service ? service->IsIncognitoEnabled(extension) : false); 783 extension_data->SetBoolean("wantsFileAccess", extension->wants_file_access()); 784 extension_data->SetBoolean("allowFileAccess", 785 service ? service->AllowFileAccess(extension) : false); 786 extension_data->SetBoolean("allow_reload", 787 extension->location() == Extension::LOAD); 788 extension_data->SetBoolean("is_hosted_app", extension->is_hosted_app()); 789 790 // Determine the sort order: Extensions loaded through --load-extensions show 791 // up at the top. Disabled extensions show up at the bottom. 792 if (extension->location() == Extension::LOAD) 793 extension_data->SetInteger("order", 1); 794 else 795 extension_data->SetInteger("order", 2); 796 797 if (!extension->options_url().is_empty()) 798 extension_data->SetString("options_url", extension->options_url().spec()); 799 800 if (service && !service->GetBrowserActionVisibility(extension)) 801 extension_data->SetBoolean("enable_show_button", true); 802 803 // Add views 804 ListValue* views = new ListValue; 805 for (std::vector<ExtensionPage>::const_iterator iter = pages.begin(); 806 iter != pages.end(); ++iter) { 807 DictionaryValue* view_value = new DictionaryValue; 808 if (iter->url.scheme() == chrome::kExtensionScheme) { 809 // No leading slash. 810 view_value->SetString("path", iter->url.path().substr(1)); 811 } else { 812 // For live pages, use the full URL. 813 view_value->SetString("path", iter->url.spec()); 814 } 815 view_value->SetInteger("renderViewId", iter->render_view_id); 816 view_value->SetInteger("renderProcessId", iter->render_process_id); 817 view_value->SetBoolean("incognito", iter->incognito); 818 views->Append(view_value); 819 } 820 extension_data->Set("views", views); 821 extension_data->SetBoolean("hasPopupAction", 822 extension->browser_action() || extension->page_action()); 823 extension_data->SetString("homepageUrl", extension->GetHomepageURL().spec()); 824 825 return extension_data; 826 } 827 828 std::vector<ExtensionPage> ExtensionsDOMHandler::GetActivePagesForExtension( 829 const Extension* extension) { 830 std::vector<ExtensionPage> result; 831 832 // Get the extension process's active views. 833 ExtensionProcessManager* process_manager = 834 extensions_service_->profile()->GetExtensionProcessManager(); 835 GetActivePagesForExtensionProcess( 836 process_manager->GetExtensionProcess(extension->url()), 837 extension, &result); 838 839 // Repeat for the incognito process, if applicable. 840 if (extensions_service_->profile()->HasOffTheRecordProfile() && 841 extension->incognito_split_mode()) { 842 ExtensionProcessManager* process_manager = 843 extensions_service_->profile()->GetOffTheRecordProfile()-> 844 GetExtensionProcessManager(); 845 GetActivePagesForExtensionProcess( 846 process_manager->GetExtensionProcess(extension->url()), 847 extension, &result); 848 } 849 850 return result; 851 } 852 853 void ExtensionsDOMHandler::GetActivePagesForExtensionProcess( 854 RenderProcessHost* process, 855 const Extension* extension, 856 std::vector<ExtensionPage> *result) { 857 if (!process) 858 return; 859 860 RenderProcessHost::listeners_iterator iter = process->ListenersIterator(); 861 for (; !iter.IsAtEnd(); iter.Advance()) { 862 const RenderWidgetHost* widget = 863 static_cast<const RenderWidgetHost*>(iter.GetCurrentValue()); 864 DCHECK(widget); 865 if (!widget || !widget->IsRenderView()) 866 continue; 867 const RenderViewHost* host = static_cast<const RenderViewHost*>(widget); 868 if (host == deleting_rvh_ || 869 ViewType::EXTENSION_POPUP == host->delegate()->GetRenderViewType()) 870 continue; 871 872 GURL url = host->delegate()->GetURL(); 873 if (url.SchemeIs(chrome::kExtensionScheme)) { 874 if (url.host() != extension->id()) 875 continue; 876 } else if (!extension->web_extent().ContainsURL(url)) { 877 continue; 878 } 879 880 result->push_back(ExtensionPage(url, process->id(), host->routing_id(), 881 process->profile()->IsOffTheRecord())); 882 } 883 } 884 885 ExtensionsDOMHandler::~ExtensionsDOMHandler() { 886 // There may be pending file dialogs, we need to tell them that we've gone 887 // away so they don't try and call back to us. 888 if (load_extension_dialog_.get()) 889 load_extension_dialog_->ListenerDestroyed(); 890 891 if (pack_job_.get()) 892 pack_job_->ClearClient(); 893 894 if (icon_loader_.get()) 895 icon_loader_->Cancel(); 896 } 897 898 // ExtensionsDOMHandler, public: ----------------------------------------------- 899 900 ExtensionsUI::ExtensionsUI(TabContents* contents) : WebUI(contents) { 901 ExtensionService *exstension_service = 902 GetProfile()->GetOriginalProfile()->GetExtensionService(); 903 904 ExtensionsDOMHandler* handler = new ExtensionsDOMHandler(exstension_service); 905 AddMessageHandler(handler); 906 handler->Attach(this); 907 908 ExtensionsUIHTMLSource* html_source = new ExtensionsUIHTMLSource(); 909 910 // Set up the chrome://extensions/ source. 911 contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); 912 } 913 914 // static 915 RefCountedMemory* ExtensionsUI::GetFaviconResourceBytes() { 916 return ResourceBundle::GetSharedInstance(). 917 LoadDataResourceBytes(IDR_PLUGIN); 918 } 919 920 // static 921 void ExtensionsUI::RegisterUserPrefs(PrefService* prefs) { 922 prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode, false); 923 } 924