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
59namespace {
60
61bool 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
90ExtensionsUIHTMLSource::ExtensionsUIHTMLSource()
91    : DataSource(chrome::kChromeUIExtensionsHost, MessageLoop::current()) {
92}
93
94void 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
195std::string ExtensionsUIHTMLSource::GetMimeType(const std::string&) const {
196  return "text/html";
197}
198
199////////////////////////////////////////////////////////////////////////////////
200//
201// ExtensionsDOMHandler::IconLoader
202//
203////////////////////////////////////////////////////////////////////////////////
204
205ExtensionsDOMHandler::IconLoader::IconLoader(ExtensionsDOMHandler* handler)
206    : handler_(handler) {
207}
208
209void 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
217void ExtensionsDOMHandler::IconLoader::Cancel() {
218  handler_ = NULL;
219}
220
221void 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
284void ExtensionsDOMHandler::IconLoader::ReportResultOnUIThread(
285    DictionaryValue* json) {
286  if (handler_)
287    handler_->OnIconsLoaded(json);
288}
289
290
291///////////////////////////////////////////////////////////////////////////////
292//
293// ExtensionsDOMHandler
294//
295///////////////////////////////////////////////////////////////////////////////
296
297ExtensionsDOMHandler::ExtensionsDOMHandler(ExtensionService* extension_service)
298    : extensions_service_(extension_service),
299      ignore_notifications_(false),
300      deleting_rvh_(NULL) {
301}
302
303void 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
334void 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
396void 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
432ExtensionResource ExtensionsDOMHandler::PickExtensionIcon(
433    const Extension* extension) {
434  return extension->GetIconResource(Extension::EXTENSION_ICON_MEDIUM,
435                                    ExtensionIconSet::MATCH_BIGGER);
436}
437
438ExtensionUninstallDialog* 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
446void 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
453void 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
473void 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
479void 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
499void 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
524void 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
536void 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
554void 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
581void ExtensionsDOMHandler::ExtensionDialogCanceled() {
582  extension_id_prompting_ = "";
583}
584
585void 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
593void ExtensionsDOMHandler::HandleShowButtonMessage(const ListValue* args) {
594  const Extension* extension = GetExtension(args);
595  extensions_service_->SetBrowserActionVisibility(extension, true);
596}
597
598void 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
605void ExtensionsDOMHandler::ShowAlert(const std::string& message) {
606  ListValue arguments;
607  arguments.Append(Value::CreateStringValue(message));
608  web_ui_->CallJavascriptFunction(L"alert", arguments);
609}
610
611void 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
644void 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
653void ExtensionsDOMHandler::OnPackFailure(const std::string& error) {
654  ShowAlert(error);
655}
656
657void ExtensionsDOMHandler::HandleAutoUpdateMessage(const ListValue* args) {
658  ExtensionUpdater* updater = extensions_service_->updater();
659  if (updater)
660    updater->CheckNow();
661}
662
663void 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
704void 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
712void ExtensionsDOMHandler::MultiFilesSelected(
713    const std::vector<FilePath>& files, void* params) {
714  NOTREACHED();
715}
716
717void 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
757const 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
763void ExtensionsDOMHandler::MaybeUpdateAfterNotification() {
764  if (!ignore_notifications_ && web_ui_->tab_contents())
765    HandleRequestExtensionsData(NULL);
766  deleting_rvh_ = NULL;
767}
768
769// Static
770DictionaryValue* 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
828std::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
853void 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
885ExtensionsDOMHandler::~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
900ExtensionsUI::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
915RefCountedMemory* ExtensionsUI::GetFaviconResourceBytes() {
916  return ResourceBundle::GetSharedInstance().
917      LoadDataResourceBytes(IDR_PLUGIN);
918}
919
920// static
921void ExtensionsUI::RegisterUserPrefs(PrefService* prefs) {
922  prefs->RegisterBooleanPref(prefs::kExtensionsUIDeveloperMode, false);
923}
924