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
ShouldShowExtension(const Extension * extension)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
ExtensionsUIHTMLSource()90 ExtensionsUIHTMLSource::ExtensionsUIHTMLSource()
91 : DataSource(chrome::kChromeUIExtensionsHost, MessageLoop::current()) {
92 }
93
StartDataRequest(const std::string & path,bool is_incognito,int request_id)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
GetMimeType(const std::string &) const195 std::string ExtensionsUIHTMLSource::GetMimeType(const std::string&) const {
196 return "text/html";
197 }
198
199 ////////////////////////////////////////////////////////////////////////////////
200 //
201 // ExtensionsDOMHandler::IconLoader
202 //
203 ////////////////////////////////////////////////////////////////////////////////
204
IconLoader(ExtensionsDOMHandler * handler)205 ExtensionsDOMHandler::IconLoader::IconLoader(ExtensionsDOMHandler* handler)
206 : handler_(handler) {
207 }
208
LoadIcons(std::vector<ExtensionResource> * icons,DictionaryValue * json)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
Cancel()217 void ExtensionsDOMHandler::IconLoader::Cancel() {
218 handler_ = NULL;
219 }
220
LoadIconsOnFileThread(std::vector<ExtensionResource> * icons,DictionaryValue * json)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
ReportResultOnUIThread(DictionaryValue * json)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
ExtensionsDOMHandler(ExtensionService * extension_service)297 ExtensionsDOMHandler::ExtensionsDOMHandler(ExtensionService* extension_service)
298 : extensions_service_(extension_service),
299 ignore_notifications_(false),
300 deleting_rvh_(NULL) {
301 }
302
RegisterMessages()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
HandleRequestExtensionsData(const ListValue * args)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
OnIconsLoaded(DictionaryValue * json)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
PickExtensionIcon(const Extension * extension)432 ExtensionResource ExtensionsDOMHandler::PickExtensionIcon(
433 const Extension* extension) {
434 return extension->GetIconResource(Extension::EXTENSION_ICON_MEDIUM,
435 ExtensionIconSet::MATCH_BIGGER);
436 }
437
GetExtensionUninstallDialog()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
HandleToggleDeveloperMode(const ListValue * args)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
HandleInspectMessage(const ListValue * args)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
HandleReloadMessage(const ListValue * args)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
HandleEnableMessage(const ListValue * args)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
HandleEnableIncognitoMessage(const ListValue * args)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
HandleAllowFileAccessMessage(const ListValue * args)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
HandleUninstallMessage(const ListValue * args)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
ExtensionDialogAccepted()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
ExtensionDialogCanceled()581 void ExtensionsDOMHandler::ExtensionDialogCanceled() {
582 extension_id_prompting_ = "";
583 }
584
HandleOptionsMessage(const ListValue * args)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
HandleShowButtonMessage(const ListValue * args)593 void ExtensionsDOMHandler::HandleShowButtonMessage(const ListValue* args) {
594 const Extension* extension = GetExtension(args);
595 extensions_service_->SetBrowserActionVisibility(extension, true);
596 }
597
HandleLoadMessage(const ListValue * args)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
ShowAlert(const std::string & message)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
HandlePackMessage(const ListValue * args)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
OnPackSuccess(const FilePath & crx_file,const FilePath & pem_file)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
OnPackFailure(const std::string & error)653 void ExtensionsDOMHandler::OnPackFailure(const std::string& error) {
654 ShowAlert(error);
655 }
656
HandleAutoUpdateMessage(const ListValue * args)657 void ExtensionsDOMHandler::HandleAutoUpdateMessage(const ListValue* args) {
658 ExtensionUpdater* updater = extensions_service_->updater();
659 if (updater)
660 updater->CheckNow();
661 }
662
HandleSelectFilePathMessage(const ListValue * args)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
FileSelected(const FilePath & path,int index,void * params)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
MultiFilesSelected(const std::vector<FilePath> & files,void * params)712 void ExtensionsDOMHandler::MultiFilesSelected(
713 const std::vector<FilePath>& files, void* params) {
714 NOTREACHED();
715 }
716
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
GetExtension(const ListValue * args)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
MaybeUpdateAfterNotification()763 void ExtensionsDOMHandler::MaybeUpdateAfterNotification() {
764 if (!ignore_notifications_ && web_ui_->tab_contents())
765 HandleRequestExtensionsData(NULL);
766 deleting_rvh_ = NULL;
767 }
768
769 // Static
CreateExtensionDetailValue(ExtensionService * service,const Extension * extension,const std::vector<ExtensionPage> & pages,bool enabled,bool terminated)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
GetActivePagesForExtension(const Extension * extension)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
GetActivePagesForExtensionProcess(RenderProcessHost * process,const Extension * extension,std::vector<ExtensionPage> * result)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
~ExtensionsDOMHandler()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
ExtensionsUI(TabContents * contents)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
GetFaviconResourceBytes()915 RefCountedMemory* ExtensionsUI::GetFaviconResourceBytes() {
916 return ResourceBundle::GetSharedInstance().
917 LoadDataResourceBytes(IDR_PLUGIN);
918 }
919
920 // static
RegisterUserPrefs(PrefService * prefs)921 void ExtensionsUI::RegisterUserPrefs(PrefService* prefs) {
922 prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode, false);
923 }
924