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
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/DownloadUtils.jsm");
15 Cu.import("resource://gre/modules/AddonManager.jsm");
16 Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
18 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
19 "resource://gre/modules/PluralForm.jsm");
21 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
22 return Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}).
23 BrowserToolboxProcess;
24 });
25 XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
26 "resource:///modules/experiments/Experiments.jsm");
28 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
29 const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
30 const PREF_XPI_ENABLED = "xpinstall.enabled";
31 const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
32 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
33 const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
34 const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
35 const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
36 const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled";
37 const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled";
39 const LOADING_MSG_DELAY = 100;
41 const SEARCH_SCORE_MULTIPLIER_NAME = 2;
42 const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
44 // Use integers so search scores are sortable by nsIXULSortService
45 const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
46 const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
47 const SEARCH_SCORE_MATCH_SUBSTRING = 3;
49 const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
50 const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";
52 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
54 const VIEW_DEFAULT = "addons://discover/";
56 var gStrings = {};
57 XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
58 "@mozilla.org/intl/stringbundle;1",
59 "nsIStringBundleService");
61 XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() {
62 return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
63 });
64 XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() {
65 return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
66 });
67 XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() {
68 return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties");
69 });
71 XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() {
72 return this.brand.GetStringFromName("brandShortName");
73 });
74 XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() {
75 return Services.appinfo.version;
76 });
78 document.addEventListener("load", initialize, true);
79 window.addEventListener("unload", shutdown, false);
81 var gPendingInitializations = 1;
82 this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0);
84 function initialize(event) {
85 // XXXbz this listener gets _all_ load events for all nodes in the
86 // document... but relies on not being called "too early".
87 if (event.target instanceof XMLStylesheetProcessingInstruction) {
88 return;
89 }
90 document.removeEventListener("load", initialize, true);
92 let globalCommandSet = document.getElementById("globalCommandSet");
93 globalCommandSet.addEventListener("command", function(event) {
94 gViewController.doCommand(event.target.id);
95 });
97 let viewCommandSet = document.getElementById("viewCommandSet");
98 viewCommandSet.addEventListener("commandupdate", function(event) {
99 gViewController.updateCommands();
100 });
101 viewCommandSet.addEventListener("command", function(event) {
102 gViewController.doCommand(event.target.id);
103 });
105 let detailScreenshot = document.getElementById("detail-screenshot");
106 detailScreenshot.addEventListener("load", function(event) {
107 this.removeAttribute("loading");
108 });
109 detailScreenshot.addEventListener("error", function(event) {
110 this.setAttribute("loading", "error");
111 });
113 let addonPage = document.getElementById("addons-page");
114 addonPage.addEventListener("dragenter", function(event) {
115 gDragDrop.onDragOver(event);
116 });
117 addonPage.addEventListener("dragover", function(event) {
118 gDragDrop.onDragOver(event);
119 });
120 addonPage.addEventListener("drop", function(event) {
121 gDragDrop.onDrop(event);
122 });
123 addonPage.addEventListener("keypress", function(event) {
124 gHeader.onKeyPress(event);
125 });
127 gViewController.initialize();
128 gCategories.initialize();
129 gHeader.initialize();
130 gEventManager.initialize();
131 Services.obs.addObserver(sendEMPong, "EM-ping", false);
132 Services.obs.notifyObservers(window, "EM-loaded", "");
134 // If the initial view has already been selected (by a call to loadView from
135 // the above notifications) then bail out now
136 if (gViewController.initialViewSelected)
137 return;
139 // If there is a history state to restore then use that
140 if (window.history.state) {
141 gViewController.updateState(window.history.state);
142 return;
143 }
145 // Default to the last selected category
146 var view = gCategories.node.value;
148 // Allow passing in a view through the window arguments
149 if ("arguments" in window && window.arguments.length > 0 &&
150 window.arguments[0] !== null && "view" in window.arguments[0]) {
151 view = window.arguments[0].view;
152 }
154 gViewController.loadInitialView(view);
156 Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false);
157 Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false);
158 }
160 function notifyInitialized() {
161 if (!gIsInitializing)
162 return;
164 gPendingInitializations--;
165 if (!gIsInitializing) {
166 var event = document.createEvent("Events");
167 event.initEvent("Initialized", true, true);
168 document.dispatchEvent(event);
169 }
170 }
172 function shutdown() {
173 gCategories.shutdown();
174 gSearchView.shutdown();
175 gEventManager.shutdown();
176 gViewController.shutdown();
177 Services.obs.removeObserver(sendEMPong, "EM-ping");
178 Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged);
179 Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged);
180 }
182 function sendEMPong(aSubject, aTopic, aData) {
183 Services.obs.notifyObservers(window, "EM-pong", "");
184 }
186 // Used by external callers to load a specific view into the manager
187 function loadView(aViewId) {
188 if (!gViewController.initialViewSelected) {
189 // The caller opened the window and immediately loaded the view so it
190 // should be the initial history entry
192 gViewController.loadInitialView(aViewId);
193 } else {
194 gViewController.loadView(aViewId);
195 }
196 }
198 function isDiscoverEnabled() {
199 if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
200 return false;
202 try {
203 if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED))
204 return false;
205 } catch (e) {}
207 try {
208 if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED))
209 return false;
210 } catch (e) {}
212 return true;
213 }
215 function getExperimentEndDate(aAddon) {
216 if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
217 return 0;
218 }
220 if (!aAddon.isActive) {
221 return aAddon.endDate;
222 }
224 let experiment = Experiments.instance().getActiveExperiment();
225 if (!experiment) {
226 return 0;
227 }
229 return experiment.endDate;
230 }
232 /**
233 * Obtain the main DOMWindow for the current context.
234 */
235 function getMainWindow() {
236 return window.QueryInterface(Ci.nsIInterfaceRequestor)
237 .getInterface(Ci.nsIWebNavigation)
238 .QueryInterface(Ci.nsIDocShellTreeItem)
239 .rootTreeItem
240 .QueryInterface(Ci.nsIInterfaceRequestor)
241 .getInterface(Ci.nsIDOMWindow);
242 }
244 /**
245 * Obtain the DOMWindow that can open a preferences pane.
246 *
247 * This is essentially "get the browser chrome window" with the added check
248 * that the supposed browser chrome window is capable of opening a preferences
249 * pane.
250 *
251 * This may return null if we can't find the browser chrome window.
252 */
253 function getMainWindowWithPreferencesPane() {
254 let mainWindow = getMainWindow();
255 if (mainWindow && "openAdvancedPreferences" in mainWindow) {
256 return mainWindow;
257 } else {
258 return null;
259 }
260 }
262 /**
263 * A wrapper around the HTML5 session history service that allows the browser
264 * back/forward controls to work within the manager
265 */
266 var HTML5History = {
267 get index() {
268 return window.QueryInterface(Ci.nsIInterfaceRequestor)
269 .getInterface(Ci.nsIWebNavigation)
270 .sessionHistory.index;
271 },
273 get canGoBack() {
274 return window.QueryInterface(Ci.nsIInterfaceRequestor)
275 .getInterface(Ci.nsIWebNavigation)
276 .canGoBack;
277 },
279 get canGoForward() {
280 return window.QueryInterface(Ci.nsIInterfaceRequestor)
281 .getInterface(Ci.nsIWebNavigation)
282 .canGoForward;
283 },
285 back: function HTML5History_back() {
286 window.history.back();
287 gViewController.updateCommand("cmd_back");
288 gViewController.updateCommand("cmd_forward");
289 },
291 forward: function HTML5History_forward() {
292 window.history.forward();
293 gViewController.updateCommand("cmd_back");
294 gViewController.updateCommand("cmd_forward");
295 },
297 pushState: function HTML5History_pushState(aState) {
298 window.history.pushState(aState, document.title);
299 },
301 replaceState: function HTML5History_replaceState(aState) {
302 window.history.replaceState(aState, document.title);
303 },
305 popState: function HTML5History_popState() {
306 function onStatePopped(aEvent) {
307 window.removeEventListener("popstate", onStatePopped, true);
308 // TODO To ensure we can't go forward again we put an additional entry
309 // for the current state into the history. Ideally we would just strip
310 // the history but there doesn't seem to be a way to do that. Bug 590661
311 window.history.pushState(aEvent.state, document.title);
312 }
313 window.addEventListener("popstate", onStatePopped, true);
314 window.history.back();
315 gViewController.updateCommand("cmd_back");
316 gViewController.updateCommand("cmd_forward");
317 }
318 };
320 /**
321 * A wrapper around a fake history service
322 */
323 var FakeHistory = {
324 pos: 0,
325 states: [null],
327 get index() {
328 return this.pos;
329 },
331 get canGoBack() {
332 return this.pos > 0;
333 },
335 get canGoForward() {
336 return (this.pos + 1) < this.states.length;
337 },
339 back: function FakeHistory_back() {
340 if (this.pos == 0)
341 throw Components.Exception("Cannot go back from this point");
343 this.pos--;
344 gViewController.updateState(this.states[this.pos]);
345 gViewController.updateCommand("cmd_back");
346 gViewController.updateCommand("cmd_forward");
347 },
349 forward: function FakeHistory_forward() {
350 if ((this.pos + 1) >= this.states.length)
351 throw Components.Exception("Cannot go forward from this point");
353 this.pos++;
354 gViewController.updateState(this.states[this.pos]);
355 gViewController.updateCommand("cmd_back");
356 gViewController.updateCommand("cmd_forward");
357 },
359 pushState: function FakeHistory_pushState(aState) {
360 this.pos++;
361 this.states.splice(this.pos, this.states.length);
362 this.states.push(aState);
363 },
365 replaceState: function FakeHistory_replaceState(aState) {
366 this.states[this.pos] = aState;
367 },
369 popState: function FakeHistory_popState() {
370 if (this.pos == 0)
371 throw Components.Exception("Cannot popState from this view");
373 this.states.splice(this.pos, this.states.length);
374 this.pos--;
376 gViewController.updateState(this.states[this.pos]);
377 gViewController.updateCommand("cmd_back");
378 gViewController.updateCommand("cmd_forward");
379 }
380 };
382 // If the window has a session history then use the HTML5 History wrapper
383 // otherwise use our fake history implementation
384 if (window.QueryInterface(Ci.nsIInterfaceRequestor)
385 .getInterface(Ci.nsIWebNavigation)
386 .sessionHistory) {
387 var gHistory = HTML5History;
388 }
389 else {
390 gHistory = FakeHistory;
391 }
393 var gEventManager = {
394 _listeners: {},
395 _installListeners: [],
397 initialize: function gEM_initialize() {
398 var self = this;
399 const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling",
400 "onDisabled", "onUninstalling", "onUninstalled",
401 "onInstalled", "onOperationCancelled",
402 "onUpdateAvailable", "onUpdateFinished",
403 "onCompatibilityUpdateAvailable",
404 "onPropertyChanged"];
405 for (let evt of ADDON_EVENTS) {
406 let event = evt;
407 self[event] = function initialize_delegateAddonEvent(...aArgs) {
408 self.delegateAddonEvent(event, aArgs);
409 };
410 }
412 const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted",
413 "onDownloadEnded", "onDownloadFailed",
414 "onDownloadProgress", "onDownloadCancelled",
415 "onInstallStarted", "onInstallEnded",
416 "onInstallFailed", "onInstallCancelled",
417 "onExternalInstall"];
418 for (let evt of INSTALL_EVENTS) {
419 let event = evt;
420 self[event] = function initialize_delegateInstallEvent(...aArgs) {
421 self.delegateInstallEvent(event, aArgs);
422 };
423 }
425 AddonManager.addManagerListener(this);
426 AddonManager.addInstallListener(this);
427 AddonManager.addAddonListener(this);
429 this.refreshGlobalWarning();
430 this.refreshAutoUpdateDefault();
432 var contextMenu = document.getElementById("addonitem-popup");
433 contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() {
434 var addon = gViewController.currentViewObj.getSelectedAddon();
435 contextMenu.setAttribute("addontype", addon.type);
437 var menuSep = document.getElementById("addonitem-menuseparator");
438 var countEnabledMenuCmds = 0;
439 for (let child of contextMenu.children) {
440 if (child.nodeName == "menuitem" &&
441 gViewController.isCommandEnabled(child.command)) {
442 countEnabledMenuCmds++;
443 }
444 }
446 // with only one menu item, we hide the menu separator
447 menuSep.hidden = (countEnabledMenuCmds <= 1);
449 }, false);
450 },
452 shutdown: function gEM_shutdown() {
453 AddonManager.removeManagerListener(this);
454 AddonManager.removeInstallListener(this);
455 AddonManager.removeAddonListener(this);
456 },
458 registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) {
459 if (!(aAddonId in this._listeners))
460 this._listeners[aAddonId] = [];
461 else if (this._listeners[aAddonId].indexOf(aListener) != -1)
462 return;
463 this._listeners[aAddonId].push(aListener);
464 },
466 unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) {
467 if (!(aAddonId in this._listeners))
468 return;
469 var index = this._listeners[aAddonId].indexOf(aListener);
470 if (index == -1)
471 return;
472 this._listeners[aAddonId].splice(index, 1);
473 },
475 registerInstallListener: function gEM_registerInstallListener(aListener) {
476 if (this._installListeners.indexOf(aListener) != -1)
477 return;
478 this._installListeners.push(aListener);
479 },
481 unregisterInstallListener: function gEM_unregisterInstallListener(aListener) {
482 var i = this._installListeners.indexOf(aListener);
483 if (i == -1)
484 return;
485 this._installListeners.splice(i, 1);
486 },
488 delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) {
489 var addon = aParams.shift();
490 if (!(addon.id in this._listeners))
491 return;
493 var listeners = this._listeners[addon.id];
494 for (let listener of listeners) {
495 if (!(aEvent in listener))
496 continue;
497 try {
498 listener[aEvent].apply(listener, aParams);
499 } catch(e) {
500 // this shouldn't be fatal
501 Cu.reportError(e);
502 }
503 }
504 },
506 delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) {
507 var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon;
508 // If the install is an update then send the event to all listeners
509 // registered for the existing add-on
510 if (existingAddon)
511 this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams));
513 for (let listener of this._installListeners) {
514 if (!(aEvent in listener))
515 continue;
516 try {
517 listener[aEvent].apply(listener, aParams);
518 } catch(e) {
519 // this shouldn't be fatal
520 Cu.reportError(e);
521 }
522 }
523 },
525 refreshGlobalWarning: function gEM_refreshGlobalWarning() {
526 var page = document.getElementById("addons-page");
528 if (Services.appinfo.inSafeMode) {
529 page.setAttribute("warning", "safemode");
530 return;
531 }
533 if (AddonManager.checkUpdateSecurityDefault &&
534 !AddonManager.checkUpdateSecurity) {
535 page.setAttribute("warning", "updatesecurity");
536 return;
537 }
539 if (!AddonManager.checkCompatibility) {
540 page.setAttribute("warning", "checkcompatibility");
541 return;
542 }
544 page.removeAttribute("warning");
545 },
547 refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() {
548 var updateEnabled = AddonManager.updateEnabled;
549 var autoUpdateDefault = AddonManager.autoUpdateDefault;
551 // The checkbox needs to reflect that both prefs need to be true
552 // for updates to be checked for and applied automatically
553 document.getElementById("utils-autoUpdateDefault")
554 .setAttribute("checked", updateEnabled && autoUpdateDefault);
556 document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault;
557 document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault;
558 },
560 onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() {
561 this.refreshGlobalWarning();
562 },
564 onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() {
565 this.refreshGlobalWarning();
566 },
568 onUpdateModeChanged: function gEM_onUpdateModeChanged() {
569 this.refreshAutoUpdateDefault();
570 }
571 };
574 var gViewController = {
575 viewPort: null,
576 currentViewId: "",
577 currentViewObj: null,
578 currentViewRequest: 0,
579 viewObjects: {},
580 viewChangeCallback: null,
581 initialViewSelected: false,
582 lastHistoryIndex: -1,
584 initialize: function gVC_initialize() {
585 this.viewPort = document.getElementById("view-port");
587 this.viewObjects["search"] = gSearchView;
588 this.viewObjects["discover"] = gDiscoverView;
589 this.viewObjects["list"] = gListView;
590 this.viewObjects["detail"] = gDetailView;
591 this.viewObjects["updates"] = gUpdatesView;
593 for each (let view in this.viewObjects)
594 view.initialize();
596 window.controllers.appendController(this);
598 window.addEventListener("popstate",
599 function window_onStatePopped(e) {
600 gViewController.updateState(e.state);
601 },
602 false);
603 },
605 shutdown: function gVC_shutdown() {
606 if (this.currentViewObj)
607 this.currentViewObj.hide();
608 this.currentViewRequest = 0;
610 for each(let view in this.viewObjects) {
611 if ("shutdown" in view) {
612 try {
613 view.shutdown();
614 } catch(e) {
615 // this shouldn't be fatal
616 Cu.reportError(e);
617 }
618 }
619 }
621 window.controllers.removeController(this);
622 },
624 updateState: function gVC_updateState(state) {
625 try {
626 this.loadViewInternal(state.view, state.previousView, state);
627 this.lastHistoryIndex = gHistory.index;
628 }
629 catch (e) {
630 // The attempt to load the view failed, try moving further along history
631 if (this.lastHistoryIndex > gHistory.index) {
632 if (gHistory.canGoBack)
633 gHistory.back();
634 else
635 gViewController.replaceView(VIEW_DEFAULT);
636 } else {
637 if (gHistory.canGoForward)
638 gHistory.forward();
639 else
640 gViewController.replaceView(VIEW_DEFAULT);
641 }
642 }
643 },
645 parseViewId: function gVC_parseViewId(aViewId) {
646 var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
647 var [,viewType, viewParam] = aViewId.match(matchRegex) || [];
648 return {type: viewType, param: decodeURIComponent(viewParam)};
649 },
651 get isLoading() {
652 return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
653 },
655 loadView: function gVC_loadView(aViewId) {
656 var isRefresh = false;
657 if (aViewId == this.currentViewId) {
658 if (this.isLoading)
659 return;
660 if (!("refresh" in this.currentViewObj))
661 return;
662 if (!this.currentViewObj.canRefresh())
663 return;
664 isRefresh = true;
665 }
667 var state = {
668 view: aViewId,
669 previousView: this.currentViewId
670 };
671 if (!isRefresh) {
672 gHistory.pushState(state);
673 this.lastHistoryIndex = gHistory.index;
674 }
675 this.loadViewInternal(aViewId, this.currentViewId, state);
676 },
678 // Replaces the existing view with a new one, rewriting the current history
679 // entry to match.
680 replaceView: function gVC_replaceView(aViewId) {
681 if (aViewId == this.currentViewId)
682 return;
684 var state = {
685 view: aViewId,
686 previousView: null
687 };
688 gHistory.replaceState(state);
689 this.loadViewInternal(aViewId, null, state);
690 },
692 loadInitialView: function gVC_loadInitialView(aViewId) {
693 var state = {
694 view: aViewId,
695 previousView: null
696 };
697 gHistory.replaceState(state);
699 this.loadViewInternal(aViewId, null, state);
700 this.initialViewSelected = true;
701 notifyInitialized();
702 },
704 loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) {
705 var view = this.parseViewId(aViewId);
707 if (!view.type || !(view.type in this.viewObjects))
708 throw Components.Exception("Invalid view: " + view.type);
710 var viewObj = this.viewObjects[view.type];
711 if (!viewObj.node)
712 throw Components.Exception("Root node doesn't exist for '" + view.type + "' view");
714 if (this.currentViewObj && aViewId != aPreviousView) {
715 try {
716 let canHide = this.currentViewObj.hide();
717 if (canHide === false)
718 return;
719 this.viewPort.selectedPanel.removeAttribute("loading");
720 } catch (e) {
721 // this shouldn't be fatal
722 Cu.reportError(e);
723 }
724 }
726 gCategories.select(aViewId, aPreviousView);
728 this.currentViewId = aViewId;
729 this.currentViewObj = viewObj;
731 this.viewPort.selectedPanel = this.currentViewObj.node;
732 this.viewPort.selectedPanel.setAttribute("loading", "true");
733 this.currentViewObj.node.focus();
735 if (aViewId == aPreviousView)
736 this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState);
737 else
738 this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
739 },
741 // Moves back in the document history and removes the current history entry
742 popState: function gVC_popState(aCallback) {
743 this.viewChangeCallback = aCallback;
744 gHistory.popState();
745 },
747 notifyViewChanged: function gVC_notifyViewChanged() {
748 this.viewPort.selectedPanel.removeAttribute("loading");
750 if (this.viewChangeCallback) {
751 this.viewChangeCallback();
752 this.viewChangeCallback = null;
753 }
755 var event = document.createEvent("Events");
756 event.initEvent("ViewChanged", true, true);
757 this.currentViewObj.node.dispatchEvent(event);
758 },
760 commands: {
761 cmd_back: {
762 isEnabled: function cmd_back_isEnabled() {
763 return gHistory.canGoBack;
764 },
765 doCommand: function cmd_back_doCommand() {
766 gHistory.back();
767 }
768 },
770 cmd_forward: {
771 isEnabled: function cmd_forward_isEnabled() {
772 return gHistory.canGoForward;
773 },
774 doCommand: function cmd_forward_doCommand() {
775 gHistory.forward();
776 }
777 },
779 cmd_focusSearch: {
780 isEnabled: () => true,
781 doCommand: function cmd_focusSearch_doCommand() {
782 gHeader.focusSearchBox();
783 }
784 },
786 cmd_restartApp: {
787 isEnabled: function cmd_restartApp_isEnabled() true,
788 doCommand: function cmd_restartApp_doCommand() {
789 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
790 createInstance(Ci.nsISupportsPRBool);
791 Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
792 "restart");
793 if (cancelQuit.data)
794 return; // somebody canceled our quit request
796 let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
797 getService(Ci.nsIAppStartup);
798 appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
799 }
800 },
802 cmd_enableCheckCompatibility: {
803 isEnabled: function cmd_enableCheckCompatibility_isEnabled() true,
804 doCommand: function cmd_enableCheckCompatibility_doCommand() {
805 AddonManager.checkCompatibility = true;
806 }
807 },
809 cmd_enableUpdateSecurity: {
810 isEnabled: function cmd_enableUpdateSecurity_isEnabled() true,
811 doCommand: function cmd_enableUpdateSecurity_doCommand() {
812 AddonManager.checkUpdateSecurity = true;
813 }
814 },
816 cmd_pluginCheck: {
817 isEnabled: function cmd_pluginCheck_isEnabled() true,
818 doCommand: function cmd_pluginCheck_doCommand() {
819 openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
820 }
821 },
823 cmd_toggleAutoUpdateDefault: {
824 isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true,
825 doCommand: function cmd_toggleAutoUpdateDefault_doCommand() {
826 if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
827 // One or both of the prefs is false, i.e. the checkbox is not checked.
828 // Now toggle both to true. If the user wants us to auto-update
829 // add-ons, we also need to auto-check for updates.
830 AddonManager.updateEnabled = true;
831 AddonManager.autoUpdateDefault = true;
832 } else {
833 // Both prefs are true, i.e. the checkbox is checked.
834 // Toggle the auto pref to false, but don't touch the enabled check.
835 AddonManager.autoUpdateDefault = false;
836 }
837 }
838 },
840 cmd_resetAddonAutoUpdate: {
841 isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true,
842 doCommand: function cmd_resetAddonAutoUpdate_doCommand() {
843 AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) {
844 for (let addon of aAddonList) {
845 if ("applyBackgroundUpdates" in addon)
846 addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
847 }
848 });
849 }
850 },
852 cmd_goToDiscoverPane: {
853 isEnabled: function cmd_goToDiscoverPane_isEnabled() {
854 return gDiscoverView.enabled;
855 },
856 doCommand: function cmd_goToDiscoverPane_doCommand() {
857 gViewController.loadView("addons://discover/");
858 }
859 },
861 cmd_goToRecentUpdates: {
862 isEnabled: function cmd_goToRecentUpdates_isEnabled() true,
863 doCommand: function cmd_goToRecentUpdates_doCommand() {
864 gViewController.loadView("addons://updates/recent");
865 }
866 },
868 cmd_goToAvailableUpdates: {
869 isEnabled: function cmd_goToAvailableUpdates_isEnabled() true,
870 doCommand: function cmd_goToAvailableUpdates_doCommand() {
871 gViewController.loadView("addons://updates/available");
872 }
873 },
875 cmd_showItemDetails: {
876 isEnabled: function cmd_showItemDetails_isEnabled(aAddon) {
877 return !!aAddon && (gViewController.currentViewObj != gDetailView);
878 },
879 doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) {
880 gViewController.loadView("addons://detail/" +
881 encodeURIComponent(aAddon.id) +
882 (aScrollToPreferences ? "/preferences" : ""));
883 }
884 },
886 cmd_findAllUpdates: {
887 inProgress: false,
888 isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress,
889 doCommand: function cmd_findAllUpdates_doCommand() {
890 this.inProgress = true;
891 gViewController.updateCommand("cmd_findAllUpdates");
892 document.getElementById("updates-noneFound").hidden = true;
893 document.getElementById("updates-progress").hidden = false;
894 document.getElementById("updates-manualUpdatesFound-btn").hidden = true;
896 var pendingChecks = 0;
897 var numUpdated = 0;
898 var numManualUpdates = 0;
899 var restartNeeded = false;
900 var self = this;
902 function updateStatus() {
903 if (pendingChecks > 0)
904 return;
906 self.inProgress = false;
907 gViewController.updateCommand("cmd_findAllUpdates");
908 document.getElementById("updates-progress").hidden = true;
909 gUpdatesView.maybeRefresh();
911 if (numManualUpdates > 0 && numUpdated == 0) {
912 document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
913 return;
914 }
916 if (numUpdated == 0) {
917 document.getElementById("updates-noneFound").hidden = false;
918 return;
919 }
921 if (restartNeeded) {
922 document.getElementById("updates-downloaded").hidden = false;
923 document.getElementById("updates-restart-btn").hidden = false;
924 } else {
925 document.getElementById("updates-installed").hidden = false;
926 }
927 }
929 var updateInstallListener = {
930 onDownloadFailed: function cmd_findAllUpdates_downloadFailed() {
931 pendingChecks--;
932 updateStatus();
933 },
934 onInstallFailed: function cmd_findAllUpdates_installFailed() {
935 pendingChecks--;
936 updateStatus();
937 },
938 onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) {
939 pendingChecks--;
940 numUpdated++;
941 if (isPending(aInstall.existingAddon, "upgrade"))
942 restartNeeded = true;
943 updateStatus();
944 }
945 };
947 var updateCheckListener = {
948 onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) {
949 gEventManager.delegateAddonEvent("onUpdateAvailable",
950 [aAddon, aInstall]);
951 if (AddonManager.shouldAutoUpdate(aAddon)) {
952 aInstall.addListener(updateInstallListener);
953 aInstall.install();
954 } else {
955 pendingChecks--;
956 numManualUpdates++;
957 updateStatus();
958 }
959 },
960 onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) {
961 pendingChecks--;
962 updateStatus();
963 },
964 onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) {
965 gEventManager.delegateAddonEvent("onUpdateFinished",
966 [aAddon, aError]);
967 }
968 };
970 AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) {
971 for (let addon of aAddonList) {
972 if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
973 pendingChecks++;
974 addon.findUpdates(updateCheckListener,
975 AddonManager.UPDATE_WHEN_USER_REQUESTED);
976 }
977 }
979 if (pendingChecks == 0)
980 updateStatus();
981 });
982 }
983 },
985 cmd_findItemUpdates: {
986 isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) {
987 if (!aAddon)
988 return false;
989 return hasPermission(aAddon, "upgrade");
990 },
991 doCommand: function cmd_findItemUpdates_doCommand(aAddon) {
992 var listener = {
993 onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) {
994 gEventManager.delegateAddonEvent("onUpdateAvailable",
995 [aAddon, aInstall]);
996 if (AddonManager.shouldAutoUpdate(aAddon))
997 aInstall.install();
998 },
999 onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) {
1000 gEventManager.delegateAddonEvent("onNoUpdateAvailable",
1001 [aAddon]);
1002 }
1003 };
1004 gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
1005 aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
1006 }
1007 },
1009 cmd_debugItem: {
1010 doCommand: function cmd_debugItem_doCommand(aAddon) {
1011 BrowserToolboxProcess.init({ addonID: aAddon.id });
1012 },
1014 isEnabled: function cmd_debugItem_isEnabled(aAddon) {
1015 let debuggerEnabled = Services.prefs.
1016 getBoolPref(PREF_ADDON_DEBUGGING_ENABLED);
1017 let remoteEnabled = Services.prefs.
1018 getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
1019 return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
1020 }
1021 },
1023 cmd_showItemPreferences: {
1024 isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
1025 if (!aAddon || !aAddon.isActive || !aAddon.optionsURL)
1026 return false;
1027 if (gViewController.currentViewObj == gDetailView &&
1028 aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
1029 return false;
1030 }
1031 if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
1032 return false;
1033 return true;
1034 },
1035 doCommand: function cmd_showItemPreferences_doCommand(aAddon) {
1036 if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
1037 gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
1038 return;
1039 }
1040 var optionsURL = aAddon.optionsURL;
1041 if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
1042 openOptionsInTab(optionsURL)) {
1043 return;
1044 }
1045 var windows = Services.wm.getEnumerator(null);
1046 while (windows.hasMoreElements()) {
1047 var win = windows.getNext();
1048 if (win.closed) {
1049 continue;
1050 }
1051 if (win.document.documentURI == optionsURL) {
1052 win.focus();
1053 return;
1054 }
1055 }
1056 var features = "chrome,titlebar,toolbar,centerscreen";
1057 try {
1058 var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
1059 features += instantApply ? ",dialog=no" : ",modal";
1060 } catch (e) {
1061 features += ",modal";
1062 }
1063 openDialog(optionsURL, "", features);
1064 }
1065 },
1067 cmd_showItemAbout: {
1068 isEnabled: function cmd_showItemAbout_isEnabled(aAddon) {
1069 // XXXunf This may be applicable to install items too. See bug 561260
1070 return !!aAddon;
1071 },
1072 doCommand: function cmd_showItemAbout_doCommand(aAddon) {
1073 var aboutURL = aAddon.aboutURL;
1074 if (aboutURL)
1075 openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon);
1076 else
1077 openDialog("chrome://mozapps/content/extensions/about.xul",
1078 "", "chrome,centerscreen,modal", aAddon);
1079 }
1080 },
1082 cmd_enableItem: {
1083 isEnabled: function cmd_enableItem_isEnabled(aAddon) {
1084 if (!aAddon)
1085 return false;
1086 let addonType = AddonManager.addonTypes[aAddon.type];
1087 return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
1088 hasPermission(aAddon, "enable"));
1089 },
1090 doCommand: function cmd_enableItem_doCommand(aAddon) {
1091 aAddon.userDisabled = false;
1092 },
1093 getTooltip: function cmd_enableItem_getTooltip(aAddon) {
1094 if (!aAddon)
1095 return "";
1096 if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
1097 return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
1098 return gStrings.ext.GetStringFromName("enableAddonTooltip");
1099 }
1100 },
1102 cmd_disableItem: {
1103 isEnabled: function cmd_disableItem_isEnabled(aAddon) {
1104 if (!aAddon)
1105 return false;
1106 let addonType = AddonManager.addonTypes[aAddon.type];
1107 return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
1108 hasPermission(aAddon, "disable"));
1109 },
1110 doCommand: function cmd_disableItem_doCommand(aAddon) {
1111 aAddon.userDisabled = true;
1112 },
1113 getTooltip: function cmd_disableItem_getTooltip(aAddon) {
1114 if (!aAddon)
1115 return "";
1116 if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
1117 return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip");
1118 return gStrings.ext.GetStringFromName("disableAddonTooltip");
1119 }
1120 },
1122 cmd_installItem: {
1123 isEnabled: function cmd_installItem_isEnabled(aAddon) {
1124 if (!aAddon)
1125 return false;
1126 return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE;
1127 },
1128 doCommand: function cmd_installItem_doCommand(aAddon) {
1129 function doInstall() {
1130 gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote();
1131 }
1133 if (gViewController.currentViewObj == gDetailView)
1134 gViewController.popState(doInstall);
1135 else
1136 doInstall();
1137 }
1138 },
1140 cmd_purchaseItem: {
1141 isEnabled: function cmd_purchaseItem_isEnabled(aAddon) {
1142 if (!aAddon)
1143 return false;
1144 return !!aAddon.purchaseURL;
1145 },
1146 doCommand: function cmd_purchaseItem_doCommand(aAddon) {
1147 openURL(aAddon.purchaseURL);
1148 }
1149 },
1151 cmd_uninstallItem: {
1152 isEnabled: function cmd_uninstallItem_isEnabled(aAddon) {
1153 if (!aAddon)
1154 return false;
1155 return hasPermission(aAddon, "uninstall");
1156 },
1157 doCommand: function cmd_uninstallItem_doCommand(aAddon) {
1158 if (gViewController.currentViewObj != gDetailView) {
1159 aAddon.uninstall();
1160 return;
1161 }
1163 gViewController.popState(function cmd_uninstallItem_popState() {
1164 gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
1165 });
1166 },
1167 getTooltip: function cmd_uninstallItem_getTooltip(aAddon) {
1168 if (!aAddon)
1169 return "";
1170 if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL)
1171 return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip");
1172 return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
1173 }
1174 },
1176 cmd_cancelUninstallItem: {
1177 isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) {
1178 if (!aAddon)
1179 return false;
1180 return isPending(aAddon, "uninstall");
1181 },
1182 doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) {
1183 aAddon.cancelUninstall();
1184 }
1185 },
1187 cmd_installFromFile: {
1188 isEnabled: function cmd_installFromFile_isEnabled() true,
1189 doCommand: function cmd_installFromFile_doCommand() {
1190 const nsIFilePicker = Ci.nsIFilePicker;
1191 var fp = Cc["@mozilla.org/filepicker;1"]
1192 .createInstance(nsIFilePicker);
1193 fp.init(window,
1194 gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
1195 nsIFilePicker.modeOpenMultiple);
1196 try {
1197 fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
1198 "*.xpi;*.jar");
1199 fp.appendFilters(nsIFilePicker.filterAll);
1200 } catch (e) { }
1202 if (fp.show() != nsIFilePicker.returnOK)
1203 return;
1205 var files = fp.files;
1206 var installs = [];
1208 function buildNextInstall() {
1209 if (!files.hasMoreElements()) {
1210 if (installs.length > 0) {
1211 // Display the normal install confirmation for the installs
1212 AddonManager.installAddonsFromWebpage("application/x-xpinstall",
1213 window, null, installs);
1214 }
1215 return;
1216 }
1218 var file = files.getNext();
1219 AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) {
1220 installs.push(aInstall);
1221 buildNextInstall();
1222 });
1223 }
1225 buildNextInstall();
1226 }
1227 },
1229 cmd_cancelOperation: {
1230 isEnabled: function cmd_cancelOperation_isEnabled(aAddon) {
1231 if (!aAddon)
1232 return false;
1233 return aAddon.pendingOperations != AddonManager.PENDING_NONE;
1234 },
1235 doCommand: function cmd_cancelOperation_doCommand(aAddon) {
1236 if (isPending(aAddon, "install")) {
1237 aAddon.install.cancel();
1238 } else if (isPending(aAddon, "upgrade")) {
1239 aAddon.pendingUpgrade.install.cancel();
1240 } else if (isPending(aAddon, "uninstall")) {
1241 aAddon.cancelUninstall();
1242 } else if (isPending(aAddon, "enable")) {
1243 aAddon.userDisabled = true;
1244 } else if (isPending(aAddon, "disable")) {
1245 aAddon.userDisabled = false;
1246 }
1247 }
1248 },
1250 cmd_contribute: {
1251 isEnabled: function cmd_contribute_isEnabled(aAddon) {
1252 if (!aAddon)
1253 return false;
1254 return ("contributionURL" in aAddon && aAddon.contributionURL);
1255 },
1256 doCommand: function cmd_contribute_doCommand(aAddon) {
1257 openURL(aAddon.contributionURL);
1258 }
1259 },
1261 cmd_askToActivateItem: {
1262 isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) {
1263 if (!aAddon)
1264 return false;
1265 let addonType = AddonManager.addonTypes[aAddon.type];
1266 return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
1267 hasPermission(aAddon, "ask_to_activate"));
1268 },
1269 doCommand: function cmd_askToActivateItem_doCommand(aAddon) {
1270 aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
1271 }
1272 },
1274 cmd_alwaysActivateItem: {
1275 isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) {
1276 if (!aAddon)
1277 return false;
1278 let addonType = AddonManager.addonTypes[aAddon.type];
1279 return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
1280 hasPermission(aAddon, "enable"));
1281 },
1282 doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) {
1283 aAddon.userDisabled = false;
1284 }
1285 },
1287 cmd_neverActivateItem: {
1288 isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) {
1289 if (!aAddon)
1290 return false;
1291 let addonType = AddonManager.addonTypes[aAddon.type];
1292 return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
1293 hasPermission(aAddon, "disable"));
1294 },
1295 doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
1296 aAddon.userDisabled = true;
1297 }
1298 },
1300 cmd_experimentsLearnMore: {
1301 isEnabled: function cmd_experimentsLearnMore_isEnabled() {
1302 let mainWindow = getMainWindow();
1303 return mainWindow && "switchToTabHavingURI" in mainWindow;
1304 },
1305 doCommand: function cmd_experimentsLearnMore_doCommand() {
1306 let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
1307 openOptionsInTab(url);
1308 },
1309 },
1311 cmd_experimentsOpenTelemetryPreferences: {
1312 isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() {
1313 return !!getMainWindowWithPreferencesPane();
1314 },
1315 doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() {
1316 let mainWindow = getMainWindowWithPreferencesPane();
1317 mainWindow.openAdvancedPreferences("dataChoicesTab");
1318 },
1319 },
1320 },
1322 supportsCommand: function gVC_supportsCommand(aCommand) {
1323 return (aCommand in this.commands);
1324 },
1326 isCommandEnabled: function gVC_isCommandEnabled(aCommand) {
1327 if (!this.supportsCommand(aCommand))
1328 return false;
1329 var addon = this.currentViewObj.getSelectedAddon();
1330 return this.commands[aCommand].isEnabled(addon);
1331 },
1333 updateCommands: function gVC_updateCommands() {
1334 // wait until the view is initialized
1335 if (!this.currentViewObj)
1336 return;
1337 var addon = this.currentViewObj.getSelectedAddon();
1338 for (let commandId in this.commands)
1339 this.updateCommand(commandId, addon);
1340 },
1342 updateCommand: function gVC_updateCommand(aCommandId, aAddon) {
1343 if (typeof aAddon == "undefined")
1344 aAddon = this.currentViewObj.getSelectedAddon();
1345 var cmd = this.commands[aCommandId];
1346 var cmdElt = document.getElementById(aCommandId);
1347 cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon));
1348 if ("getTooltip" in cmd) {
1349 let tooltip = cmd.getTooltip(aAddon);
1350 if (tooltip)
1351 cmdElt.setAttribute("tooltiptext", tooltip);
1352 else
1353 cmdElt.removeAttribute("tooltiptext");
1354 }
1355 },
1357 doCommand: function gVC_doCommand(aCommand, aAddon) {
1358 if (!this.supportsCommand(aCommand))
1359 return;
1360 var cmd = this.commands[aCommand];
1361 if (!aAddon)
1362 aAddon = this.currentViewObj.getSelectedAddon();
1363 if (!cmd.isEnabled(aAddon))
1364 return;
1365 cmd.doCommand(aAddon);
1366 },
1368 onEvent: function gVC_onEvent() {}
1369 };
1371 function hasInlineOptions(aAddon) {
1372 return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
1373 aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO);
1374 }
1376 function openOptionsInTab(optionsURL) {
1377 let mainWindow = getMainWindow();
1378 if ("switchToTabHavingURI" in mainWindow) {
1379 mainWindow.switchToTabHavingURI(optionsURL, true);
1380 return true;
1381 }
1382 return false;
1383 }
1385 function formatDate(aDate) {
1386 return Cc["@mozilla.org/intl/scriptabledateformat;1"]
1387 .getService(Ci.nsIScriptableDateFormat)
1388 .FormatDate("",
1389 Ci.nsIScriptableDateFormat.dateFormatLong,
1390 aDate.getFullYear(),
1391 aDate.getMonth() + 1,
1392 aDate.getDate()
1393 );
1394 }
1397 function hasPermission(aAddon, aPerm) {
1398 var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
1399 return !!(aAddon.permissions & perm);
1400 }
1403 function isPending(aAddon, aAction) {
1404 var action = AddonManager["PENDING_" + aAction.toUpperCase()];
1405 return !!(aAddon.pendingOperations & action);
1406 }
1408 function isInState(aInstall, aState) {
1409 var state = AddonManager["STATE_" + aState.toUpperCase()];
1410 return aInstall.state == state;
1411 }
1413 function shouldShowVersionNumber(aAddon) {
1414 if (!aAddon.version)
1415 return false;
1417 // The version number is hidden for lightweight themes.
1418 if (aAddon.type == "theme")
1419 return !/@personas\.mozilla\.org$/.test(aAddon.id);
1421 return true;
1422 }
1424 function createItem(aObj, aIsInstall, aIsRemote) {
1425 let item = document.createElement("richlistitem");
1427 item.setAttribute("class", "addon addon-view");
1428 item.setAttribute("name", aObj.name);
1429 item.setAttribute("type", aObj.type);
1430 item.setAttribute("remote", !!aIsRemote);
1432 if (aIsInstall) {
1433 item.mInstall = aObj;
1435 if (aObj.state != AddonManager.STATE_INSTALLED) {
1436 item.setAttribute("status", "installing");
1437 return item;
1438 }
1439 aObj = aObj.addon;
1440 }
1442 item.mAddon = aObj;
1444 item.setAttribute("status", "installed");
1446 // set only attributes needed for sorting and XBL binding,
1447 // the binding handles the rest
1448 item.setAttribute("value", aObj.id);
1450 if (aObj.type == "experiment") {
1451 item.endDate = getExperimentEndDate(aObj);
1452 }
1454 return item;
1455 }
1457 function sortElements(aElements, aSortBy, aAscending) {
1458 // aSortBy is an Array of attributes to sort by, in decending
1459 // order of priority.
1461 const DATE_FIELDS = ["updateDate"];
1462 const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"];
1464 // We're going to group add-ons into the following buckets:
1465 //
1466 // enabledInstalled
1467 // * Enabled
1468 // * Incompatible but enabled because compatibility checking is off
1469 // * Waiting to be installed
1470 // * Waiting to be enabled
1471 //
1472 // pendingDisable
1473 // * Waiting to be disabled
1474 //
1475 // pendingUninstall
1476 // * Waiting to be removed
1477 //
1478 // disabledIncompatibleBlocked
1479 // * Disabled
1480 // * Incompatible
1481 // * Blocklisted
1483 const UISTATE_ORDER = ["enabled", "pendingDisable", "pendingUninstall",
1484 "disabled"];
1486 function dateCompare(a, b) {
1487 var aTime = a.getTime();
1488 var bTime = b.getTime();
1489 if (aTime < bTime)
1490 return -1;
1491 if (aTime > bTime)
1492 return 1;
1493 return 0;
1494 }
1496 function numberCompare(a, b) {
1497 return a - b;
1498 }
1500 function stringCompare(a, b) {
1501 return a.localeCompare(b);
1502 }
1504 function uiStateCompare(a, b) {
1505 // If we're in descending order, swap a and b, because
1506 // we don't ever want to have descending uiStates
1507 if (!aAscending)
1508 [a, b] = [b, a];
1510 return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b));
1511 }
1513 function getValue(aObj, aKey) {
1514 if (!aObj)
1515 return null;
1517 if (aObj.hasAttribute(aKey))
1518 return aObj.getAttribute(aKey);
1520 var addon = aObj.mAddon || aObj.mInstall;
1521 if (!addon)
1522 return null;
1524 if (aKey == "uiState") {
1525 if (addon.pendingOperations == AddonManager.PENDING_DISABLE)
1526 return "pendingDisable";
1527 if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL)
1528 return "pendingUninstall";
1529 if (!addon.isActive &&
1530 (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
1531 addon.pendingOperations != AddonManager.PENDING_INSTALL))
1532 return "disabled";
1533 else
1534 return "enabled";
1535 }
1537 return addon[aKey];
1538 }
1540 // aSortFuncs will hold the sorting functions that we'll
1541 // use per element, in the correct order.
1542 var aSortFuncs = [];
1544 for (let i = 0; i < aSortBy.length; i++) {
1545 var sortBy = aSortBy[i];
1547 aSortFuncs[i] = stringCompare;
1549 if (sortBy == "uiState")
1550 aSortFuncs[i] = uiStateCompare;
1551 else if (DATE_FIELDS.indexOf(sortBy) != -1)
1552 aSortFuncs[i] = dateCompare;
1553 else if (NUMERIC_FIELDS.indexOf(sortBy) != -1)
1554 aSortFuncs[i] = numberCompare;
1555 }
1558 aElements.sort(function elementsSort(a, b) {
1559 if (!aAscending)
1560 [a, b] = [b, a];
1562 for (let i = 0; i < aSortFuncs.length; i++) {
1563 var sortBy = aSortBy[i];
1564 var aValue = getValue(a, sortBy);
1565 var bValue = getValue(b, sortBy);
1567 if (!aValue && !bValue)
1568 return 0;
1569 if (!aValue)
1570 return -1;
1571 if (!bValue)
1572 return 1;
1573 if (aValue != bValue) {
1574 var result = aSortFuncs[i](aValue, bValue);
1576 if (result != 0)
1577 return result;
1578 }
1579 }
1581 // If we got here, then all values of a and b
1582 // must have been equal.
1583 return 0;
1585 });
1586 }
1588 function sortList(aList, aSortBy, aAscending) {
1589 var elements = Array.slice(aList.childNodes, 0);
1590 sortElements(elements, [aSortBy], aAscending);
1592 while (aList.listChild)
1593 aList.removeChild(aList.lastChild);
1595 for (let element of elements)
1596 aList.appendChild(element);
1597 }
1599 function getAddonsAndInstalls(aType, aCallback) {
1600 let addons = null, installs = null;
1601 let types = (aType != null) ? [aType] : null;
1603 AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) {
1604 addons = aAddonsList;
1605 if (installs != null)
1606 aCallback(addons, installs);
1607 });
1609 AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) {
1610 // skip over upgrade installs and non-active installs
1611 installs = aInstallsList.filter(function installsFilter(aInstall) {
1612 return !(aInstall.existingAddon ||
1613 aInstall.state == AddonManager.STATE_AVAILABLE);
1614 });
1616 if (addons != null)
1617 aCallback(addons, installs)
1618 });
1619 }
1621 function doPendingUninstalls(aListBox) {
1622 // Uninstalling add-ons can mutate the list so find the add-ons first then
1623 // uninstall them
1624 var items = [];
1625 var listitem = aListBox.firstChild;
1626 while (listitem) {
1627 if (listitem.getAttribute("pending") == "uninstall" &&
1628 !listitem.isPending("uninstall"))
1629 items.push(listitem.mAddon);
1630 listitem = listitem.nextSibling;
1631 }
1633 for (let addon of items)
1634 addon.uninstall();
1635 }
1637 var gCategories = {
1638 node: null,
1639 _search: null,
1641 initialize: function gCategories_initialize() {
1642 this.node = document.getElementById("categories");
1643 this._search = this.get("addons://search/");
1645 var types = AddonManager.addonTypes;
1646 for (var type in types)
1647 this.onTypeAdded(types[type]);
1649 AddonManager.addTypeListener(this);
1651 try {
1652 this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
1653 } catch (e) { }
1655 // If there was no last view or no existing category matched the last view
1656 // then the list will default to selecting the search category and we never
1657 // want to show that as the first view so switch to the default category
1658 if (!this.node.selectedItem || this.node.selectedItem == this._search)
1659 this.node.value = VIEW_DEFAULT;
1661 var self = this;
1662 this.node.addEventListener("select", function node_onSelected() {
1663 self.maybeHideSearch();
1664 gViewController.loadView(self.node.selectedItem.value);
1665 }, false);
1667 this.node.addEventListener("click", function node_onClicked(aEvent) {
1668 var selectedItem = self.node.selectedItem;
1669 if (aEvent.target.localName == "richlistitem" &&
1670 aEvent.target == selectedItem) {
1671 var viewId = selectedItem.value;
1673 if (gViewController.parseViewId(viewId).type == "search") {
1674 viewId += encodeURIComponent(gHeader.searchQuery);
1675 }
1677 gViewController.loadView(viewId);
1678 }
1679 }, false);
1680 },
1682 shutdown: function gCategories_shutdown() {
1683 AddonManager.removeTypeListener(this);
1684 },
1686 _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) {
1687 // If this category already exists then don't re-add it
1688 if (document.getElementById("category-" + aId))
1689 return;
1691 var category = document.createElement("richlistitem");
1692 category.setAttribute("id", "category-" + aId);
1693 category.setAttribute("value", aView);
1694 category.setAttribute("class", "category");
1695 category.setAttribute("name", aName);
1696 category.setAttribute("tooltiptext", aName);
1697 category.setAttribute("priority", aPriority);
1698 category.setAttribute("hidden", aStartHidden);
1700 var node;
1701 for (node of this.node.children) {
1702 var nodePriority = parseInt(node.getAttribute("priority"));
1703 // If the new type's priority is higher than this one then this is the
1704 // insertion point
1705 if (aPriority < nodePriority)
1706 break;
1707 // If the new type's priority is lower than this one then this is isn't
1708 // the insertion point
1709 if (aPriority > nodePriority)
1710 continue;
1711 // If the priorities are equal and the new type's name is earlier
1712 // alphabetically then this is the insertion point
1713 if (String.localeCompare(aName, node.getAttribute("name")) < 0)
1714 break;
1715 }
1717 this.node.insertBefore(category, node);
1718 },
1720 _removeCategory: function gCategories_removeCategory(aId) {
1721 var category = document.getElementById("category-" + aId);
1722 if (!category)
1723 return;
1725 // If this category is currently selected then switch to the default view
1726 if (this.node.selectedItem == category)
1727 gViewController.replaceView(VIEW_DEFAULT);
1729 this.node.removeChild(category);
1730 },
1732 onTypeAdded: function gCategories_onTypeAdded(aType) {
1733 // Ignore types that we don't have a view object for
1734 if (!(aType.viewType in gViewController.viewObjects))
1735 return;
1737 var aViewId = "addons://" + aType.viewType + "/" + aType.id;
1739 var startHidden = false;
1740 if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
1741 var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
1742 try {
1743 startHidden = Services.prefs.getBoolPref(prefName);
1744 }
1745 catch (e) {
1746 // Default to hidden
1747 startHidden = true;
1748 }
1750 var self = this;
1751 gPendingInitializations++;
1752 getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) {
1753 var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
1754 var item = self.get(aViewId);
1756 // Don't load view that is becoming hidden
1757 if (hidden && aViewId == gViewController.currentViewId)
1758 gViewController.loadView(VIEW_DEFAULT);
1760 item.hidden = hidden;
1761 Services.prefs.setBoolPref(prefName, hidden);
1763 if (aAddonsList.length > 0 || aInstallsList.length > 0) {
1764 notifyInitialized();
1765 return;
1766 }
1768 gEventManager.registerInstallListener({
1769 onDownloadStarted: function gCategories_onDownloadStarted(aInstall) {
1770 this._maybeShowCategory(aInstall);
1771 },
1773 onInstallStarted: function gCategories_onInstallStarted(aInstall) {
1774 this._maybeShowCategory(aInstall);
1775 },
1777 onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) {
1778 this._maybeShowCategory(aAddon);
1779 },
1781 onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
1782 this._maybeShowCategory(aAddon);
1783 },
1785 _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) {
1786 if (aType.id == aAddon.type) {
1787 self.get(aViewId).hidden = false;
1788 Services.prefs.setBoolPref(prefName, false);
1789 gEventManager.unregisterInstallListener(this);
1790 }
1791 }
1792 });
1794 notifyInitialized();
1795 });
1796 }
1798 this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
1799 startHidden);
1800 },
1802 onTypeRemoved: function gCategories_onTypeRemoved(aType) {
1803 this._removeCategory(aType.id);
1804 },
1806 get selected() {
1807 return this.node.selectedItem ? this.node.selectedItem.value : null;
1808 },
1810 select: function gCategories_select(aId, aPreviousView) {
1811 var view = gViewController.parseViewId(aId);
1812 if (view.type == "detail" && aPreviousView) {
1813 aId = aPreviousView;
1814 view = gViewController.parseViewId(aPreviousView);
1815 }
1817 Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId);
1819 if (this.node.selectedItem &&
1820 this.node.selectedItem.value == aId) {
1821 this.node.selectedItem.hidden = false;
1822 this.node.selectedItem.disabled = false;
1823 return;
1824 }
1826 if (view.type == "search")
1827 var item = this._search;
1828 else
1829 var item = this.get(aId);
1831 if (item) {
1832 item.hidden = false;
1833 item.disabled = false;
1834 this.node.suppressOnSelect = true;
1835 this.node.selectedItem = item;
1836 this.node.suppressOnSelect = false;
1837 this.node.ensureElementIsVisible(item);
1839 this.maybeHideSearch();
1840 }
1841 },
1843 get: function gCategories_get(aId) {
1844 var items = document.getElementsByAttribute("value", aId);
1845 if (items.length)
1846 return items[0];
1847 return null;
1848 },
1850 setBadge: function gCategories_setBadge(aId, aCount) {
1851 let item = this.get(aId);
1852 if (item)
1853 item.badgeCount = aCount;
1854 },
1856 maybeHideSearch: function gCategories_maybeHideSearch() {
1857 var view = gViewController.parseViewId(this.node.selectedItem.value);
1858 this._search.disabled = view.type != "search";
1859 }
1860 };
1863 var gHeader = {
1864 _search: null,
1865 _dest: "",
1867 initialize: function gHeader_initialize() {
1868 this._search = document.getElementById("header-search");
1870 this._search.addEventListener("command", function search_onCommand(aEvent) {
1871 var query = aEvent.target.value;
1872 if (query.length == 0)
1873 return;
1875 gViewController.loadView("addons://search/" + encodeURIComponent(query));
1876 }, false);
1878 function updateNavButtonVisibility() {
1879 var shouldShow = gHeader.shouldShowNavButtons;
1880 document.getElementById("back-btn").hidden = !shouldShow;
1881 document.getElementById("forward-btn").hidden = !shouldShow;
1882 }
1884 window.addEventListener("focus", function window_onFocus(aEvent) {
1885 if (aEvent.target == window)
1886 updateNavButtonVisibility();
1887 }, false);
1889 updateNavButtonVisibility();
1890 },
1892 focusSearchBox: function gHeader_focusSearchBox() {
1893 this._search.focus();
1894 },
1896 onKeyPress: function gHeader_onKeyPress(aEvent) {
1897 if (String.fromCharCode(aEvent.charCode) == "/") {
1898 this.focusSearchBox();
1899 return;
1900 }
1902 // XXXunf Temporary until bug 371900 is fixed.
1903 let key = document.getElementById("focusSearch").getAttribute("key");
1904 #ifdef XP_MACOSX
1905 let keyModifier = aEvent.metaKey;
1906 #else
1907 let keyModifier = aEvent.ctrlKey;
1908 #endif
1909 if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
1910 this.focusSearchBox();
1911 return;
1912 }
1913 },
1915 get shouldShowNavButtons() {
1916 var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
1917 .getInterface(Ci.nsIWebNavigation)
1918 .QueryInterface(Ci.nsIDocShellTreeItem);
1920 // If there is no outer frame then make the buttons visible
1921 if (docshellItem.rootTreeItem == docshellItem)
1922 return true;
1924 var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
1925 .getInterface(Ci.nsIDOMWindow);
1926 var outerDoc = outerWin.document;
1927 var node = outerDoc.getElementById("back-button");
1928 // If the outer frame has no back-button then make the buttons visible
1929 if (!node)
1930 return true;
1932 // If the back-button or any of its parents are hidden then make the buttons
1933 // visible
1934 while (node != outerDoc) {
1935 var style = outerWin.getComputedStyle(node, "");
1936 if (style.display == "none")
1937 return true;
1938 if (style.visibility != "visible")
1939 return true;
1940 node = node.parentNode;
1941 }
1943 return false;
1944 },
1946 get searchQuery() {
1947 return this._search.value;
1948 },
1950 set searchQuery(aQuery) {
1951 this._search.value = aQuery;
1952 },
1953 };
1956 var gDiscoverView = {
1957 node: null,
1958 enabled: true,
1959 // Set to true after the view is first shown. If initialization completes
1960 // after this then it must also load the discover homepage
1961 loaded: false,
1962 _browser: null,
1963 _loading: null,
1964 _error: null,
1965 homepageURL: null,
1966 _loadListeners: [],
1968 initialize: function gDiscoverView_initialize() {
1969 this.enabled = isDiscoverEnabled();
1970 if (!this.enabled) {
1971 gCategories.get("addons://discover/").hidden = true;
1972 return;
1973 }
1975 this.node = document.getElementById("discover-view");
1976 this._loading = document.getElementById("discover-loading");
1977 this._error = document.getElementById("discover-error");
1978 this._browser = document.getElementById("discover-browser");
1980 let compatMode = "normal";
1981 if (!AddonManager.checkCompatibility)
1982 compatMode = "ignore";
1983 else if (AddonManager.strictCompatibility)
1984 compatMode = "strict";
1986 var url = Services.prefs.getCharPref(PREF_DISCOVERURL);
1987 url = url.replace("%COMPATIBILITY_MODE%", compatMode);
1988 url = Services.urlFormatter.formatURL(url);
1990 var self = this;
1992 function setURL(aURL) {
1993 try {
1994 self.homepageURL = Services.io.newURI(aURL, null, null);
1995 } catch (e) {
1996 self.showError();
1997 notifyInitialized();
1998 return;
1999 }
2001 self._browser.homePage = self.homepageURL.spec;
2002 self._browser.addProgressListener(self);
2004 if (self.loaded)
2005 self._loadURL(self.homepageURL.spec, false, notifyInitialized);
2006 else
2007 notifyInitialized();
2008 }
2010 if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) {
2011 setURL(url);
2012 return;
2013 }
2015 gPendingInitializations++;
2016 AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) {
2017 var list = {};
2018 for (let addon of aAddons) {
2019 var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%",
2020 addon.id);
2021 try {
2022 if (!Services.prefs.getBoolPref(prefName))
2023 continue;
2024 } catch (e) { }
2025 list[addon.id] = {
2026 name: addon.name,
2027 version: addon.version,
2028 type: addon.type,
2029 userDisabled: addon.userDisabled,
2030 isCompatible: addon.isCompatible,
2031 isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
2032 }
2033 }
2035 setURL(url + "#" + JSON.stringify(list));
2036 });
2037 },
2039 destroy: function gDiscoverView_destroy() {
2040 try {
2041 this._browser.removeProgressListener(this);
2042 }
2043 catch (e) {
2044 // Ignore the case when the listener wasn't already registered
2045 }
2046 },
2048 show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) {
2049 gViewController.updateCommands();
2051 // If we're being told to load a specific URL then just do that
2052 if (aState && "url" in aState) {
2053 this.loaded = true;
2054 this._loadURL(aState.url);
2055 }
2057 // If the view has loaded before and still at the homepage (if refreshing),
2058 // and the error page is not visible then there is nothing else to do
2059 if (this.loaded && this.node.selectedPanel != this._error &&
2060 (!aIsRefresh || (this._browser.currentURI &&
2061 this._browser.currentURI.spec == this._browser.homePage))) {
2062 gViewController.notifyViewChanged();
2063 return;
2064 }
2066 this.loaded = true;
2068 // No homepage means initialization isn't complete, the browser will get
2069 // loaded once initialization is complete
2070 if (!this.homepageURL) {
2071 this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
2072 return;
2073 }
2075 this._loadURL(this.homepageURL.spec, aIsRefresh,
2076 gViewController.notifyViewChanged.bind(gViewController));
2077 },
2079 canRefresh: function gDiscoverView_canRefresh() {
2080 if (this._browser.currentURI &&
2081 this._browser.currentURI.spec == this._browser.homePage)
2082 return false;
2083 return true;
2084 },
2086 refresh: function gDiscoverView_refresh(aParam, aRequest, aState) {
2087 this.show(aParam, aRequest, aState, true);
2088 },
2090 hide: function gDiscoverView_hide() { },
2092 showError: function gDiscoverView_showError() {
2093 this.node.selectedPanel = this._error;
2094 },
2096 _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) {
2097 if (this._browser.currentURI.spec == aURL) {
2098 if (aCallback)
2099 aCallback();
2100 return;
2101 }
2103 if (aCallback)
2104 this._loadListeners.push(aCallback);
2106 var flags = 0;
2107 if (!aKeepHistory)
2108 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
2110 this._browser.loadURIWithFlags(aURL, flags);
2111 },
2113 onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
2114 // Ignore the about:blank load
2115 if (aLocation.spec == "about:blank")
2116 return;
2118 // When using the real session history the inner-frame will update the
2119 // session history automatically, if using the fake history though it must
2120 // be manually updated
2121 if (gHistory == FakeHistory) {
2122 var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);
2124 var state = {
2125 view: "addons://discover/",
2126 url: aLocation.spec
2127 };
2129 var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
2130 if (docshell.loadType & replaceHistory)
2131 gHistory.replaceState(state);
2132 else
2133 gHistory.pushState(state);
2134 gViewController.lastHistoryIndex = gHistory.index;
2135 }
2137 gViewController.updateCommands();
2139 // If the hostname is the same as the new location's host and either the
2140 // default scheme is insecure or the new location is secure then continue
2141 // with the load
2142 if (aLocation.host == this.homepageURL.host &&
2143 (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
2144 return;
2146 // Canceling the request will send an error to onStateChange which will show
2147 // the error page
2148 aRequest.cancel(Components.results.NS_BINDING_ABORTED);
2149 },
2151 onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) {
2152 // Don't care about security if the page is not https
2153 if (!this.homepageURL.schemeIs("https"))
2154 return;
2156 // If the request was secure then it is ok
2157 if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE)
2158 return;
2160 // Canceling the request will send an error to onStateChange which will show
2161 // the error page
2162 aRequest.cancel(Components.results.NS_BINDING_ABORTED);
2163 },
2165 onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
2166 let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
2167 Ci.nsIWebProgressListener.STATE_IS_REQUEST |
2168 Ci.nsIWebProgressListener.STATE_TRANSFERRING;
2169 // Once transferring begins show the content
2170 if (aStateFlags & transferStart)
2171 this.node.selectedPanel = this._browser;
2173 // Only care about the network events
2174 if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
2175 return;
2177 // If this is the start of network activity then show the loading page
2178 if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
2179 this.node.selectedPanel = this._loading;
2181 // Ignore anything except stop events
2182 if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
2183 return;
2185 // Consider the successful load of about:blank as still loading
2186 if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank")
2187 return;
2189 // If there was an error loading the page or the new hostname is not the
2190 // same as the default hostname or the default scheme is secure and the new
2191 // scheme is insecure then show the error page
2192 const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
2193 if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) ||
2194 (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
2195 this.showError();
2196 } else {
2197 // Got a successful load, make sure the browser is visible
2198 this.node.selectedPanel = this._browser;
2199 gViewController.updateCommands();
2200 }
2202 var listeners = this._loadListeners;
2203 this._loadListeners = [];
2205 for (let listener of listeners)
2206 listener();
2207 },
2209 onProgressChange: function gDiscoverView_onProgressChange() { },
2210 onStatusChange: function gDiscoverView_onStatusChange() { },
2212 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
2213 Ci.nsISupportsWeakReference]),
2215 getSelectedAddon: function gDiscoverView_getSelectedAddon() null
2216 };
2219 var gCachedAddons = {};
2221 var gSearchView = {
2222 node: null,
2223 _filter: null,
2224 _sorters: null,
2225 _loading: null,
2226 _listBox: null,
2227 _emptyNotice: null,
2228 _allResultsLink: null,
2229 _lastQuery: null,
2230 _lastRemoteTotal: 0,
2231 _pendingSearches: 0,
2233 initialize: function gSearchView_initialize() {
2234 this.node = document.getElementById("search-view");
2235 this._filter = document.getElementById("search-filter-radiogroup");
2236 this._sorters = document.getElementById("search-sorters");
2237 this._sorters.handler = this;
2238 this._loading = document.getElementById("search-loading");
2239 this._listBox = document.getElementById("search-list");
2240 this._emptyNotice = document.getElementById("search-list-empty");
2241 this._allResultsLink = document.getElementById("search-allresults-link");
2243 if (!AddonManager.isInstallEnabled("application/x-xpinstall"))
2244 this._filter.hidden = true;
2246 var self = this;
2247 this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
2248 if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
2249 var item = self._listBox.selectedItem;
2250 if (item)
2251 item.showInDetailView();
2252 }
2253 }, false);
2255 this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false);
2256 },
2258 shutdown: function gSearchView_shutdown() {
2259 if (AddonRepository.isSearching)
2260 AddonRepository.cancelSearch();
2261 },
2263 get isSearching() {
2264 return this._pendingSearches > 0;
2265 },
2267 show: function gSearchView_show(aQuery, aRequest) {
2268 gEventManager.registerInstallListener(this);
2270 this.showEmptyNotice(false);
2271 this.showAllResultsLink(0);
2272 this.showLoading(true);
2273 this._sorters.showprice = false;
2275 gHeader.searchQuery = aQuery;
2276 aQuery = aQuery.trim().toLocaleLowerCase();
2277 if (this._lastQuery == aQuery) {
2278 this.updateView();
2279 gViewController.notifyViewChanged();
2280 return;
2281 }
2282 this._lastQuery = aQuery;
2284 if (AddonRepository.isSearching)
2285 AddonRepository.cancelSearch();
2287 while (this._listBox.firstChild.localName == "richlistitem")
2288 this._listBox.removeChild(this._listBox.firstChild);
2290 var self = this;
2291 gCachedAddons = {};
2292 this._pendingSearches = 2;
2293 this._sorters.setSort("relevancescore", false);
2295 var elements = [];
2297 function createSearchResults(aObjsList, aIsInstall, aIsRemote) {
2298 for (let index in aObjsList) {
2299 let obj = aObjsList[index];
2300 let score = aObjsList.length - index;
2301 if (!aIsRemote && aQuery.length > 0) {
2302 score = self.getMatchScore(obj, aQuery);
2303 if (score == 0)
2304 continue;
2305 }
2307 let item = createItem(obj, aIsInstall, aIsRemote);
2308 item.setAttribute("relevancescore", score);
2309 if (aIsRemote) {
2310 gCachedAddons[obj.id] = obj;
2311 if (obj.purchaseURL)
2312 self._sorters.showprice = true;
2313 }
2315 elements.push(item);
2316 }
2317 }
2319 function finishSearch(createdCount) {
2320 if (elements.length > 0) {
2321 sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
2322 for (let element of elements)
2323 self._listBox.insertBefore(element, self._listBox.lastChild);
2324 self.updateListAttributes();
2325 }
2327 self._pendingSearches--;
2328 self.updateView();
2330 if (!self.isSearching)
2331 gViewController.notifyViewChanged();
2332 }
2334 getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) {
2335 if (gViewController && aRequest != gViewController.currentViewRequest)
2336 return;
2338 createSearchResults(aAddons, false, false);
2339 createSearchResults(aInstalls, true, false);
2340 finishSearch();
2341 });
2343 var maxRemoteResults = 0;
2344 try {
2345 maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS);
2346 } catch(e) {}
2348 if (maxRemoteResults <= 0) {
2349 finishSearch(0);
2350 return;
2351 }
2353 AddonRepository.searchAddons(aQuery, maxRemoteResults, {
2354 searchFailed: function show_SearchFailed() {
2355 if (gViewController && aRequest != gViewController.currentViewRequest)
2356 return;
2358 self._lastRemoteTotal = 0;
2360 // XXXunf Better handling of AMO search failure. See bug 579502
2361 finishSearch(0); // Silently fail
2362 },
2364 searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) {
2365 if (gViewController && aRequest != gViewController.currentViewRequest)
2366 return;
2368 if (aTotalResults > maxRemoteResults)
2369 self._lastRemoteTotal = aTotalResults;
2370 else
2371 self._lastRemoteTotal = 0;
2373 var createdCount = createSearchResults(aAddonsList, false, true);
2374 finishSearch(createdCount);
2375 }
2376 });
2377 },
2379 showLoading: function gSearchView_showLoading(aLoading) {
2380 this._loading.hidden = !aLoading;
2381 this._listBox.hidden = aLoading;
2382 },
2384 updateView: function gSearchView_updateView() {
2385 var showLocal = this._filter.value == "local";
2387 if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall"))
2388 showLocal = true;
2390 this._listBox.setAttribute("local", showLocal);
2391 this._listBox.setAttribute("remote", !showLocal);
2393 this.showLoading(this.isSearching && !showLocal);
2394 if (!this.isSearching) {
2395 var isEmpty = true;
2396 var results = this._listBox.getElementsByTagName("richlistitem");
2397 for (let result of results) {
2398 var isRemote = (result.getAttribute("remote") == "true");
2399 if ((isRemote && !showLocal) || (!isRemote && showLocal)) {
2400 isEmpty = false;
2401 break;
2402 }
2403 }
2405 this.showEmptyNotice(isEmpty);
2406 this.showAllResultsLink(this._lastRemoteTotal);
2407 }
2409 gViewController.updateCommands();
2410 },
2412 hide: function gSearchView_hide() {
2413 gEventManager.unregisterInstallListener(this);
2414 doPendingUninstalls(this._listBox);
2415 },
2417 getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) {
2418 var score = 0;
2419 score += this.calculateMatchScore(aObj.name, aQuery,
2420 SEARCH_SCORE_MULTIPLIER_NAME);
2421 score += this.calculateMatchScore(aObj.description, aQuery,
2422 SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
2423 return score;
2424 },
2426 calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) {
2427 var score = 0;
2428 if (!aStr || aQuery.length == 0)
2429 return score;
2431 aStr = aStr.trim().toLocaleLowerCase();
2432 var haystack = aStr.split(/\s+/);
2433 var needles = aQuery.split(/\s+/);
2435 for (let needle of needles) {
2436 for (let hay of haystack) {
2437 if (hay == needle) {
2438 // matching whole words is best
2439 score += SEARCH_SCORE_MATCH_WHOLEWORD;
2440 } else {
2441 let i = hay.indexOf(needle);
2442 if (i == 0) // matching on word boundries is also good
2443 score += SEARCH_SCORE_MATCH_WORDBOUNDRY;
2444 else if (i > 0) // substring matches not so good
2445 score += SEARCH_SCORE_MATCH_SUBSTRING;
2446 }
2447 }
2448 }
2450 // give progressively higher score for longer queries, since longer queries
2451 // are more likely to be unique and therefore more relevant.
2452 if (needles.length > 1 && aStr.indexOf(aQuery) != -1)
2453 score += needles.length;
2455 return score * aMultiplier;
2456 },
2458 showEmptyNotice: function gSearchView_showEmptyNotice(aShow) {
2459 this._emptyNotice.hidden = !aShow;
2460 this._listBox.hidden = aShow;
2461 },
2463 showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) {
2464 if (aTotalResults == 0) {
2465 this._allResultsLink.hidden = true;
2466 return;
2467 }
2469 var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults");
2470 linkStr = PluralForm.get(aTotalResults, linkStr);
2471 linkStr = linkStr.replace("#1", aTotalResults);
2472 this._allResultsLink.setAttribute("value", linkStr);
2474 this._allResultsLink.setAttribute("href",
2475 AddonRepository.getSearchURL(this._lastQuery));
2476 this._allResultsLink.hidden = false;
2477 },
2479 updateListAttributes: function gSearchView_updateListAttributes() {
2480 var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
2481 if (item)
2482 item.removeAttribute("first");
2483 item = this._listBox.querySelector("richlistitem[remote='true'][last]");
2484 if (item)
2485 item.removeAttribute("last");
2486 var items = this._listBox.querySelectorAll("richlistitem[remote='true']");
2487 if (items.length > 0) {
2488 items[0].setAttribute("first", true);
2489 items[items.length - 1].setAttribute("last", true);
2490 }
2492 item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]");
2493 if (item)
2494 item.removeAttribute("first");
2495 item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]");
2496 if (item)
2497 item.removeAttribute("last");
2498 items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])");
2499 if (items.length > 0) {
2500 items[0].setAttribute("first", true);
2501 items[items.length - 1].setAttribute("last", true);
2502 }
2504 },
2506 onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) {
2507 var footer = this._listBox.lastChild;
2508 this._listBox.removeChild(footer);
2510 sortList(this._listBox, aSortBy, aAscending);
2511 this.updateListAttributes();
2513 this._listBox.appendChild(footer);
2514 },
2516 onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) {
2517 this.removeInstall(aInstall);
2518 },
2520 onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) {
2521 this.removeInstall(aInstall);
2522 },
2524 removeInstall: function gSearchView_removeInstall(aInstall) {
2525 for (let item of this._listBox.childNodes) {
2526 if (item.mInstall == aInstall) {
2527 this._listBox.removeChild(item);
2528 return;
2529 }
2530 }
2531 },
2533 getSelectedAddon: function gSearchView_getSelectedAddon() {
2534 var item = this._listBox.selectedItem;
2535 if (item)
2536 return item.mAddon;
2537 return null;
2538 },
2540 getListItemForID: function gSearchView_getListItemForID(aId) {
2541 var listitem = this._listBox.firstChild;
2542 while (listitem) {
2543 if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
2544 return listitem;
2545 listitem = listitem.nextSibling;
2546 }
2547 return null;
2548 }
2549 };
2552 var gListView = {
2553 node: null,
2554 _listBox: null,
2555 _emptyNotice: null,
2556 _type: null,
2558 initialize: function gListView_initialize() {
2559 this.node = document.getElementById("list-view");
2560 this._listBox = document.getElementById("addon-list");
2561 this._emptyNotice = document.getElementById("addon-list-empty");
2563 var self = this;
2564 this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
2565 if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
2566 var item = self._listBox.selectedItem;
2567 if (item)
2568 item.showInDetailView();
2569 }
2570 }, false);
2571 },
2573 show: function gListView_show(aType, aRequest) {
2574 if (!(aType in AddonManager.addonTypes))
2575 throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG);
2577 this._type = aType;
2578 this.node.setAttribute("type", aType);
2579 this.showEmptyNotice(false);
2581 while (this._listBox.itemCount > 0)
2582 this._listBox.removeItemAt(0);
2584 var self = this;
2585 getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) {
2586 if (gViewController && aRequest != gViewController.currentViewRequest)
2587 return;
2589 var elements = [];
2591 for (let addonItem of aAddonsList)
2592 elements.push(createItem(addonItem));
2594 for (let installItem of aInstallsList)
2595 elements.push(createItem(installItem, true));
2597 self.showEmptyNotice(elements.length == 0);
2598 if (elements.length > 0) {
2599 sortElements(elements, ["uiState", "name"], true);
2600 for (let element of elements)
2601 self._listBox.appendChild(element);
2602 }
2604 gEventManager.registerInstallListener(self);
2605 gViewController.updateCommands();
2606 gViewController.notifyViewChanged();
2607 });
2608 },
2610 hide: function gListView_hide() {
2611 gEventManager.unregisterInstallListener(this);
2612 doPendingUninstalls(this._listBox);
2613 },
2615 showEmptyNotice: function gListView_showEmptyNotice(aShow) {
2616 this._emptyNotice.hidden = !aShow;
2617 },
2619 onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) {
2620 sortList(this._listBox, aSortBy, aAscending);
2621 },
2623 onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
2624 // The existing list item will take care of upgrade installs
2625 if (aExistingAddon)
2626 return;
2628 this.addItem(aAddon);
2629 },
2631 onDownloadStarted: function gListView_onDownloadStarted(aInstall) {
2632 this.addItem(aInstall, true);
2633 },
2635 onInstallStarted: function gListView_onInstallStarted(aInstall) {
2636 this.addItem(aInstall, true);
2637 },
2639 onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) {
2640 this.removeItem(aInstall, true);
2641 },
2643 onInstallCancelled: function gListView_onInstallCancelled(aInstall) {
2644 this.removeItem(aInstall, true);
2645 },
2647 onInstallEnded: function gListView_onInstallEnded(aInstall) {
2648 // Remove any install entries for upgrades, their status will appear against
2649 // the existing item
2650 if (aInstall.existingAddon)
2651 this.removeItem(aInstall, true);
2653 if (aInstall.addon.type == "experiment") {
2654 let item = this.getListItemForID(aInstall.addon.id);
2655 if (item) {
2656 item.endDate = getExperimentEndDate(aInstall.addon);
2657 }
2658 }
2659 },
2661 addItem: function gListView_addItem(aObj, aIsInstall) {
2662 if (aObj.type != this._type)
2663 return;
2665 if (aIsInstall && aObj.existingAddon)
2666 return;
2668 let prop = aIsInstall ? "mInstall" : "mAddon";
2669 for (let item of this._listBox.childNodes) {
2670 if (item[prop] == aObj)
2671 return;
2672 }
2674 let item = createItem(aObj, aIsInstall);
2675 this._listBox.insertBefore(item, this._listBox.firstChild);
2676 this.showEmptyNotice(false);
2677 },
2679 removeItem: function gListView_removeItem(aObj, aIsInstall) {
2680 let prop = aIsInstall ? "mInstall" : "mAddon";
2682 for (let item of this._listBox.childNodes) {
2683 if (item[prop] == aObj) {
2684 this._listBox.removeChild(item);
2685 this.showEmptyNotice(this._listBox.itemCount == 0);
2686 return;
2687 }
2688 }
2689 },
2691 getSelectedAddon: function gListView_getSelectedAddon() {
2692 var item = this._listBox.selectedItem;
2693 if (item)
2694 return item.mAddon;
2695 return null;
2696 },
2698 getListItemForID: function gListView_getListItemForID(aId) {
2699 var listitem = this._listBox.firstChild;
2700 while (listitem) {
2701 if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
2702 return listitem;
2703 listitem = listitem.nextSibling;
2704 }
2705 return null;
2706 }
2707 };
2710 var gDetailView = {
2711 node: null,
2712 _addon: null,
2713 _loadingTimer: null,
2714 _autoUpdate: null,
2716 initialize: function gDetailView_initialize() {
2717 this.node = document.getElementById("detail-view");
2719 this._autoUpdate = document.getElementById("detail-autoUpdate");
2721 var self = this;
2722 this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() {
2723 self._addon.applyBackgroundUpdates = self._autoUpdate.value;
2724 }, true);
2725 },
2727 shutdown: function gDetailView_shutdown() {
2728 AddonManager.removeManagerListener(this);
2729 },
2731 onUpdateModeChanged: function gDetailView_onUpdateModeChanged() {
2732 this.onPropertyChanged(["applyBackgroundUpdates"]);
2733 },
2735 _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) {
2736 AddonManager.addManagerListener(this);
2737 this.clearLoading();
2739 this._addon = aAddon;
2740 gEventManager.registerAddonListener(this, aAddon.id);
2741 gEventManager.registerInstallListener(this);
2743 this.node.setAttribute("type", aAddon.type);
2745 // If the search category isn't selected then make sure to select the
2746 // correct category
2747 if (gCategories.selected != "addons://search/")
2748 gCategories.select("addons://list/" + aAddon.type);
2750 document.getElementById("detail-name").textContent = aAddon.name;
2751 var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL;
2752 document.getElementById("detail-icon").src = icon ? icon : "";
2753 document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL);
2755 var version = document.getElementById("detail-version");
2756 if (shouldShowVersionNumber(aAddon)) {
2757 version.hidden = false;
2758 version.value = aAddon.version;
2759 } else {
2760 version.hidden = true;
2761 }
2763 var screenshot = document.getElementById("detail-screenshot");
2764 if (aAddon.screenshots && aAddon.screenshots.length > 0) {
2765 if (aAddon.screenshots[0].thumbnailURL) {
2766 screenshot.src = aAddon.screenshots[0].thumbnailURL;
2767 screenshot.width = aAddon.screenshots[0].thumbnailWidth;
2768 screenshot.height = aAddon.screenshots[0].thumbnailHeight;
2769 } else {
2770 screenshot.src = aAddon.screenshots[0].url;
2771 screenshot.width = aAddon.screenshots[0].width;
2772 screenshot.height = aAddon.screenshots[0].height;
2773 }
2774 screenshot.setAttribute("loading", "true");
2775 screenshot.hidden = false;
2776 } else {
2777 screenshot.hidden = true;
2778 }
2780 var desc = document.getElementById("detail-desc");
2781 desc.textContent = aAddon.description;
2783 var fullDesc = document.getElementById("detail-fulldesc");
2784 if (aAddon.fullDescription) {
2785 fullDesc.textContent = aAddon.fullDescription;
2786 fullDesc.hidden = false;
2787 } else {
2788 fullDesc.hidden = true;
2789 }
2791 var contributions = document.getElementById("detail-contributions");
2792 if ("contributionURL" in aAddon && aAddon.contributionURL) {
2793 contributions.hidden = false;
2794 var amount = document.getElementById("detail-contrib-suggested");
2795 if (aAddon.contributionAmount) {
2796 amount.value = gStrings.ext.formatStringFromName("contributionAmount2",
2797 [aAddon.contributionAmount],
2798 1);
2799 amount.hidden = false;
2800 } else {
2801 amount.hidden = true;
2802 }
2803 } else {
2804 contributions.hidden = true;
2805 }
2807 if ("purchaseURL" in aAddon && aAddon.purchaseURL) {
2808 var purchase = document.getElementById("detail-purchase-btn");
2809 purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label",
2810 [aAddon.purchaseDisplayAmount],
2811 1);
2812 purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey");
2813 }
2815 var updateDateRow = document.getElementById("detail-dateUpdated");
2816 if (aAddon.updateDate) {
2817 var date = formatDate(aAddon.updateDate);
2818 updateDateRow.value = date;
2819 } else {
2820 updateDateRow.value = null;
2821 }
2823 // TODO if the add-on was downloaded from releases.mozilla.org link to the
2824 // AMO profile (bug 590344)
2825 if (false) {
2826 document.getElementById("detail-repository-row").hidden = false;
2827 document.getElementById("detail-homepage-row").hidden = true;
2828 var repository = document.getElementById("detail-repository");
2829 repository.value = aAddon.homepageURL;
2830 repository.href = aAddon.homepageURL;
2831 } else if (aAddon.homepageURL) {
2832 document.getElementById("detail-repository-row").hidden = true;
2833 document.getElementById("detail-homepage-row").hidden = false;
2834 var homepage = document.getElementById("detail-homepage");
2835 homepage.value = aAddon.homepageURL;
2836 homepage.href = aAddon.homepageURL;
2837 } else {
2838 document.getElementById("detail-repository-row").hidden = true;
2839 document.getElementById("detail-homepage-row").hidden = true;
2840 }
2842 var rating = document.getElementById("detail-rating");
2843 if (aAddon.averageRating) {
2844 rating.averageRating = aAddon.averageRating;
2845 rating.hidden = false;
2846 } else {
2847 rating.hidden = true;
2848 }
2850 var reviews = document.getElementById("detail-reviews");
2851 if (aAddon.reviewURL) {
2852 var text = gStrings.ext.GetStringFromName("numReviews");
2853 text = PluralForm.get(aAddon.reviewCount, text)
2854 text = text.replace("#1", aAddon.reviewCount);
2855 reviews.value = text;
2856 reviews.hidden = false;
2857 reviews.href = aAddon.reviewURL;
2858 } else {
2859 reviews.hidden = true;
2860 }
2862 document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL;
2864 var sizeRow = document.getElementById("detail-size");
2865 if (aAddon.size && aIsRemote) {
2866 let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size));
2867 let formatted = gStrings.dl.GetStringFromName("doneSize");
2868 formatted = formatted.replace("#1", size).replace("#2", unit);
2869 sizeRow.value = formatted;
2870 } else {
2871 sizeRow.value = null;
2872 }
2874 var downloadsRow = document.getElementById("detail-downloads");
2875 if (aAddon.totalDownloads && aIsRemote) {
2876 var downloads = aAddon.totalDownloads;
2877 downloadsRow.value = downloads;
2878 } else {
2879 downloadsRow.value = null;
2880 }
2882 var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID;
2883 document.getElementById("detail-updates-row").hidden = !canUpdate;
2885 if ("applyBackgroundUpdates" in aAddon) {
2886 this._autoUpdate.hidden = false;
2887 this._autoUpdate.value = aAddon.applyBackgroundUpdates;
2888 let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
2889 document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
2890 } else {
2891 this._autoUpdate.hidden = true;
2892 document.getElementById("detail-findUpdates-btn").hidden = false;
2893 }
2895 document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
2896 !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
2898 var gridRows = document.querySelectorAll("#detail-grid rows row");
2899 let first = true;
2900 for (let gridRow of gridRows) {
2901 if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") {
2902 gridRow.setAttribute("first-row", true);
2903 first = false;
2904 } else {
2905 gridRow.removeAttribute("first-row");
2906 }
2907 }
2909 if (this._addon.type == "experiment") {
2910 let prefix = "details.experiment.";
2911 let active = this._addon.isActive;
2913 let stateKey = prefix + "state." + (active ? "active" : "complete");
2914 let node = document.getElementById("detail-experiment-state");
2915 node.value = gStrings.ext.GetStringFromName(stateKey);
2917 let now = Date.now();
2918 let end = getExperimentEndDate(this._addon);
2919 let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
2921 let timeKey = prefix + "time.";
2922 let timeMessage;
2923 if (days < 1) {
2924 timeKey += (active ? "endsToday" : "endedToday");
2925 timeMessage = gStrings.ext.GetStringFromName(timeKey);
2926 } else {
2927 timeKey += (active ? "daysRemaining" : "daysPassed");
2928 days = Math.round(days);
2929 let timeString = gStrings.ext.GetStringFromName(timeKey);
2930 timeMessage = PluralForm.get(days, timeString)
2931 .replace("#1", days);
2932 }
2934 document.getElementById("detail-experiment-time").value = timeMessage;
2935 }
2937 this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
2938 this.updateState();
2939 gViewController.notifyViewChanged();
2940 }).bind(this));
2941 },
2943 show: function gDetailView_show(aAddonId, aRequest) {
2944 let index = aAddonId.indexOf("/preferences");
2945 let scrollToPreferences = false;
2946 if (index >= 0) {
2947 aAddonId = aAddonId.substring(0, index);
2948 scrollToPreferences = true;
2949 }
2951 var self = this;
2952 this._loadingTimer = setTimeout(function loadTimeOutTimer() {
2953 self.node.setAttribute("loading-extended", true);
2954 }, LOADING_MSG_DELAY);
2956 var view = gViewController.currentViewId;
2958 AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) {
2959 if (gViewController && aRequest != gViewController.currentViewRequest)
2960 return;
2962 if (aAddon) {
2963 self._updateView(aAddon, false, scrollToPreferences);
2964 return;
2965 }
2967 // Look for an add-on pending install
2968 AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) {
2969 for (let install of aInstalls) {
2970 if (install.state == AddonManager.STATE_INSTALLED &&
2971 install.addon.id == aAddonId) {
2972 self._updateView(install.addon, false);
2973 return;
2974 }
2975 }
2977 if (aAddonId in gCachedAddons) {
2978 self._updateView(gCachedAddons[aAddonId], true);
2979 return;
2980 }
2982 // This might happen due to session restore restoring us back to an
2983 // add-on that doesn't exist but otherwise shouldn't normally happen.
2984 // Either way just revert to the default view.
2985 gViewController.replaceView(VIEW_DEFAULT);
2986 });
2987 });
2988 },
2990 hide: function gDetailView_hide() {
2991 AddonManager.removeManagerListener(this);
2992 this.clearLoading();
2993 if (this._addon) {
2994 if (hasInlineOptions(this._addon)) {
2995 Services.obs.notifyObservers(document,
2996 AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
2997 this._addon.id);
2998 }
3000 gEventManager.unregisterAddonListener(this, this._addon.id);
3001 gEventManager.unregisterInstallListener(this);
3002 this._addon = null;
3004 // Flush the preferences to disk so they survive any crash
3005 if (this.node.getElementsByTagName("setting").length)
3006 Services.prefs.savePrefFile(null);
3007 }
3008 },
3010 updateState: function gDetailView_updateState() {
3011 gViewController.updateCommands();
3013 var pending = this._addon.pendingOperations;
3014 if (pending != AddonManager.PENDING_NONE) {
3015 this.node.removeAttribute("notification");
3017 var pending = null;
3018 const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall",
3019 "upgrade"];
3020 for (let op of PENDING_OPERATIONS) {
3021 if (isPending(this._addon, op))
3022 pending = op;
3023 }
3025 this.node.setAttribute("pending", pending);
3026 document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName(
3027 "details.notification." + pending,
3028 [this._addon.name, gStrings.brandShortName], 2
3029 );
3030 } else {
3031 this.node.removeAttribute("pending");
3033 if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
3034 this.node.setAttribute("notification", "error");
3035 document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
3036 "details.notification.blocked",
3037 [this._addon.name], 1
3038 );
3039 var errorLink = document.getElementById("detail-error-link");
3040 errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
3041 errorLink.href = this._addon.blocklistURL;
3042 errorLink.hidden = false;
3043 } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility ||
3044 (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
3045 this.node.setAttribute("notification", "warning");
3046 document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
3047 "details.notification.incompatible",
3048 [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
3049 );
3050 document.getElementById("detail-warning-link").hidden = true;
3051 } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
3052 this.node.setAttribute("notification", "warning");
3053 document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
3054 "details.notification.softblocked",
3055 [this._addon.name], 1
3056 );
3057 var warningLink = document.getElementById("detail-warning-link");
3058 warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
3059 warningLink.href = this._addon.blocklistURL;
3060 warningLink.hidden = false;
3061 } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
3062 this.node.setAttribute("notification", "warning");
3063 document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
3064 "details.notification.outdated",
3065 [this._addon.name], 1
3066 );
3067 var warningLink = document.getElementById("detail-warning-link");
3068 warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link");
3069 warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url");
3070 warningLink.hidden = false;
3071 } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
3072 this.node.setAttribute("notification", "error");
3073 document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
3074 "details.notification.vulnerableUpdatable",
3075 [this._addon.name], 1
3076 );
3077 var errorLink = document.getElementById("detail-error-link");
3078 errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link");
3079 errorLink.href = this._addon.blocklistURL;
3080 errorLink.hidden = false;
3081 } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
3082 this.node.setAttribute("notification", "error");
3083 document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
3084 "details.notification.vulnerableNoUpdate",
3085 [this._addon.name], 1
3086 );
3087 var errorLink = document.getElementById("detail-error-link");
3088 errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
3089 errorLink.href = this._addon.blocklistURL;
3090 errorLink.hidden = false;
3091 } else {
3092 this.node.removeAttribute("notification");
3093 }
3094 }
3096 let menulist = document.getElementById("detail-state-menulist");
3097 let addonType = AddonManager.addonTypes[this._addon.type];
3098 if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
3099 (hasPermission(this._addon, "ask_to_activate") ||
3100 hasPermission(this._addon, "enable") ||
3101 hasPermission(this._addon, "disable"))) {
3102 let askItem = document.getElementById("detail-ask-to-activate-menuitem");
3103 let alwaysItem = document.getElementById("detail-always-activate-menuitem");
3104 let neverItem = document.getElementById("detail-never-activate-menuitem");
3105 if (this._addon.userDisabled === true) {
3106 menulist.selectedItem = neverItem;
3107 } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
3108 menulist.selectedItem = askItem;
3109 } else {
3110 menulist.selectedItem = alwaysItem;
3111 }
3112 menulist.hidden = false;
3113 } else {
3114 menulist.hidden = true;
3115 }
3117 this.node.setAttribute("active", this._addon.isActive);
3118 },
3120 clearLoading: function gDetailView_clearLoading() {
3121 if (this._loadingTimer) {
3122 clearTimeout(this._loadingTimer);
3123 this._loadingTimer = null;
3124 }
3126 this.node.removeAttribute("loading-extended");
3127 },
3129 emptySettingsRows: function gDetailView_emptySettingsRows() {
3130 var lastRow = document.getElementById("detail-downloads");
3131 var rows = lastRow.parentNode;
3132 while (lastRow.nextSibling)
3133 rows.removeChild(rows.lastChild);
3134 },
3136 fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) {
3137 this.emptySettingsRows();
3138 if (!hasInlineOptions(this._addon)) {
3139 if (aCallback)
3140 aCallback();
3141 return;
3142 }
3144 // This function removes and returns the text content of aNode without
3145 // removing any child elements. Removing the text nodes ensures any XBL
3146 // bindings apply properly.
3147 function stripTextNodes(aNode) {
3148 var text = '';
3149 for (var i = 0; i < aNode.childNodes.length; i++) {
3150 if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
3151 text += aNode.childNodes[i].textContent;
3152 aNode.removeChild(aNode.childNodes[i--]);
3153 } else {
3154 text += stripTextNodes(aNode.childNodes[i]);
3155 }
3156 }
3157 return text;
3158 }
3160 var rows = document.getElementById("detail-downloads").parentNode;
3162 try {
3163 var xhr = new XMLHttpRequest();
3164 xhr.open("GET", this._addon.optionsURL, true);
3165 xhr.responseType = "xml";
3166 xhr.onload = (function fillSettingsRows_onload() {
3167 var xml = xhr.responseXML;
3168 var settings = xml.querySelectorAll(":root > setting");
3170 var firstSetting = null;
3171 for (var setting of settings) {
3173 var desc = stripTextNodes(setting).trim();
3174 if (!setting.hasAttribute("desc"))
3175 setting.setAttribute("desc", desc);
3177 var type = setting.getAttribute("type");
3178 if (type == "file" || type == "directory")
3179 setting.setAttribute("fullpath", "true");
3181 setting = document.importNode(setting, true);
3182 var style = setting.getAttribute("style");
3183 if (style) {
3184 setting.removeAttribute("style");
3185 setting.setAttribute("style", style);
3186 }
3188 rows.appendChild(setting);
3189 var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none";
3190 if (!firstSetting && visible) {
3191 setting.setAttribute("first-row", true);
3192 firstSetting = setting;
3193 }
3194 }
3196 // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
3197 // then notify observers.
3198 if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) {
3199 gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() {
3200 gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false);
3201 if (firstSetting)
3202 firstSetting.clientTop;
3203 Services.obs.notifyObservers(document,
3204 AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
3205 gDetailView._addon.id);
3206 if (aScrollToPreferences)
3207 gDetailView.scrollToPreferencesRows();
3208 }, false);
3209 } else {
3210 if (firstSetting)
3211 firstSetting.clientTop;
3212 Services.obs.notifyObservers(document,
3213 AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
3214 this._addon.id);
3215 if (aScrollToPreferences)
3216 gDetailView.scrollToPreferencesRows();
3217 }
3218 if (aCallback)
3219 aCallback();
3220 }).bind(this);
3221 xhr.onerror = function fillSettingsRows_onerror(aEvent) {
3222 Cu.reportError("Error " + aEvent.target.status +
3223 " occurred while receiving " + this._addon.optionsURL);
3224 if (aCallback)
3225 aCallback();
3226 };
3227 xhr.send();
3228 } catch(e) {
3229 Cu.reportError(e);
3230 if (aCallback)
3231 aCallback();
3232 }
3233 },
3235 scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() {
3236 // We find this row, rather than remembering it from above,
3237 // in case it has been changed by the observers.
3238 let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
3239 if (firstRow) {
3240 let top = firstRow.boxObject.y;
3241 top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
3243 let detailViewBoxObject = gDetailView.node.boxObject;
3244 top -= detailViewBoxObject.y;
3246 detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject);
3247 detailViewBoxObject.scrollTo(0, top);
3248 }
3249 },
3251 getSelectedAddon: function gDetailView_getSelectedAddon() {
3252 return this._addon;
3253 },
3255 onEnabling: function gDetailView_onEnabling() {
3256 this.updateState();
3257 },
3259 onEnabled: function gDetailView_onEnabled() {
3260 this.updateState();
3261 this.fillSettingsRows();
3262 },
3264 onDisabling: function gDetailView_onDisabling(aNeedsRestart) {
3265 this.updateState();
3266 if (!aNeedsRestart && hasInlineOptions(this._addon)) {
3267 Services.obs.notifyObservers(document,
3268 AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
3269 this._addon.id);
3270 }
3271 },
3273 onDisabled: function gDetailView_onDisabled() {
3274 this.updateState();
3275 this.emptySettingsRows();
3276 },
3278 onUninstalling: function gDetailView_onUninstalling() {
3279 this.updateState();
3280 },
3282 onUninstalled: function gDetailView_onUninstalled() {
3283 gViewController.popState();
3284 },
3286 onOperationCancelled: function gDetailView_onOperationCancelled() {
3287 this.updateState();
3288 },
3290 onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) {
3291 if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
3292 this._autoUpdate.value = this._addon.applyBackgroundUpdates;
3293 let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
3294 document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
3295 }
3297 if (aProperties.indexOf("appDisabled") != -1 ||
3298 aProperties.indexOf("userDisabled") != -1)
3299 this.updateState();
3300 },
3302 onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
3303 // Only care about upgrades for the currently displayed add-on
3304 if (!aExistingAddon || aExistingAddon.id != this._addon.id)
3305 return;
3307 if (!aNeedsRestart)
3308 this._updateView(aAddon, false);
3309 else
3310 this.updateState();
3311 },
3313 onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) {
3314 if (aInstall.addon.id == this._addon.id)
3315 gViewController.popState();
3316 }
3317 };
3320 var gUpdatesView = {
3321 node: null,
3322 _listBox: null,
3323 _emptyNotice: null,
3324 _sorters: null,
3325 _updateSelected: null,
3326 _categoryItem: null,
3328 initialize: function gUpdatesView_initialize() {
3329 this.node = document.getElementById("updates-view");
3330 this._listBox = document.getElementById("updates-list");
3331 this._emptyNotice = document.getElementById("updates-list-empty");
3332 this._sorters = document.getElementById("updates-sorters");
3333 this._sorters.handler = this;
3335 this._categoryItem = gCategories.get("addons://updates/available");
3337 this._updateSelected = document.getElementById("update-selected-btn");
3338 this._updateSelected.addEventListener("command", function updateSelected_onCommand() {
3339 gUpdatesView.installSelected();
3340 }, false);
3342 this.updateAvailableCount(true);
3344 AddonManager.addAddonListener(this);
3345 AddonManager.addInstallListener(this);
3346 },
3348 shutdown: function gUpdatesView_shutdown() {
3349 AddonManager.removeAddonListener(this);
3350 AddonManager.removeInstallListener(this);
3351 },
3353 show: function gUpdatesView_show(aType, aRequest) {
3354 document.getElementById("empty-availableUpdates-msg").hidden = aType != "available";
3355 document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent";
3356 this.showEmptyNotice(false);
3358 while (this._listBox.itemCount > 0)
3359 this._listBox.removeItemAt(0);
3361 this.node.setAttribute("updatetype", aType);
3362 if (aType == "recent")
3363 this._showRecentUpdates(aRequest);
3364 else
3365 this._showAvailableUpdates(false, aRequest);
3366 },
3368 hide: function gUpdatesView_hide() {
3369 this._updateSelected.hidden = true;
3370 this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
3371 doPendingUninstalls(this._listBox);
3372 },
3374 _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) {
3375 var self = this;
3376 AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) {
3377 if (gViewController && aRequest != gViewController.currentViewRequest)
3378 return;
3380 var elements = [];
3381 let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
3382 for (let addon of aAddonsList) {
3383 if (!addon.updateDate || addon.updateDate.getTime() < threshold)
3384 continue;
3386 elements.push(createItem(addon));
3387 }
3389 self.showEmptyNotice(elements.length == 0);
3390 if (elements.length > 0) {
3391 sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
3392 for (let element of elements)
3393 self._listBox.appendChild(element);
3394 }
3396 gViewController.notifyViewChanged();
3397 });
3398 },
3400 _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) {
3401 /* Disable the Update Selected button so it can't get clicked
3402 before everything is initialized asynchronously.
3403 It will get re-enabled by maybeDisableUpdateSelected(). */
3404 this._updateSelected.disabled = true;
3406 var self = this;
3407 AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) {
3408 if (!aIsRefresh && gViewController && aRequest &&
3409 aRequest != gViewController.currentViewRequest)
3410 return;
3412 if (aIsRefresh) {
3413 self.showEmptyNotice(false);
3414 self._updateSelected.hidden = true;
3416 while (self._listBox.itemCount > 0)
3417 self._listBox.removeItemAt(0);
3418 }
3420 var elements = [];
3422 for (let install of aInstallsList) {
3423 if (!self.isManualUpdate(install))
3424 continue;
3426 let item = createItem(install.existingAddon);
3427 item.setAttribute("upgrade", true);
3428 item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() {
3429 self.maybeDisableUpdateSelected();
3430 }, false);
3431 elements.push(item);
3432 }
3434 self.showEmptyNotice(elements.length == 0);
3435 if (elements.length > 0) {
3436 self._updateSelected.hidden = false;
3437 sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
3438 for (let element of elements)
3439 self._listBox.appendChild(element);
3440 }
3442 // ensure badge count is in sync
3443 self._categoryItem.badgeCount = self._listBox.itemCount;
3445 gViewController.notifyViewChanged();
3446 });
3447 },
3449 showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) {
3450 this._emptyNotice.hidden = !aShow;
3451 },
3453 isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) {
3454 var isManual = aInstall.existingAddon &&
3455 !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
3456 if (isManual && aOnlyAvailable)
3457 return isInState(aInstall, "available");
3458 return isManual;
3459 },
3461 maybeRefresh: function gUpdatesView_maybeRefresh() {
3462 if (gViewController.currentViewId == "addons://updates/available")
3463 this._showAvailableUpdates(true);
3464 this.updateAvailableCount();
3465 },
3467 updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) {
3468 if (aInitializing)
3469 gPendingInitializations++;
3470 var self = this;
3471 AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) {
3472 var count = aInstallsList.filter(function installListFilter(aInstall) {
3473 return self.isManualUpdate(aInstall, true);
3474 }).length;
3475 self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
3476 count == 0;
3477 self._categoryItem.badgeCount = count;
3478 if (aInitializing)
3479 notifyInitialized();
3480 });
3481 },
3483 maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() {
3484 for (let item of this._listBox.childNodes) {
3485 if (item.includeUpdate) {
3486 this._updateSelected.disabled = false;
3487 return;
3488 }
3489 }
3490 this._updateSelected.disabled = true;
3491 },
3493 installSelected: function gUpdatesView_installSelected() {
3494 for (let item of this._listBox.childNodes) {
3495 if (item.includeUpdate)
3496 item.upgrade();
3497 }
3499 this._updateSelected.disabled = true;
3500 },
3502 getSelectedAddon: function gUpdatesView_getSelectedAddon() {
3503 var item = this._listBox.selectedItem;
3504 if (item)
3505 return item.mAddon;
3506 return null;
3507 },
3509 getListItemForID: function gUpdatesView_getListItemForID(aId) {
3510 var listitem = this._listBox.firstChild;
3511 while (listitem) {
3512 if (listitem.mAddon.id == aId)
3513 return listitem;
3514 listitem = listitem.nextSibling;
3515 }
3516 return null;
3517 },
3519 onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) {
3520 sortList(this._listBox, aSortBy, aAscending);
3521 },
3523 onNewInstall: function gUpdatesView_onNewInstall(aInstall) {
3524 if (!this.isManualUpdate(aInstall))
3525 return;
3526 this.maybeRefresh();
3527 },
3529 onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) {
3530 this.updateAvailableCount();
3531 },
3533 onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) {
3534 if (!this.isManualUpdate(aInstall))
3535 return;
3536 this.maybeRefresh();
3537 },
3539 onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) {
3540 if (aProperties.indexOf("applyBackgroundUpdates") != -1)
3541 this.updateAvailableCount();
3542 }
3543 };
3545 function debuggingPrefChanged() {
3546 gViewController.updateState();
3547 gViewController.updateCommands();
3548 gViewController.notifyViewChanged();
3549 }
3551 var gDragDrop = {
3552 onDragOver: function gDragDrop_onDragOver(aEvent) {
3553 var types = aEvent.dataTransfer.types;
3554 if (types.contains("text/uri-list") ||
3555 types.contains("text/x-moz-url") ||
3556 types.contains("application/x-moz-file"))
3557 aEvent.preventDefault();
3558 },
3560 onDrop: function gDragDrop_onDrop(aEvent) {
3561 var dataTransfer = aEvent.dataTransfer;
3562 var urls = [];
3564 // Convert every dropped item into a url
3565 for (var i = 0; i < dataTransfer.mozItemCount; i++) {
3566 var url = dataTransfer.mozGetDataAt("text/uri-list", i);
3567 if (url) {
3568 urls.push(url);
3569 continue;
3570 }
3572 url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
3573 if (url) {
3574 urls.push(url.split("\n")[0]);
3575 continue;
3576 }
3578 var file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
3579 if (file) {
3580 urls.push(Services.io.newFileURI(file).spec);
3581 continue;
3582 }
3583 }
3585 var pos = 0;
3586 var installs = [];
3588 function buildNextInstall() {
3589 if (pos == urls.length) {
3590 if (installs.length > 0) {
3591 // Display the normal install confirmation for the installs
3592 AddonManager.installAddonsFromWebpage("application/x-xpinstall",
3593 window, null, installs);
3594 }
3595 return;
3596 }
3598 AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) {
3599 installs.push(aInstall);
3600 buildNextInstall();
3601 }, "application/x-xpinstall");
3602 }
3604 buildNextInstall();
3606 aEvent.preventDefault();
3607 }
3608 };