OLD | NEW |
| (Empty) |
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_window.h" | |
19 #include "chrome/browser/extensions/crx_installer.h" | |
20 #include "chrome/browser/extensions/extension_disabled_infobar_delegate.h" | |
21 #include "chrome/browser/extensions/extension_error_reporter.h" | |
22 #include "chrome/browser/extensions/extension_host.h" | |
23 #include "chrome/browser/extensions/extension_message_service.h" | |
24 #include "chrome/browser/extensions/extension_service.h" | |
25 #include "chrome/browser/extensions/extension_updater.h" | |
26 #include "chrome/browser/google/google_util.h" | |
27 #include "chrome/browser/prefs/pref_service.h" | |
28 #include "chrome/browser/profiles/profile.h" | |
29 #include "chrome/browser/tab_contents/background_contents.h" | |
30 #include "chrome/browser/ui/browser_list.h" | |
31 #include "chrome/browser/ui/webui/chrome_web_ui_data_source.h" | |
32 #include "chrome/browser/ui/webui/extension_icon_source.h" | |
33 #include "chrome/common/chrome_notification_types.h" | |
34 #include "chrome/common/extensions/extension.h" | |
35 #include "chrome/common/extensions/extension_icon_set.h" | |
36 #include "chrome/common/extensions/url_pattern.h" | |
37 #include "chrome/common/extensions/user_script.h" | |
38 #include "chrome/common/jstemplate_builder.h" | |
39 #include "chrome/common/pref_names.h" | |
40 #include "chrome/common/url_constants.h" | |
41 #include "content/browser/renderer_host/render_process_host.h" | |
42 #include "content/browser/renderer_host/render_view_host.h" | |
43 #include "content/browser/renderer_host/render_widget_host.h" | |
44 #include "content/browser/tab_contents/tab_contents.h" | |
45 #include "content/browser/tab_contents/tab_contents_view.h" | |
46 #include "content/common/content_notification_types.h" | |
47 #include "content/common/notification_service.h" | |
48 #include "googleurl/src/gurl.h" | |
49 #include "grit/browser_resources.h" | |
50 #include "grit/chromium_strings.h" | |
51 #include "grit/generated_resources.h" | |
52 #include "grit/theme_resources.h" | |
53 #include "grit/theme_resources_standard.h" | |
54 #include "net/base/net_util.h" | |
55 #include "ui/base/l10n/l10n_util.h" | |
56 #include "ui/base/resource/resource_bundle.h" | |
57 | |
58 namespace { | |
59 | |
60 bool ShouldShowExtension(const Extension* extension) { | |
61 // Don't show themes since this page's UI isn't really useful for themes. | |
62 if (extension->is_theme()) | |
63 return false; | |
64 | |
65 // Don't show component extensions because they are only extensions as an | |
66 // implementation detail of Chrome. | |
67 if (extension->location() == Extension::COMPONENT) | |
68 return false; | |
69 | |
70 // Always show unpacked extensions and apps. | |
71 if (extension->location() == Extension::LOAD) | |
72 return true; | |
73 | |
74 // Unless they are unpacked, never show hosted apps. | |
75 if (extension->is_hosted_app()) | |
76 return false; | |
77 | |
78 return true; | |
79 } | |
80 | |
81 } // namespace | |
82 | |
83 ChromeWebUIDataSource* CreateExtensionsUIHTMLSource() { | |
84 ChromeWebUIDataSource* source = | |
85 new ChromeWebUIDataSource(chrome::kChromeUIExtensionsHost); | |
86 | |
87 source->AddLocalizedString("title", IDS_EXTENSIONS_TITLE); | |
88 source->AddLocalizedString("devModeLink", IDS_EXTENSIONS_DEVELOPER_MODE_LINK); | |
89 source->AddLocalizedString("devModePrefix", | |
90 IDS_EXTENSIONS_DEVELOPER_MODE_PREFIX); | |
91 source->AddLocalizedString("loadUnpackedButton", | |
92 IDS_EXTENSIONS_LOAD_UNPACKED_BUTTON); | |
93 source->AddLocalizedString("packButton", IDS_EXTENSIONS_PACK_BUTTON); | |
94 source->AddLocalizedString("updateButton", IDS_EXTENSIONS_UPDATE_BUTTON); | |
95 source->AddLocalizedString("noExtensions", IDS_EXTENSIONS_NONE_INSTALLED); | |
96 source->AddLocalizedString("extensionCrashed", | |
97 IDS_EXTENSIONS_CRASHED_EXTENSION); | |
98 source->AddLocalizedString("extensionDisabled", | |
99 IDS_EXTENSIONS_DISABLED_EXTENSION); | |
100 source->AddLocalizedString("inDevelopment", IDS_EXTENSIONS_IN_DEVELOPMENT); | |
101 source->AddLocalizedString("viewIncognito", IDS_EXTENSIONS_VIEW_INCOGNITO); | |
102 source->AddLocalizedString("extensionId", IDS_EXTENSIONS_ID); | |
103 source->AddLocalizedString("extensionPath", IDS_EXTENSIONS_PATH); | |
104 source->AddLocalizedString("extensionVersion", IDS_EXTENSIONS_VERSION); | |
105 source->AddLocalizedString("inspectViews", IDS_EXTENSIONS_INSPECT_VIEWS); | |
106 source->AddLocalizedString("inspectPopupsInstructions", | |
107 IDS_EXTENSIONS_INSPECT_POPUPS_INSTRUCTIONS); | |
108 source->AddLocalizedString("disable", IDS_EXTENSIONS_DISABLE); | |
109 source->AddLocalizedString("enable", IDS_EXTENSIONS_ENABLE); | |
110 source->AddLocalizedString("enableIncognito", | |
111 IDS_EXTENSIONS_ENABLE_INCOGNITO); | |
112 source->AddLocalizedString("allowFileAccess", | |
113 IDS_EXTENSIONS_ALLOW_FILE_ACCESS); | |
114 source->AddLocalizedString("reload", IDS_EXTENSIONS_RELOAD); | |
115 source->AddLocalizedString("uninstall", IDS_EXTENSIONS_UNINSTALL); | |
116 source->AddLocalizedString("options", IDS_EXTENSIONS_OPTIONS); | |
117 source->AddLocalizedString("policyControlled", | |
118 IDS_EXTENSIONS_POLICY_CONTROLLED); | |
119 source->AddLocalizedString("packDialogTitle", | |
120 IDS_EXTENSION_PACK_DIALOG_TITLE); | |
121 source->AddLocalizedString("packDialogHeading", | |
122 IDS_EXTENSION_PACK_DIALOG_HEADING); | |
123 source->AddLocalizedString("rootDirectoryLabel", | |
124 IDS_EXTENSION_PACK_DIALOG_ROOT_DIRECTORY_LABEL); | |
125 source->AddLocalizedString("packDialogBrowse", | |
126 IDS_EXTENSION_PACK_DIALOG_BROWSE); | |
127 source->AddLocalizedString("privateKeyLabel", | |
128 IDS_EXTENSION_PACK_DIALOG_PRIVATE_KEY_LABEL); | |
129 source->AddLocalizedString("okButton", IDS_OK); | |
130 source->AddLocalizedString("cancelButton", IDS_CANCEL); | |
131 source->AddLocalizedString("showButton", IDS_EXTENSIONS_SHOW_BUTTON); | |
132 | |
133 source->AddString("incognitoWarning", | |
134 l10n_util::GetStringFUTF16(IDS_EXTENSIONS_INCOGNITO_WARNING, | |
135 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); | |
136 | |
137 source->AddString("suggestGallery", | |
138 l10n_util::GetStringFUTF16(IDS_EXTENSIONS_NONE_INSTALLED_SUGGEST_GALLERY, | |
139 ASCIIToUTF16("<a href='") + | |
140 ASCIIToUTF16(google_util::AppendGoogleLocaleParam( | |
141 GURL(extension_urls::GetWebstoreLaunchURL())).spec()) + | |
142 ASCIIToUTF16("'>"), | |
143 ASCIIToUTF16("</a>"))); | |
144 | |
145 source->AddString("getMoreExtensions", | |
146 ASCIIToUTF16("<a href='") + | |
147 ASCIIToUTF16(google_util::AppendGoogleLocaleParam( | |
148 GURL(extension_urls::GetWebstoreLaunchURL())).spec()) + | |
149 ASCIIToUTF16("'>") + | |
150 l10n_util::GetStringUTF16(IDS_GET_MORE_EXTENSIONS) + | |
151 ASCIIToUTF16("</a>")); | |
152 | |
153 source->set_json_path("strings.js"); | |
154 source->add_resource_path("extensions_ui.js", IDR_EXTENSIONS_UI_JS); | |
155 source->set_default_resource(IDR_EXTENSIONS_UI_HTML); | |
156 return source; | |
157 } | |
158 | |
159 /////////////////////////////////////////////////////////////////////////////// | |
160 // | |
161 // ExtensionsDOMHandler | |
162 // | |
163 /////////////////////////////////////////////////////////////////////////////// | |
164 | |
165 ExtensionsDOMHandler::ExtensionsDOMHandler(ExtensionService* extension_service) | |
166 : extension_service_(extension_service), | |
167 ignore_notifications_(false), | |
168 deleting_rvh_(NULL) { | |
169 RegisterForNotifications(); | |
170 } | |
171 | |
172 void ExtensionsDOMHandler::RegisterMessages() { | |
173 web_ui_->RegisterMessageCallback("requestExtensionsData", | |
174 NewCallback(this, &ExtensionsDOMHandler::HandleRequestExtensionsData)); | |
175 web_ui_->RegisterMessageCallback("toggleDeveloperMode", | |
176 NewCallback(this, &ExtensionsDOMHandler::HandleToggleDeveloperMode)); | |
177 web_ui_->RegisterMessageCallback("inspect", | |
178 NewCallback(this, &ExtensionsDOMHandler::HandleInspectMessage)); | |
179 web_ui_->RegisterMessageCallback("reload", | |
180 NewCallback(this, &ExtensionsDOMHandler::HandleReloadMessage)); | |
181 web_ui_->RegisterMessageCallback("enable", | |
182 NewCallback(this, &ExtensionsDOMHandler::HandleEnableMessage)); | |
183 web_ui_->RegisterMessageCallback("enableIncognito", | |
184 NewCallback(this, &ExtensionsDOMHandler::HandleEnableIncognitoMessage)); | |
185 web_ui_->RegisterMessageCallback("allowFileAccess", | |
186 NewCallback(this, &ExtensionsDOMHandler::HandleAllowFileAccessMessage)); | |
187 web_ui_->RegisterMessageCallback("uninstall", | |
188 NewCallback(this, &ExtensionsDOMHandler::HandleUninstallMessage)); | |
189 web_ui_->RegisterMessageCallback("options", | |
190 NewCallback(this, &ExtensionsDOMHandler::HandleOptionsMessage)); | |
191 web_ui_->RegisterMessageCallback("showButton", | |
192 NewCallback(this, &ExtensionsDOMHandler::HandleShowButtonMessage)); | |
193 web_ui_->RegisterMessageCallback("load", | |
194 NewCallback(this, &ExtensionsDOMHandler::HandleLoadMessage)); | |
195 web_ui_->RegisterMessageCallback("pack", | |
196 NewCallback(this, &ExtensionsDOMHandler::HandlePackMessage)); | |
197 web_ui_->RegisterMessageCallback("autoupdate", | |
198 NewCallback(this, &ExtensionsDOMHandler::HandleAutoUpdateMessage)); | |
199 web_ui_->RegisterMessageCallback("selectFilePath", | |
200 NewCallback(this, &ExtensionsDOMHandler::HandleSelectFilePathMessage)); | |
201 } | |
202 | |
203 void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) { | |
204 DictionaryValue results; | |
205 | |
206 // Add the extensions to the results structure. | |
207 ListValue* extensions_list = new ListValue(); | |
208 | |
209 const ExtensionList* extensions = extension_service_->extensions(); | |
210 for (ExtensionList::const_iterator extension = extensions->begin(); | |
211 extension != extensions->end(); ++extension) { | |
212 if (ShouldShowExtension(*extension)) { | |
213 extensions_list->Append(CreateExtensionDetailValue( | |
214 extension_service_, | |
215 *extension, | |
216 GetActivePagesForExtension(*extension), | |
217 true, false)); // enabled, terminated | |
218 } | |
219 } | |
220 extensions = extension_service_->disabled_extensions(); | |
221 for (ExtensionList::const_iterator extension = extensions->begin(); | |
222 extension != extensions->end(); ++extension) { | |
223 if (ShouldShowExtension(*extension)) { | |
224 extensions_list->Append(CreateExtensionDetailValue( | |
225 extension_service_, | |
226 *extension, | |
227 GetActivePagesForExtension(*extension), | |
228 false, false)); // enabled, terminated | |
229 } | |
230 } | |
231 extensions = extension_service_->terminated_extensions(); | |
232 std::vector<ExtensionPage> empty_pages; | |
233 for (ExtensionList::const_iterator extension = extensions->begin(); | |
234 extension != extensions->end(); ++extension) { | |
235 if (ShouldShowExtension(*extension)) { | |
236 extensions_list->Append(CreateExtensionDetailValue( | |
237 extension_service_, | |
238 *extension, | |
239 empty_pages, // Terminated process has no active pages. | |
240 false, true)); // enabled, terminated | |
241 } | |
242 } | |
243 results.Set("extensions", extensions_list); | |
244 | |
245 bool developer_mode = Profile::FromWebUI(web_ui_)->GetPrefs()-> | |
246 GetBoolean(prefs::kExtensionsUIDeveloperMode); | |
247 results.SetBoolean("developerMode", developer_mode); | |
248 | |
249 web_ui_->CallJavascriptFunction("returnExtensionsData", results); | |
250 } | |
251 | |
252 void ExtensionsDOMHandler::RegisterForNotifications() { | |
253 // Register for notifications that we need to reload the page. | |
254 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, | |
255 NotificationService::AllSources()); | |
256 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_CREATED, | |
257 NotificationService::AllSources()); | |
258 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
259 NotificationService::AllSources()); | |
260 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, | |
261 NotificationService::AllSources()); | |
262 registrar_.Add(this, | |
263 content::NOTIFICATION_NAV_ENTRY_COMMITTED, | |
264 NotificationService::AllSources()); | |
265 registrar_.Add(this, | |
266 content::NOTIFICATION_RENDER_VIEW_HOST_CREATED, | |
267 NotificationService::AllSources()); | |
268 registrar_.Add(this, | |
269 content::NOTIFICATION_RENDER_VIEW_HOST_DELETED, | |
270 NotificationService::AllSources()); | |
271 registrar_.Add(this, | |
272 chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED, | |
273 NotificationService::AllSources()); | |
274 registrar_.Add(this, | |
275 chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED, | |
276 NotificationService::AllSources()); | |
277 registrar_.Add(this, | |
278 chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED, | |
279 NotificationService::AllSources()); | |
280 } | |
281 | |
282 ExtensionUninstallDialog* ExtensionsDOMHandler::GetExtensionUninstallDialog() { | |
283 if (!extension_uninstall_dialog_.get()) { | |
284 extension_uninstall_dialog_.reset( | |
285 new ExtensionUninstallDialog(Profile::FromWebUI(web_ui_))); | |
286 } | |
287 return extension_uninstall_dialog_.get(); | |
288 } | |
289 | |
290 void ExtensionsDOMHandler::HandleToggleDeveloperMode(const ListValue* args) { | |
291 Profile* profile = Profile::FromWebUI(web_ui_); | |
292 bool developer_mode = | |
293 profile->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode); | |
294 profile->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, | |
295 !developer_mode); | |
296 } | |
297 | |
298 void ExtensionsDOMHandler::HandleInspectMessage(const ListValue* args) { | |
299 std::string render_process_id_str; | |
300 std::string render_view_id_str; | |
301 int render_process_id; | |
302 int render_view_id; | |
303 CHECK(args->GetSize() == 2); | |
304 CHECK(args->GetString(0, &render_process_id_str)); | |
305 CHECK(args->GetString(1, &render_view_id_str)); | |
306 CHECK(base::StringToInt(render_process_id_str, &render_process_id)); | |
307 CHECK(base::StringToInt(render_view_id_str, &render_view_id)); | |
308 RenderViewHost* host = RenderViewHost::FromID(render_process_id, | |
309 render_view_id); | |
310 if (!host) { | |
311 // This can happen if the host has gone away since the page was displayed. | |
312 return; | |
313 } | |
314 | |
315 DevToolsWindow::OpenDevToolsWindow(host); | |
316 } | |
317 | |
318 void ExtensionsDOMHandler::HandleReloadMessage(const ListValue* args) { | |
319 std::string extension_id = UTF16ToASCII(ExtractStringValue(args)); | |
320 CHECK(!extension_id.empty()); | |
321 extension_service_->ReloadExtension(extension_id); | |
322 } | |
323 | |
324 void ExtensionsDOMHandler::HandleEnableMessage(const ListValue* args) { | |
325 CHECK(args->GetSize() == 2); | |
326 std::string extension_id, enable_str; | |
327 CHECK(args->GetString(0, &extension_id)); | |
328 CHECK(args->GetString(1, &enable_str)); | |
329 const Extension* extension = | |
330 extension_service_->GetExtensionById(extension_id, true); | |
331 DCHECK(extension); | |
332 if (!Extension::UserMayDisable(extension->location())) { | |
333 LOG(ERROR) << "Attempt to enable an extension that is non-usermanagable was" | |
334 << "made. Extension id: " << extension->id(); | |
335 return; | |
336 } | |
337 | |
338 if (enable_str == "true") { | |
339 ExtensionPrefs* prefs = extension_service_->extension_prefs(); | |
340 if (prefs->DidExtensionEscalatePermissions(extension_id)) { | |
341 ShowExtensionDisabledDialog(extension_service_, | |
342 Profile::FromWebUI(web_ui_), extension); | |
343 } else { | |
344 extension_service_->EnableExtension(extension_id); | |
345 } | |
346 } else { | |
347 extension_service_->DisableExtension(extension_id); | |
348 } | |
349 } | |
350 | |
351 void ExtensionsDOMHandler::HandleEnableIncognitoMessage(const ListValue* args) { | |
352 CHECK(args->GetSize() == 2); | |
353 std::string extension_id, enable_str; | |
354 CHECK(args->GetString(0, &extension_id)); | |
355 CHECK(args->GetString(1, &enable_str)); | |
356 const Extension* extension = | |
357 extension_service_->GetExtensionById(extension_id, true); | |
358 DCHECK(extension); | |
359 | |
360 // Flipping the incognito bit will generate unload/load notifications for the | |
361 // extension, but we don't want to reload the page, because a) we've already | |
362 // updated the UI to reflect the change, and b) we want the yellow warning | |
363 // text to stay until the user has left the page. | |
364 // | |
365 // TODO(aa): This creates crapiness in some cases. For example, in a main | |
366 // window, when toggling this, the browser action will flicker because it gets | |
367 // unloaded, then reloaded. It would be better to have a dedicated | |
368 // notification for this case. | |
369 // | |
370 // Bug: http://crbug.com/41384 | |
371 ignore_notifications_ = true; | |
372 extension_service_->SetIsIncognitoEnabled(extension_id, | |
373 enable_str == "true"); | |
374 ignore_notifications_ = false; | |
375 } | |
376 | |
377 void ExtensionsDOMHandler::HandleAllowFileAccessMessage(const ListValue* args) { | |
378 CHECK(args->GetSize() == 2); | |
379 std::string extension_id, allow_str; | |
380 CHECK(args->GetString(0, &extension_id)); | |
381 CHECK(args->GetString(1, &allow_str)); | |
382 const Extension* extension = | |
383 extension_service_->GetExtensionById(extension_id, true); | |
384 DCHECK(extension); | |
385 if (!Extension::UserMayDisable(extension->location())) { | |
386 LOG(ERROR) << "Attempt to change allow file access of an extension that is " | |
387 << "non-usermanagable was made. Extension id : " | |
388 << extension->id(); | |
389 return; | |
390 } | |
391 | |
392 extension_service_->SetAllowFileAccess(extension, allow_str == "true"); | |
393 } | |
394 | |
395 void ExtensionsDOMHandler::HandleUninstallMessage(const ListValue* args) { | |
396 std::string extension_id = UTF16ToASCII(ExtractStringValue(args)); | |
397 CHECK(!extension_id.empty()); | |
398 const Extension* extension = | |
399 extension_service_->GetExtensionById(extension_id, true); | |
400 if (!extension) | |
401 extension = extension_service_->GetTerminatedExtension(extension_id); | |
402 if (!extension) | |
403 return; | |
404 | |
405 if (!Extension::UserMayDisable(extension->location())) { | |
406 LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable " | |
407 << "was made. Extension id : " << extension->id(); | |
408 return; | |
409 } | |
410 | |
411 if (!extension_id_prompting_.empty()) | |
412 return; // Only one prompt at a time. | |
413 | |
414 extension_id_prompting_ = extension_id; | |
415 | |
416 GetExtensionUninstallDialog()->ConfirmUninstall(this, extension); | |
417 } | |
418 | |
419 void ExtensionsDOMHandler::ExtensionDialogAccepted() { | |
420 DCHECK(!extension_id_prompting_.empty()); | |
421 | |
422 bool was_terminated = false; | |
423 | |
424 // The extension can be uninstalled in another window while the UI was | |
425 // showing. Do nothing in that case. | |
426 const Extension* extension = | |
427 extension_service_->GetExtensionById(extension_id_prompting_, true); | |
428 if (!extension) { | |
429 extension = extension_service_->GetTerminatedExtension( | |
430 extension_id_prompting_); | |
431 was_terminated = true; | |
432 } | |
433 if (!extension) | |
434 return; | |
435 | |
436 extension_service_->UninstallExtension(extension_id_prompting_, | |
437 false /* external_uninstall */, NULL); | |
438 extension_id_prompting_ = ""; | |
439 | |
440 // There will be no EXTENSION_UNLOADED notification for terminated | |
441 // extensions as they were already unloaded. | |
442 if (was_terminated) | |
443 HandleRequestExtensionsData(NULL); | |
444 } | |
445 | |
446 void ExtensionsDOMHandler::ExtensionDialogCanceled() { | |
447 extension_id_prompting_ = ""; | |
448 } | |
449 | |
450 void ExtensionsDOMHandler::HandleOptionsMessage(const ListValue* args) { | |
451 const Extension* extension = GetExtension(args); | |
452 if (!extension || extension->options_url().is_empty()) | |
453 return; | |
454 Profile::FromWebUI(web_ui_)->GetExtensionProcessManager()->OpenOptionsPage( | |
455 extension, NULL); | |
456 } | |
457 | |
458 void ExtensionsDOMHandler::HandleShowButtonMessage(const ListValue* args) { | |
459 const Extension* extension = GetExtension(args); | |
460 extension_service_->SetBrowserActionVisibility(extension, true); | |
461 } | |
462 | |
463 void ExtensionsDOMHandler::HandleLoadMessage(const ListValue* args) { | |
464 FilePath::StringType string_path; | |
465 CHECK(args->GetSize() == 1) << args->GetSize(); | |
466 CHECK(args->GetString(0, &string_path)); | |
467 extension_service_->LoadExtension(FilePath(string_path)); | |
468 } | |
469 | |
470 void ExtensionsDOMHandler::ShowAlert(const std::string& message) { | |
471 ListValue arguments; | |
472 arguments.Append(Value::CreateStringValue(message)); | |
473 web_ui_->CallJavascriptFunction("alert", arguments); | |
474 } | |
475 | |
476 void ExtensionsDOMHandler::HandlePackMessage(const ListValue* args) { | |
477 std::string extension_path; | |
478 std::string private_key_path; | |
479 CHECK(args->GetSize() == 2); | |
480 CHECK(args->GetString(0, &extension_path)); | |
481 CHECK(args->GetString(1, &private_key_path)); | |
482 | |
483 FilePath root_directory = | |
484 FilePath::FromWStringHack(UTF8ToWide(extension_path)); | |
485 FilePath key_file = FilePath::FromWStringHack(UTF8ToWide(private_key_path)); | |
486 | |
487 if (root_directory.empty()) { | |
488 if (extension_path.empty()) { | |
489 ShowAlert(l10n_util::GetStringUTF8( | |
490 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_REQUIRED)); | |
491 } else { | |
492 ShowAlert(l10n_util::GetStringUTF8( | |
493 IDS_EXTENSION_PACK_DIALOG_ERROR_ROOT_INVALID)); | |
494 } | |
495 | |
496 return; | |
497 } | |
498 | |
499 if (!private_key_path.empty() && key_file.empty()) { | |
500 ShowAlert(l10n_util::GetStringUTF8( | |
501 IDS_EXTENSION_PACK_DIALOG_ERROR_KEY_INVALID)); | |
502 return; | |
503 } | |
504 | |
505 pack_job_ = new PackExtensionJob(this, root_directory, key_file); | |
506 pack_job_->Start(); | |
507 } | |
508 | |
509 void ExtensionsDOMHandler::OnPackSuccess(const FilePath& crx_file, | |
510 const FilePath& pem_file) { | |
511 ShowAlert(UTF16ToUTF8(PackExtensionJob::StandardSuccessMessage(crx_file, | |
512 pem_file))); | |
513 | |
514 ListValue results; | |
515 web_ui_->CallJavascriptFunction("hidePackDialog", results); | |
516 } | |
517 | |
518 void ExtensionsDOMHandler::OnPackFailure(const std::string& error) { | |
519 ShowAlert(error); | |
520 } | |
521 | |
522 void ExtensionsDOMHandler::HandleAutoUpdateMessage(const ListValue* args) { | |
523 ExtensionUpdater* updater = extension_service_->updater(); | |
524 if (updater) | |
525 updater->CheckNow(); | |
526 } | |
527 | |
528 void ExtensionsDOMHandler::HandleSelectFilePathMessage(const ListValue* args) { | |
529 std::string select_type; | |
530 std::string operation; | |
531 CHECK(args->GetSize() == 2); | |
532 CHECK(args->GetString(0, &select_type)); | |
533 CHECK(args->GetString(1, &operation)); | |
534 | |
535 SelectFileDialog::Type type = SelectFileDialog::SELECT_FOLDER; | |
536 SelectFileDialog::FileTypeInfo info; | |
537 int file_type_index = 0; | |
538 if (select_type == "file") | |
539 type = SelectFileDialog::SELECT_OPEN_FILE; | |
540 | |
541 string16 select_title; | |
542 if (operation == "load") { | |
543 select_title = l10n_util::GetStringUTF16(IDS_EXTENSION_LOAD_FROM_DIRECTORY); | |
544 } else if (operation == "packRoot") { | |
545 select_title = l10n_util::GetStringUTF16( | |
546 IDS_EXTENSION_PACK_DIALOG_SELECT_ROOT); | |
547 } else if (operation == "pem") { | |
548 select_title = l10n_util::GetStringUTF16( | |
549 IDS_EXTENSION_PACK_DIALOG_SELECT_KEY); | |
550 info.extensions.push_back(std::vector<FilePath::StringType>()); | |
551 info.extensions.front().push_back(FILE_PATH_LITERAL("pem")); | |
552 info.extension_description_overrides.push_back( | |
553 l10n_util::GetStringUTF16( | |
554 IDS_EXTENSION_PACK_DIALOG_KEY_FILE_TYPE_DESCRIPTION)); | |
555 info.include_all_files = true; | |
556 file_type_index = 1; | |
557 } else { | |
558 NOTREACHED(); | |
559 return; | |
560 } | |
561 | |
562 load_extension_dialog_ = SelectFileDialog::Create(this); | |
563 load_extension_dialog_->SelectFile(type, select_title, FilePath(), &info, | |
564 file_type_index, FILE_PATH_LITERAL(""), web_ui_->tab_contents(), | |
565 web_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL); | |
566 } | |
567 | |
568 | |
569 void ExtensionsDOMHandler::FileSelected(const FilePath& path, int index, | |
570 void* params) { | |
571 // Add the extensions to the results structure. | |
572 ListValue results; | |
573 results.Append(Value::CreateStringValue(path.value())); | |
574 web_ui_->CallJavascriptFunction("window.handleFilePathSelected", results); | |
575 } | |
576 | |
577 void ExtensionsDOMHandler::MultiFilesSelected( | |
578 const std::vector<FilePath>& files, void* params) { | |
579 NOTREACHED(); | |
580 } | |
581 | |
582 void ExtensionsDOMHandler::Observe(int type, | |
583 const NotificationSource& source, | |
584 const NotificationDetails& details) { | |
585 switch (type) { | |
586 // We listen for notifications that will result in the page being | |
587 // repopulated with data twice for the same event in certain cases. | |
588 // For instance, EXTENSION_LOADED & EXTENSION_PROCESS_CREATED because | |
589 // we don't know about the views for an extension at EXTENSION_LOADED, but | |
590 // if we only listen to EXTENSION_PROCESS_CREATED, we'll miss extensions | |
591 // that don't have a process at startup. Similarly, NAV_ENTRY_COMMITTED & | |
592 // RENDER_VIEW_HOST_CREATED because we want to handle both | |
593 // the case of navigating from a non-extension page to an extension page in | |
594 // a TabContents (which will generate NAV_ENTRY_COMMITTED) as well as | |
595 // extension content being shown in popups and balloons (which will generate | |
596 // RENDER_VIEW_HOST_CREATED but no NAV_ENTRY_COMMITTED). | |
597 // | |
598 // Doing it this way gets everything but causes the page to be rendered | |
599 // more than we need. It doesn't seem to result in any noticeable flicker. | |
600 case content::NOTIFICATION_RENDER_VIEW_HOST_DELETED: | |
601 deleting_rvh_ = Source<RenderViewHost>(source).ptr(); | |
602 MaybeUpdateAfterNotification(); | |
603 break; | |
604 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED: | |
605 deleting_rvh_ = Details<BackgroundContents>(details)->render_view_host(); | |
606 MaybeUpdateAfterNotification(); | |
607 break; | |
608 case chrome::NOTIFICATION_EXTENSION_LOADED: | |
609 case chrome::NOTIFICATION_EXTENSION_PROCESS_CREATED: | |
610 case chrome::NOTIFICATION_EXTENSION_UNLOADED: | |
611 case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED: | |
612 case content::NOTIFICATION_RENDER_VIEW_HOST_CREATED: | |
613 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: | |
614 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: | |
615 case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: | |
616 MaybeUpdateAfterNotification(); | |
617 break; | |
618 default: | |
619 NOTREACHED(); | |
620 } | |
621 } | |
622 | |
623 const Extension* ExtensionsDOMHandler::GetExtension(const ListValue* args) { | |
624 std::string extension_id = UTF16ToASCII(ExtractStringValue(args)); | |
625 CHECK(!extension_id.empty()); | |
626 return extension_service_->GetExtensionById(extension_id, true); | |
627 } | |
628 | |
629 void ExtensionsDOMHandler::MaybeUpdateAfterNotification() { | |
630 if (!ignore_notifications_ && | |
631 web_ui_->tab_contents() && | |
632 web_ui_->tab_contents()->render_view_host()) { | |
633 HandleRequestExtensionsData(NULL); | |
634 } | |
635 deleting_rvh_ = NULL; | |
636 } | |
637 | |
638 // Static | |
639 DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( | |
640 ExtensionService* service, const Extension* extension, | |
641 const std::vector<ExtensionPage>& pages, bool enabled, bool terminated) { | |
642 DictionaryValue* extension_data = new DictionaryValue(); | |
643 GURL icon = | |
644 ExtensionIconSource::GetIconURL(extension, | |
645 Extension::EXTENSION_ICON_MEDIUM, | |
646 ExtensionIconSet::MATCH_BIGGER, | |
647 !enabled, NULL); | |
648 extension_data->SetString("id", extension->id()); | |
649 extension_data->SetString("name", extension->name()); | |
650 extension_data->SetString("description", extension->description()); | |
651 if (extension->location() == Extension::LOAD) | |
652 extension_data->SetString("path", extension->path().value()); | |
653 extension_data->SetString("version", extension->version()->GetString()); | |
654 extension_data->SetString("icon", icon.spec()); | |
655 extension_data->SetBoolean("isUnpacked", | |
656 extension->location() == Extension::LOAD); | |
657 extension_data->SetBoolean("mayDisable", | |
658 Extension::UserMayDisable(extension->location())); | |
659 extension_data->SetBoolean("enabled", enabled); | |
660 extension_data->SetBoolean("terminated", terminated); | |
661 extension_data->SetBoolean("enabledIncognito", | |
662 service ? service->IsIncognitoEnabled(extension->id()) : false); | |
663 extension_data->SetBoolean("wantsFileAccess", extension->wants_file_access()); | |
664 extension_data->SetBoolean("allowFileAccess", | |
665 service ? service->AllowFileAccess(extension) : false); | |
666 extension_data->SetBoolean("allow_reload", | |
667 extension->location() == Extension::LOAD); | |
668 extension_data->SetBoolean("is_hosted_app", extension->is_hosted_app()); | |
669 | |
670 // Determine the sort order: Extensions loaded through --load-extensions show | |
671 // up at the top. Disabled extensions show up at the bottom. | |
672 if (extension->location() == Extension::LOAD) | |
673 extension_data->SetInteger("order", 1); | |
674 else | |
675 extension_data->SetInteger("order", 2); | |
676 | |
677 if (!extension->options_url().is_empty()) | |
678 extension_data->SetString("options_url", extension->options_url().spec()); | |
679 | |
680 if (service && !service->GetBrowserActionVisibility(extension)) | |
681 extension_data->SetBoolean("enable_show_button", true); | |
682 | |
683 // Add views | |
684 ListValue* views = new ListValue; | |
685 for (std::vector<ExtensionPage>::const_iterator iter = pages.begin(); | |
686 iter != pages.end(); ++iter) { | |
687 DictionaryValue* view_value = new DictionaryValue; | |
688 if (iter->url.scheme() == chrome::kExtensionScheme) { | |
689 // No leading slash. | |
690 view_value->SetString("path", iter->url.path().substr(1)); | |
691 } else { | |
692 // For live pages, use the full URL. | |
693 view_value->SetString("path", iter->url.spec()); | |
694 } | |
695 view_value->SetInteger("renderViewId", iter->render_view_id); | |
696 view_value->SetInteger("renderProcessId", iter->render_process_id); | |
697 view_value->SetBoolean("incognito", iter->incognito); | |
698 views->Append(view_value); | |
699 } | |
700 extension_data->Set("views", views); | |
701 extension_data->SetBoolean("hasPopupAction", | |
702 extension->browser_action() || extension->page_action()); | |
703 extension_data->SetString("homepageUrl", extension->GetHomepageURL().spec()); | |
704 | |
705 return extension_data; | |
706 } | |
707 | |
708 std::vector<ExtensionPage> ExtensionsDOMHandler::GetActivePagesForExtension( | |
709 const Extension* extension) { | |
710 std::vector<ExtensionPage> result; | |
711 | |
712 // Get the extension process's active views. | |
713 ExtensionProcessManager* process_manager = | |
714 extension_service_->profile()->GetExtensionProcessManager(); | |
715 GetActivePagesForExtensionProcess( | |
716 process_manager->GetExtensionProcess(extension->url()), | |
717 extension, &result); | |
718 | |
719 // Repeat for the incognito process, if applicable. | |
720 if (extension_service_->profile()->HasOffTheRecordProfile() && | |
721 extension->incognito_split_mode()) { | |
722 ExtensionProcessManager* process_manager = | |
723 extension_service_->profile()->GetOffTheRecordProfile()-> | |
724 GetExtensionProcessManager(); | |
725 GetActivePagesForExtensionProcess( | |
726 process_manager->GetExtensionProcess(extension->url()), | |
727 extension, &result); | |
728 } | |
729 | |
730 return result; | |
731 } | |
732 | |
733 void ExtensionsDOMHandler::GetActivePagesForExtensionProcess( | |
734 RenderProcessHost* process, | |
735 const Extension* extension, | |
736 std::vector<ExtensionPage> *result) { | |
737 if (!process) | |
738 return; | |
739 | |
740 RenderProcessHost::listeners_iterator iter = process->ListenersIterator(); | |
741 for (; !iter.IsAtEnd(); iter.Advance()) { | |
742 const RenderWidgetHost* widget = | |
743 static_cast<const RenderWidgetHost*>(iter.GetCurrentValue()); | |
744 DCHECK(widget); | |
745 if (!widget || !widget->IsRenderView()) | |
746 continue; | |
747 const RenderViewHost* host = static_cast<const RenderViewHost*>(widget); | |
748 if (host == deleting_rvh_ || | |
749 ViewType::EXTENSION_POPUP == host->delegate()->GetRenderViewType() || | |
750 ViewType::EXTENSION_DIALOG == host->delegate()->GetRenderViewType()) | |
751 continue; | |
752 | |
753 GURL url = host->delegate()->GetURL(); | |
754 if (url.SchemeIs(chrome::kExtensionScheme)) { | |
755 if (url.host() != extension->id()) | |
756 continue; | |
757 } else if (!extension->web_extent().MatchesURL(url)) { | |
758 continue; | |
759 } | |
760 | |
761 result->push_back( | |
762 ExtensionPage(url, process->id(), host->routing_id(), | |
763 process->browser_context()->IsOffTheRecord())); | |
764 } | |
765 } | |
766 | |
767 ExtensionsDOMHandler::~ExtensionsDOMHandler() { | |
768 // There may be pending file dialogs, we need to tell them that we've gone | |
769 // away so they don't try and call back to us. | |
770 if (load_extension_dialog_.get()) | |
771 load_extension_dialog_->ListenerDestroyed(); | |
772 | |
773 if (pack_job_.get()) | |
774 pack_job_->ClearClient(); | |
775 | |
776 registrar_.RemoveAll(); | |
777 } | |
778 | |
779 // ExtensionsDOMHandler, public: ----------------------------------------------- | |
780 | |
781 ExtensionsUI::ExtensionsUI(TabContents* contents) : ChromeWebUI(contents) { | |
782 ExtensionService *extension_service = | |
783 GetProfile()->GetOriginalProfile()->GetExtensionService(); | |
784 | |
785 ExtensionsDOMHandler* handler = new ExtensionsDOMHandler(extension_service); | |
786 AddMessageHandler(handler); | |
787 handler->Attach(this); | |
788 | |
789 // Set up the chrome://extensions/ source. | |
790 Profile* profile = Profile::FromBrowserContext(contents->browser_context()); | |
791 profile->GetChromeURLDataManager()->AddDataSource( | |
792 CreateExtensionsUIHTMLSource()); | |
793 } | |
794 | |
795 // static | |
796 RefCountedMemory* ExtensionsUI::GetFaviconResourceBytes() { | |
797 return ResourceBundle::GetSharedInstance(). | |
798 LoadDataResourceBytes(IDR_PLUGIN); | |
799 } | |
800 | |
801 // static | |
802 void ExtensionsUI::RegisterUserPrefs(PrefService* prefs) { | |
803 prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode, | |
804 false, | |
805 PrefService::SYNCABLE_PREF); | |
806 } | |
OLD | NEW |