mobile/android/chrome/content/aboutAddons.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:863eaa18c748
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 "use strict";
6
7 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
8
9 Cu.import("resource://gre/modules/Services.jsm")
10 Cu.import("resource://gre/modules/AddonManager.jsm");
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12
13 const AMO_ICON = "chrome://browser/skin/images/amo-logo.png";
14
15 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties");
16
17 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function()
18 window.QueryInterface(Ci.nsIInterfaceRequestor)
19 .getInterface(Ci.nsIWebNavigation)
20 .QueryInterface(Ci.nsIDocShellTreeItem)
21 .rootTreeItem
22 .QueryInterface(Ci.nsIInterfaceRequestor)
23 .getInterface(Ci.nsIDOMWindow)
24 .QueryInterface(Ci.nsIDOMChromeWindow));
25
26 var ContextMenus = {
27 target: null,
28
29 init: function() {
30 document.addEventListener("contextmenu", this, false);
31
32 document.getElementById("contextmenu-enable").addEventListener("click", ContextMenus.enable.bind(this), false);
33 document.getElementById("contextmenu-disable").addEventListener("click", ContextMenus.disable.bind(this), false);
34 document.getElementById("contextmenu-uninstall").addEventListener("click", ContextMenus.uninstall.bind(this), false);
35
36 // XXX - Hack to fix bug 985867 for now
37 document.addEventListener("touchstart", function() { });
38 },
39
40 handleEvent: function(event) {
41 // store the target of context menu events so that we know which app to act on
42 this.target = event.target;
43 while (!this.target.hasAttribute("contextmenu")) {
44 this.target = this.target.parentNode;
45 }
46
47 if (!this.target) {
48 document.getElementById("contextmenu-enable").setAttribute("hidden", "true");
49 document.getElementById("contextmenu-disable").setAttribute("hidden", "true");
50 document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true");
51 return;
52 }
53
54 let addon = this.target.addon;
55 if (addon.scope == AddonManager.SCOPE_APPLICATION) {
56 document.getElementById("contextmenu-uninstall").setAttribute("hidden", "true");
57 } else {
58 document.getElementById("contextmenu-uninstall").removeAttribute("hidden");
59 }
60
61 let enabled = this.target.getAttribute("isDisabled") != "true";
62 if (enabled) {
63 document.getElementById("contextmenu-enable").setAttribute("hidden", "true");
64 document.getElementById("contextmenu-disable").removeAttribute("hidden");
65 } else {
66 document.getElementById("contextmenu-enable").removeAttribute("hidden");
67 document.getElementById("contextmenu-disable").setAttribute("hidden", "true");
68 }
69 },
70
71 enable: function(event) {
72 Addons.setEnabled(true, this.target.addon);
73 this.target = null;
74 },
75
76 disable: function (event) {
77 Addons.setEnabled(false, this.target.addon);
78 this.target = null;
79 },
80
81 uninstall: function (event) {
82 Addons.uninstall(this.target.addon);
83 this.target = null;
84 }
85 }
86
87 function init() {
88 window.addEventListener("popstate", onPopState, false);
89
90 AddonManager.addInstallListener(Addons);
91 AddonManager.addAddonListener(Addons);
92 Addons.init();
93 showList();
94 ContextMenus.init();
95
96 document.getElementById("header-button").addEventListener("click", openLink, false);
97 }
98
99
100 function uninit() {
101 AddonManager.removeInstallListener(Addons);
102 AddonManager.removeAddonListener(Addons);
103 }
104
105 function openLink(aEvent) {
106 try {
107 let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
108
109 let url = formatter.formatURLPref(aEvent.currentTarget.getAttribute("pref"));
110 let BrowserApp = gChromeWin.BrowserApp;
111 BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id });
112 } catch (ex) {}
113 }
114
115 function onPopState(aEvent) {
116 // Called when back/forward is used to change the state of the page
117 if (aEvent.state) {
118 // Show the detail page for an addon
119 Addons.showDetails(Addons._getElementForAddon(aEvent.state.id));
120 } else {
121 // Clear any previous detail addon
122 let detailItem = document.querySelector("#addons-details > .addon-item");
123 detailItem.addon = null;
124
125 showList();
126 }
127 }
128
129 function showList() {
130 // Hide the detail page and show the list
131 let details = document.querySelector("#addons-details");
132 details.style.display = "none";
133 let list = document.querySelector("#addons-list");
134 list.style.display = "block";
135 }
136
137 var Addons = {
138 _restartCount: 0,
139
140 _createItem: function _createItem(aAddon) {
141 let outer = document.createElement("div");
142 outer.setAttribute("addonID", aAddon.id);
143 outer.className = "addon-item list-item";
144 outer.setAttribute("role", "button");
145 outer.setAttribute("contextmenu", "addonmenu");
146 outer.addEventListener("click", function() {
147 this.showDetails(outer);
148 history.pushState({ id: aAddon.id }, document.title);
149 }.bind(this), true);
150
151 let img = document.createElement("img");
152 img.className = "icon";
153 img.setAttribute("src", aAddon.iconURL || AMO_ICON);
154 outer.appendChild(img);
155
156 let inner = document.createElement("div");
157 inner.className = "inner";
158
159 let details = document.createElement("div");
160 details.className = "details";
161 inner.appendChild(details);
162
163 let titlePart = document.createElement("div");
164 titlePart.textContent = aAddon.name;
165 titlePart.className = "title";
166 details.appendChild(titlePart);
167
168 let versionPart = document.createElement("div");
169 versionPart.textContent = aAddon.version;
170 versionPart.className = "version";
171 details.appendChild(versionPart);
172
173 if ("description" in aAddon) {
174 let descPart = document.createElement("div");
175 descPart.textContent = aAddon.description;
176 descPart.className = "description";
177 inner.appendChild(descPart);
178 }
179
180 outer.appendChild(inner);
181 return outer;
182 },
183
184 _createBrowseItem: function _createBrowseItem() {
185 let outer = document.createElement("div");
186 outer.className = "addon-item list-item";
187 outer.setAttribute("role", "button");
188 outer.setAttribute("pref", "extensions.getAddons.browseAddons");
189 outer.addEventListener("click", openLink, true);
190
191 let img = document.createElement("img");
192 img.className = "icon";
193 img.setAttribute("src", AMO_ICON);
194 outer.appendChild(img);
195
196 let inner = document.createElement("div");
197 inner.className = "inner";
198
199 let title = document.createElement("div");
200 title.id = "browse-title";
201 title.className = "title";
202 title.textContent = gStringBundle.GetStringFromName("addons.browseAll");
203 inner.appendChild(title);
204
205 outer.appendChild(inner);
206 return outer;
207 },
208
209 _createItemForAddon: function _createItemForAddon(aAddon) {
210 let appManaged = (aAddon.scope == AddonManager.SCOPE_APPLICATION);
211 let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
212 let updateable = (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE) > 0;
213 let uninstallable = (aAddon.permissions & AddonManager.PERM_CAN_UNINSTALL) > 0;
214
215 let blocked = "";
216 switch(aAddon.blocklistState) {
217 case Ci.nsIBlocklistService.STATE_BLOCKED:
218 blocked = "blocked";
219 break;
220 case Ci.nsIBlocklistService.STATE_SOFTBLOCKED:
221 blocked = "softBlocked";
222 break;
223 case Ci.nsIBlocklistService.STATE_OUTDATED:
224 blocked = "outdated";
225 break;
226 }
227
228 let item = this._createItem(aAddon);
229 item.setAttribute("isDisabled", !aAddon.isActive);
230 item.setAttribute("opType", opType);
231 item.setAttribute("updateable", updateable);
232 if (blocked)
233 item.setAttribute("blockedStatus", blocked);
234 item.setAttribute("optionsURL", aAddon.optionsURL || "");
235 item.addon = aAddon;
236
237 return item;
238 },
239
240 _getElementForAddon: function(aKey) {
241 let list = document.getElementById("addons-list");
242 let element = list.querySelector("div[addonID=" + aKey.quote() + "]");
243 return element;
244 },
245
246 init: function init() {
247 let self = this;
248 AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(aAddons) {
249 // Clear all content before filling the addons
250 let list = document.getElementById("addons-list");
251 list.innerHTML = "";
252
253 for (let i=0; i<aAddons.length; i++) {
254 let item = self._createItemForAddon(aAddons[i]);
255 list.appendChild(item);
256 }
257
258 // Add a "Browse all Firefox Add-ons" item to the bottom of the list.
259 let browseItem = self._createBrowseItem();
260 list.appendChild(browseItem);
261 });
262
263 document.getElementById("uninstall-btn").addEventListener("click", Addons.uninstallCurrent.bind(this), false);
264 document.getElementById("cancel-btn").addEventListener("click", Addons.cancelUninstall.bind(this), false);
265 document.getElementById("disable-btn").addEventListener("click", Addons.disable.bind(this), false);
266 document.getElementById("enable-btn").addEventListener("click", Addons.enable.bind(this), false);
267 },
268
269 _getOpTypeForOperations: function _getOpTypeForOperations(aOperations) {
270 if (aOperations & AddonManager.PENDING_UNINSTALL)
271 return "needs-uninstall";
272 if (aOperations & AddonManager.PENDING_ENABLE)
273 return "needs-enable";
274 if (aOperations & AddonManager.PENDING_DISABLE)
275 return "needs-disable";
276 return "";
277 },
278
279 showDetails: function showDetails(aListItem) {
280 // This function removes and returns the text content of aNode without
281 // removing any child elements. Removing the text nodes ensures any XBL
282 // bindings apply properly.
283 function stripTextNodes(aNode) {
284 var text = "";
285 for (var i = 0; i < aNode.childNodes.length; i++) {
286 if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
287 text += aNode.childNodes[i].textContent;
288 aNode.removeChild(aNode.childNodes[i--]);
289 } else {
290 text += stripTextNodes(aNode.childNodes[i]);
291 }
292 }
293 return text;
294 }
295
296 let detailItem = document.querySelector("#addons-details > .addon-item");
297 detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled"));
298 detailItem.setAttribute("opType", aListItem.getAttribute("opType"));
299 detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL"));
300 let addon = detailItem.addon = aListItem.addon;
301
302 let favicon = document.querySelector("#addons-details > .addon-item .icon");
303 favicon.setAttribute("src", addon.iconURL || AMO_ICON);
304
305 detailItem.querySelector(".title").textContent = addon.name;
306 detailItem.querySelector(".version").textContent = addon.version;
307 detailItem.querySelector(".description-full").textContent = addon.description;
308 detailItem.querySelector(".status-uninstalled").textContent =
309 gStringBundle.formatStringFromName("addonStatus.uninstalled", [addon.name], 1);
310
311 let enableBtn = document.getElementById("enable-btn");
312 if (addon.appDisabled)
313 enableBtn.setAttribute("disabled", "true");
314 else
315 enableBtn.removeAttribute("disabled");
316
317 let uninstallBtn = document.getElementById("uninstall-btn");
318 if (addon.scope == AddonManager.SCOPE_APPLICATION)
319 uninstallBtn.setAttribute("disabled", "true");
320 else
321 uninstallBtn.removeAttribute("disabled");
322
323 let box = document.querySelector("#addons-details > .addon-item .options-box");
324 box.innerHTML = "";
325
326 // Retrieve the extensions preferences
327 try {
328 let optionsURL = aListItem.getAttribute("optionsURL");
329 let xhr = new XMLHttpRequest();
330 xhr.open("GET", optionsURL, false);
331 xhr.send();
332 if (xhr.responseXML) {
333 // Only allow <setting> for now
334 let settings = xhr.responseXML.querySelectorAll(":root > setting");
335 if (settings.length > 0) {
336 for (let i = 0; i < settings.length; i++) {
337 var setting = settings[i];
338 var desc = stripTextNodes(setting).trim();
339 if (!setting.hasAttribute("desc"))
340 setting.setAttribute("desc", desc);
341 box.appendChild(setting);
342 }
343 // Send an event so add-ons can prepopulate any non-preference based
344 // settings
345 let event = document.createEvent("Events");
346 event.initEvent("AddonOptionsLoad", true, false);
347 window.dispatchEvent(event);
348
349 // Also send a notification to match the behavior of desktop Firefox
350 let id = aListItem.getAttribute("addonID");
351 Services.obs.notifyObservers(document, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, id);
352 } else {
353 // No options, so hide the header and reset the list item
354 detailItem.setAttribute("optionsURL", "");
355 aListItem.setAttribute("optionsURL", "");
356 }
357 }
358 } catch (e) { }
359
360 let list = document.querySelector("#addons-list");
361 list.style.display = "none";
362 let details = document.querySelector("#addons-details");
363 details.style.display = "block";
364 },
365
366 setEnabled: function setEnabled(aValue, aAddon) {
367 let detailItem = document.querySelector("#addons-details > .addon-item");
368 let addon = aAddon || detailItem.addon;
369 if (!addon)
370 return;
371
372 let listItem = this._getElementForAddon(addon.id);
373
374 let opType;
375 if (addon.type == "theme") {
376 if (aValue) {
377 // We can have only one theme enabled, so disable the current one if any
378 let list = document.getElementById("addons-list");
379 let item = list.firstElementChild;
380 while (item) {
381 if (item.addon && (item.addon.type == "theme") && (item.addon.isActive)) {
382 item.addon.userDisabled = true;
383 item.setAttribute("isDisabled", true);
384 break;
385 }
386 item = item.nextSibling;
387 }
388 }
389 addon.userDisabled = !aValue;
390 } else if (addon.type == "locale") {
391 addon.userDisabled = !aValue;
392 } else {
393 addon.userDisabled = !aValue;
394 opType = this._getOpTypeForOperations(addon.pendingOperations);
395
396 if ((addon.pendingOperations & AddonManager.PENDING_ENABLE) ||
397 (addon.pendingOperations & AddonManager.PENDING_DISABLE)) {
398 this.showRestart();
399 } else if (listItem && /needs-(enable|disable)/.test(listItem.getAttribute("opType"))) {
400 this.hideRestart();
401 }
402 }
403
404 if (addon == detailItem.addon) {
405 detailItem.setAttribute("isDisabled", !aValue);
406 if (opType)
407 detailItem.setAttribute("opType", opType);
408 else
409 detailItem.removeAttribute("opType");
410 }
411
412 // Sync to the list item
413 if (listItem) {
414 listItem.setAttribute("isDisabled", !aValue);
415 if (opType)
416 listItem.setAttribute("opType", opType);
417 else
418 listItem.removeAttribute("opType");
419 }
420 },
421
422 enable: function enable() {
423 this.setEnabled(true);
424 },
425
426 disable: function disable() {
427 this.setEnabled(false);
428 },
429
430 uninstallCurrent: function uninstallCurrent() {
431 let detailItem = document.querySelector("#addons-details > .addon-item");
432
433 let addon = detailItem.addon;
434 if (!addon)
435 return;
436
437 this.uninstall(addon);
438 },
439
440 uninstall: function uninstall(aAddon) {
441 let list = document.getElementById("addons-list");
442
443 if (!aAddon) {
444 return;
445 }
446
447 let listItem = this._getElementForAddon(aAddon.id);
448
449 aAddon.uninstall();
450 if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
451 this.showRestart();
452
453 // A disabled addon doesn't need a restart so it has no pending ops and
454 // can't be cancelled
455 let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
456 if (!aAddon.isActive && opType == "")
457 opType = "needs-uninstall";
458
459 detailItem.setAttribute("opType", opType);
460 listItem.setAttribute("opType", opType);
461 } else {
462 list.removeChild(listItem);
463 history.back();
464 }
465 },
466
467 cancelUninstall: function ev_cancelUninstall() {
468 let detailItem = document.querySelector("#addons-details > .addon-item");
469 let addon = detailItem.addon;
470 if (!addon)
471 return;
472
473 addon.cancelUninstall();
474 this.hideRestart();
475
476 let opType = this._getOpTypeForOperations(addon.pendingOperations);
477 detailItem.setAttribute("opType", opType);
478
479 let listItem = this._getElementForAddon(addon.id);
480 listItem.setAttribute("opType", opType);
481 },
482
483 showRestart: function showRestart() {
484 this._restartCount++;
485 gChromeWin.XPInstallObserver.showRestartPrompt();
486 },
487
488 hideRestart: function hideRestart() {
489 this._restartCount--;
490 if (this._restartCount == 0)
491 gChromeWin.XPInstallObserver.hideRestartPrompt();
492 },
493
494 onEnabled: function(aAddon) {
495 let listItem = this._getElementForAddon(aAddon.id);
496 if (!listItem)
497 return;
498
499 // Reload the details to pick up any options now that it's enabled.
500 listItem.setAttribute("optionsURL", aAddon.optionsURL || "");
501 let detailItem = document.querySelector("#addons-details > .addon-item");
502 if (aAddon == detailItem.addon)
503 this.showDetails(listItem);
504 },
505
506 onInstallEnded: function(aInstall, aAddon) {
507 let needsRestart = false;
508 if (aInstall.existingAddon && (aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE))
509 needsRestart = true;
510 else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL)
511 needsRestart = true;
512
513 let list = document.getElementById("addons-list");
514 let element = this._getElementForAddon(aAddon.id);
515 if (!element) {
516 element = this._createItemForAddon(aAddon);
517 list.insertBefore(element, list.firstElementChild);
518 }
519
520 if (needsRestart)
521 element.setAttribute("opType", "needs-restart");
522 },
523
524 onInstallFailed: function(aInstall) {
525 },
526
527 onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {
528 },
529
530 onDownloadFailed: function(aInstall) {
531 },
532
533 onDownloadCancelled: function(aInstall) {
534 }
535 }
536
537 window.addEventListener("load", init, false);
538 window.addEventListener("unload", uninit, false);

mercurial