Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 "use strict";
7 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
9 Cu.import("resource://gre/modules/Services.jsm")
10 Cu.import("resource://gre/modules/AddonManager.jsm");
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 const AMO_ICON = "chrome://browser/skin/images/amo-logo.png";
15 let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties");
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));
26 var ContextMenus = {
27 target: null,
29 init: function() {
30 document.addEventListener("contextmenu", this, false);
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);
36 // XXX - Hack to fix bug 985867 for now
37 document.addEventListener("touchstart", function() { });
38 },
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 }
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 }
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 }
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 },
71 enable: function(event) {
72 Addons.setEnabled(true, this.target.addon);
73 this.target = null;
74 },
76 disable: function (event) {
77 Addons.setEnabled(false, this.target.addon);
78 this.target = null;
79 },
81 uninstall: function (event) {
82 Addons.uninstall(this.target.addon);
83 this.target = null;
84 }
85 }
87 function init() {
88 window.addEventListener("popstate", onPopState, false);
90 AddonManager.addInstallListener(Addons);
91 AddonManager.addAddonListener(Addons);
92 Addons.init();
93 showList();
94 ContextMenus.init();
96 document.getElementById("header-button").addEventListener("click", openLink, false);
97 }
100 function uninit() {
101 AddonManager.removeInstallListener(Addons);
102 AddonManager.removeAddonListener(Addons);
103 }
105 function openLink(aEvent) {
106 try {
107 let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
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 }
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;
125 showList();
126 }
127 }
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 }
137 var Addons = {
138 _restartCount: 0,
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);
151 let img = document.createElement("img");
152 img.className = "icon";
153 img.setAttribute("src", aAddon.iconURL || AMO_ICON);
154 outer.appendChild(img);
156 let inner = document.createElement("div");
157 inner.className = "inner";
159 let details = document.createElement("div");
160 details.className = "details";
161 inner.appendChild(details);
163 let titlePart = document.createElement("div");
164 titlePart.textContent = aAddon.name;
165 titlePart.className = "title";
166 details.appendChild(titlePart);
168 let versionPart = document.createElement("div");
169 versionPart.textContent = aAddon.version;
170 versionPart.className = "version";
171 details.appendChild(versionPart);
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 }
180 outer.appendChild(inner);
181 return outer;
182 },
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);
191 let img = document.createElement("img");
192 img.className = "icon";
193 img.setAttribute("src", AMO_ICON);
194 outer.appendChild(img);
196 let inner = document.createElement("div");
197 inner.className = "inner";
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);
205 outer.appendChild(inner);
206 return outer;
207 },
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;
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 }
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;
237 return item;
238 },
240 _getElementForAddon: function(aKey) {
241 let list = document.getElementById("addons-list");
242 let element = list.querySelector("div[addonID=" + aKey.quote() + "]");
243 return element;
244 },
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 = "";
253 for (let i=0; i<aAddons.length; i++) {
254 let item = self._createItemForAddon(aAddons[i]);
255 list.appendChild(item);
256 }
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 });
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 },
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 },
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 }
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;
302 let favicon = document.querySelector("#addons-details > .addon-item .icon");
303 favicon.setAttribute("src", addon.iconURL || AMO_ICON);
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);
311 let enableBtn = document.getElementById("enable-btn");
312 if (addon.appDisabled)
313 enableBtn.setAttribute("disabled", "true");
314 else
315 enableBtn.removeAttribute("disabled");
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");
323 let box = document.querySelector("#addons-details > .addon-item .options-box");
324 box.innerHTML = "";
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);
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) { }
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 },
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;
372 let listItem = this._getElementForAddon(addon.id);
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);
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 }
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 }
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 },
422 enable: function enable() {
423 this.setEnabled(true);
424 },
426 disable: function disable() {
427 this.setEnabled(false);
428 },
430 uninstallCurrent: function uninstallCurrent() {
431 let detailItem = document.querySelector("#addons-details > .addon-item");
433 let addon = detailItem.addon;
434 if (!addon)
435 return;
437 this.uninstall(addon);
438 },
440 uninstall: function uninstall(aAddon) {
441 let list = document.getElementById("addons-list");
443 if (!aAddon) {
444 return;
445 }
447 let listItem = this._getElementForAddon(aAddon.id);
449 aAddon.uninstall();
450 if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
451 this.showRestart();
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";
459 detailItem.setAttribute("opType", opType);
460 listItem.setAttribute("opType", opType);
461 } else {
462 list.removeChild(listItem);
463 history.back();
464 }
465 },
467 cancelUninstall: function ev_cancelUninstall() {
468 let detailItem = document.querySelector("#addons-details > .addon-item");
469 let addon = detailItem.addon;
470 if (!addon)
471 return;
473 addon.cancelUninstall();
474 this.hideRestart();
476 let opType = this._getOpTypeForOperations(addon.pendingOperations);
477 detailItem.setAttribute("opType", opType);
479 let listItem = this._getElementForAddon(addon.id);
480 listItem.setAttribute("opType", opType);
481 },
483 showRestart: function showRestart() {
484 this._restartCount++;
485 gChromeWin.XPInstallObserver.showRestartPrompt();
486 },
488 hideRestart: function hideRestart() {
489 this._restartCount--;
490 if (this._restartCount == 0)
491 gChromeWin.XPInstallObserver.hideRestartPrompt();
492 },
494 onEnabled: function(aAddon) {
495 let listItem = this._getElementForAddon(aAddon.id);
496 if (!listItem)
497 return;
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 },
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;
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 }
520 if (needsRestart)
521 element.setAttribute("opType", "needs-restart");
522 },
524 onInstallFailed: function(aInstall) {
525 },
527 onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {
528 },
530 onDownloadFailed: function(aInstall) {
531 },
533 onDownloadCancelled: function(aInstall) {
534 }
535 }
537 window.addEventListener("load", init, false);
538 window.addEventListener("unload", uninit, false);