|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 const Cr = Components.results; |
|
11 |
|
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"); |
|
17 |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", |
|
19 "resource://gre/modules/PluralForm.jsm"); |
|
20 |
|
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"); |
|
27 |
|
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"; |
|
38 |
|
39 const LOADING_MSG_DELAY = 100; |
|
40 |
|
41 const SEARCH_SCORE_MULTIPLIER_NAME = 2; |
|
42 const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2; |
|
43 |
|
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; |
|
48 |
|
49 const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds) |
|
50 const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl"; |
|
51 |
|
52 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" |
|
53 |
|
54 const VIEW_DEFAULT = "addons://discover/"; |
|
55 |
|
56 var gStrings = {}; |
|
57 XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc", |
|
58 "@mozilla.org/intl/stringbundle;1", |
|
59 "nsIStringBundleService"); |
|
60 |
|
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 }); |
|
70 |
|
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 }); |
|
77 |
|
78 document.addEventListener("load", initialize, true); |
|
79 window.addEventListener("unload", shutdown, false); |
|
80 |
|
81 var gPendingInitializations = 1; |
|
82 this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0); |
|
83 |
|
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); |
|
91 |
|
92 let globalCommandSet = document.getElementById("globalCommandSet"); |
|
93 globalCommandSet.addEventListener("command", function(event) { |
|
94 gViewController.doCommand(event.target.id); |
|
95 }); |
|
96 |
|
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 }); |
|
104 |
|
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 }); |
|
112 |
|
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 }); |
|
126 |
|
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", ""); |
|
133 |
|
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; |
|
138 |
|
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 } |
|
144 |
|
145 // Default to the last selected category |
|
146 var view = gCategories.node.value; |
|
147 |
|
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 } |
|
153 |
|
154 gViewController.loadInitialView(view); |
|
155 |
|
156 Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false); |
|
157 Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false); |
|
158 } |
|
159 |
|
160 function notifyInitialized() { |
|
161 if (!gIsInitializing) |
|
162 return; |
|
163 |
|
164 gPendingInitializations--; |
|
165 if (!gIsInitializing) { |
|
166 var event = document.createEvent("Events"); |
|
167 event.initEvent("Initialized", true, true); |
|
168 document.dispatchEvent(event); |
|
169 } |
|
170 } |
|
171 |
|
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 } |
|
181 |
|
182 function sendEMPong(aSubject, aTopic, aData) { |
|
183 Services.obs.notifyObservers(window, "EM-pong", ""); |
|
184 } |
|
185 |
|
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 |
|
191 |
|
192 gViewController.loadInitialView(aViewId); |
|
193 } else { |
|
194 gViewController.loadView(aViewId); |
|
195 } |
|
196 } |
|
197 |
|
198 function isDiscoverEnabled() { |
|
199 if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID) |
|
200 return false; |
|
201 |
|
202 try { |
|
203 if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) |
|
204 return false; |
|
205 } catch (e) {} |
|
206 |
|
207 try { |
|
208 if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED)) |
|
209 return false; |
|
210 } catch (e) {} |
|
211 |
|
212 return true; |
|
213 } |
|
214 |
|
215 function getExperimentEndDate(aAddon) { |
|
216 if (!("@mozilla.org/browser/experiments-service;1" in Cc)) { |
|
217 return 0; |
|
218 } |
|
219 |
|
220 if (!aAddon.isActive) { |
|
221 return aAddon.endDate; |
|
222 } |
|
223 |
|
224 let experiment = Experiments.instance().getActiveExperiment(); |
|
225 if (!experiment) { |
|
226 return 0; |
|
227 } |
|
228 |
|
229 return experiment.endDate; |
|
230 } |
|
231 |
|
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 } |
|
243 |
|
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 } |
|
261 |
|
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 }, |
|
272 |
|
273 get canGoBack() { |
|
274 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
275 .getInterface(Ci.nsIWebNavigation) |
|
276 .canGoBack; |
|
277 }, |
|
278 |
|
279 get canGoForward() { |
|
280 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
281 .getInterface(Ci.nsIWebNavigation) |
|
282 .canGoForward; |
|
283 }, |
|
284 |
|
285 back: function HTML5History_back() { |
|
286 window.history.back(); |
|
287 gViewController.updateCommand("cmd_back"); |
|
288 gViewController.updateCommand("cmd_forward"); |
|
289 }, |
|
290 |
|
291 forward: function HTML5History_forward() { |
|
292 window.history.forward(); |
|
293 gViewController.updateCommand("cmd_back"); |
|
294 gViewController.updateCommand("cmd_forward"); |
|
295 }, |
|
296 |
|
297 pushState: function HTML5History_pushState(aState) { |
|
298 window.history.pushState(aState, document.title); |
|
299 }, |
|
300 |
|
301 replaceState: function HTML5History_replaceState(aState) { |
|
302 window.history.replaceState(aState, document.title); |
|
303 }, |
|
304 |
|
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 }; |
|
319 |
|
320 /** |
|
321 * A wrapper around a fake history service |
|
322 */ |
|
323 var FakeHistory = { |
|
324 pos: 0, |
|
325 states: [null], |
|
326 |
|
327 get index() { |
|
328 return this.pos; |
|
329 }, |
|
330 |
|
331 get canGoBack() { |
|
332 return this.pos > 0; |
|
333 }, |
|
334 |
|
335 get canGoForward() { |
|
336 return (this.pos + 1) < this.states.length; |
|
337 }, |
|
338 |
|
339 back: function FakeHistory_back() { |
|
340 if (this.pos == 0) |
|
341 throw Components.Exception("Cannot go back from this point"); |
|
342 |
|
343 this.pos--; |
|
344 gViewController.updateState(this.states[this.pos]); |
|
345 gViewController.updateCommand("cmd_back"); |
|
346 gViewController.updateCommand("cmd_forward"); |
|
347 }, |
|
348 |
|
349 forward: function FakeHistory_forward() { |
|
350 if ((this.pos + 1) >= this.states.length) |
|
351 throw Components.Exception("Cannot go forward from this point"); |
|
352 |
|
353 this.pos++; |
|
354 gViewController.updateState(this.states[this.pos]); |
|
355 gViewController.updateCommand("cmd_back"); |
|
356 gViewController.updateCommand("cmd_forward"); |
|
357 }, |
|
358 |
|
359 pushState: function FakeHistory_pushState(aState) { |
|
360 this.pos++; |
|
361 this.states.splice(this.pos, this.states.length); |
|
362 this.states.push(aState); |
|
363 }, |
|
364 |
|
365 replaceState: function FakeHistory_replaceState(aState) { |
|
366 this.states[this.pos] = aState; |
|
367 }, |
|
368 |
|
369 popState: function FakeHistory_popState() { |
|
370 if (this.pos == 0) |
|
371 throw Components.Exception("Cannot popState from this view"); |
|
372 |
|
373 this.states.splice(this.pos, this.states.length); |
|
374 this.pos--; |
|
375 |
|
376 gViewController.updateState(this.states[this.pos]); |
|
377 gViewController.updateCommand("cmd_back"); |
|
378 gViewController.updateCommand("cmd_forward"); |
|
379 } |
|
380 }; |
|
381 |
|
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 } |
|
392 |
|
393 var gEventManager = { |
|
394 _listeners: {}, |
|
395 _installListeners: [], |
|
396 |
|
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 } |
|
411 |
|
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 } |
|
424 |
|
425 AddonManager.addManagerListener(this); |
|
426 AddonManager.addInstallListener(this); |
|
427 AddonManager.addAddonListener(this); |
|
428 |
|
429 this.refreshGlobalWarning(); |
|
430 this.refreshAutoUpdateDefault(); |
|
431 |
|
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); |
|
436 |
|
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 } |
|
445 |
|
446 // with only one menu item, we hide the menu separator |
|
447 menuSep.hidden = (countEnabledMenuCmds <= 1); |
|
448 |
|
449 }, false); |
|
450 }, |
|
451 |
|
452 shutdown: function gEM_shutdown() { |
|
453 AddonManager.removeManagerListener(this); |
|
454 AddonManager.removeInstallListener(this); |
|
455 AddonManager.removeAddonListener(this); |
|
456 }, |
|
457 |
|
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 }, |
|
465 |
|
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 }, |
|
474 |
|
475 registerInstallListener: function gEM_registerInstallListener(aListener) { |
|
476 if (this._installListeners.indexOf(aListener) != -1) |
|
477 return; |
|
478 this._installListeners.push(aListener); |
|
479 }, |
|
480 |
|
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 }, |
|
487 |
|
488 delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) { |
|
489 var addon = aParams.shift(); |
|
490 if (!(addon.id in this._listeners)) |
|
491 return; |
|
492 |
|
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 }, |
|
505 |
|
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)); |
|
512 |
|
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 }, |
|
524 |
|
525 refreshGlobalWarning: function gEM_refreshGlobalWarning() { |
|
526 var page = document.getElementById("addons-page"); |
|
527 |
|
528 if (Services.appinfo.inSafeMode) { |
|
529 page.setAttribute("warning", "safemode"); |
|
530 return; |
|
531 } |
|
532 |
|
533 if (AddonManager.checkUpdateSecurityDefault && |
|
534 !AddonManager.checkUpdateSecurity) { |
|
535 page.setAttribute("warning", "updatesecurity"); |
|
536 return; |
|
537 } |
|
538 |
|
539 if (!AddonManager.checkCompatibility) { |
|
540 page.setAttribute("warning", "checkcompatibility"); |
|
541 return; |
|
542 } |
|
543 |
|
544 page.removeAttribute("warning"); |
|
545 }, |
|
546 |
|
547 refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() { |
|
548 var updateEnabled = AddonManager.updateEnabled; |
|
549 var autoUpdateDefault = AddonManager.autoUpdateDefault; |
|
550 |
|
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); |
|
555 |
|
556 document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault; |
|
557 document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault; |
|
558 }, |
|
559 |
|
560 onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() { |
|
561 this.refreshGlobalWarning(); |
|
562 }, |
|
563 |
|
564 onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() { |
|
565 this.refreshGlobalWarning(); |
|
566 }, |
|
567 |
|
568 onUpdateModeChanged: function gEM_onUpdateModeChanged() { |
|
569 this.refreshAutoUpdateDefault(); |
|
570 } |
|
571 }; |
|
572 |
|
573 |
|
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, |
|
583 |
|
584 initialize: function gVC_initialize() { |
|
585 this.viewPort = document.getElementById("view-port"); |
|
586 |
|
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; |
|
592 |
|
593 for each (let view in this.viewObjects) |
|
594 view.initialize(); |
|
595 |
|
596 window.controllers.appendController(this); |
|
597 |
|
598 window.addEventListener("popstate", |
|
599 function window_onStatePopped(e) { |
|
600 gViewController.updateState(e.state); |
|
601 }, |
|
602 false); |
|
603 }, |
|
604 |
|
605 shutdown: function gVC_shutdown() { |
|
606 if (this.currentViewObj) |
|
607 this.currentViewObj.hide(); |
|
608 this.currentViewRequest = 0; |
|
609 |
|
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 } |
|
620 |
|
621 window.controllers.removeController(this); |
|
622 }, |
|
623 |
|
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 }, |
|
644 |
|
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 }, |
|
650 |
|
651 get isLoading() { |
|
652 return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading"); |
|
653 }, |
|
654 |
|
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 } |
|
666 |
|
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 }, |
|
677 |
|
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; |
|
683 |
|
684 var state = { |
|
685 view: aViewId, |
|
686 previousView: null |
|
687 }; |
|
688 gHistory.replaceState(state); |
|
689 this.loadViewInternal(aViewId, null, state); |
|
690 }, |
|
691 |
|
692 loadInitialView: function gVC_loadInitialView(aViewId) { |
|
693 var state = { |
|
694 view: aViewId, |
|
695 previousView: null |
|
696 }; |
|
697 gHistory.replaceState(state); |
|
698 |
|
699 this.loadViewInternal(aViewId, null, state); |
|
700 this.initialViewSelected = true; |
|
701 notifyInitialized(); |
|
702 }, |
|
703 |
|
704 loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) { |
|
705 var view = this.parseViewId(aViewId); |
|
706 |
|
707 if (!view.type || !(view.type in this.viewObjects)) |
|
708 throw Components.Exception("Invalid view: " + view.type); |
|
709 |
|
710 var viewObj = this.viewObjects[view.type]; |
|
711 if (!viewObj.node) |
|
712 throw Components.Exception("Root node doesn't exist for '" + view.type + "' view"); |
|
713 |
|
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 } |
|
725 |
|
726 gCategories.select(aViewId, aPreviousView); |
|
727 |
|
728 this.currentViewId = aViewId; |
|
729 this.currentViewObj = viewObj; |
|
730 |
|
731 this.viewPort.selectedPanel = this.currentViewObj.node; |
|
732 this.viewPort.selectedPanel.setAttribute("loading", "true"); |
|
733 this.currentViewObj.node.focus(); |
|
734 |
|
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 }, |
|
740 |
|
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 }, |
|
746 |
|
747 notifyViewChanged: function gVC_notifyViewChanged() { |
|
748 this.viewPort.selectedPanel.removeAttribute("loading"); |
|
749 |
|
750 if (this.viewChangeCallback) { |
|
751 this.viewChangeCallback(); |
|
752 this.viewChangeCallback = null; |
|
753 } |
|
754 |
|
755 var event = document.createEvent("Events"); |
|
756 event.initEvent("ViewChanged", true, true); |
|
757 this.currentViewObj.node.dispatchEvent(event); |
|
758 }, |
|
759 |
|
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 }, |
|
769 |
|
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 }, |
|
778 |
|
779 cmd_focusSearch: { |
|
780 isEnabled: () => true, |
|
781 doCommand: function cmd_focusSearch_doCommand() { |
|
782 gHeader.focusSearchBox(); |
|
783 } |
|
784 }, |
|
785 |
|
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 |
|
795 |
|
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 }, |
|
801 |
|
802 cmd_enableCheckCompatibility: { |
|
803 isEnabled: function cmd_enableCheckCompatibility_isEnabled() true, |
|
804 doCommand: function cmd_enableCheckCompatibility_doCommand() { |
|
805 AddonManager.checkCompatibility = true; |
|
806 } |
|
807 }, |
|
808 |
|
809 cmd_enableUpdateSecurity: { |
|
810 isEnabled: function cmd_enableUpdateSecurity_isEnabled() true, |
|
811 doCommand: function cmd_enableUpdateSecurity_doCommand() { |
|
812 AddonManager.checkUpdateSecurity = true; |
|
813 } |
|
814 }, |
|
815 |
|
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 }, |
|
822 |
|
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 }, |
|
839 |
|
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 }, |
|
851 |
|
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 }, |
|
860 |
|
861 cmd_goToRecentUpdates: { |
|
862 isEnabled: function cmd_goToRecentUpdates_isEnabled() true, |
|
863 doCommand: function cmd_goToRecentUpdates_doCommand() { |
|
864 gViewController.loadView("addons://updates/recent"); |
|
865 } |
|
866 }, |
|
867 |
|
868 cmd_goToAvailableUpdates: { |
|
869 isEnabled: function cmd_goToAvailableUpdates_isEnabled() true, |
|
870 doCommand: function cmd_goToAvailableUpdates_doCommand() { |
|
871 gViewController.loadView("addons://updates/available"); |
|
872 } |
|
873 }, |
|
874 |
|
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 }, |
|
885 |
|
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; |
|
895 |
|
896 var pendingChecks = 0; |
|
897 var numUpdated = 0; |
|
898 var numManualUpdates = 0; |
|
899 var restartNeeded = false; |
|
900 var self = this; |
|
901 |
|
902 function updateStatus() { |
|
903 if (pendingChecks > 0) |
|
904 return; |
|
905 |
|
906 self.inProgress = false; |
|
907 gViewController.updateCommand("cmd_findAllUpdates"); |
|
908 document.getElementById("updates-progress").hidden = true; |
|
909 gUpdatesView.maybeRefresh(); |
|
910 |
|
911 if (numManualUpdates > 0 && numUpdated == 0) { |
|
912 document.getElementById("updates-manualUpdatesFound-btn").hidden = false; |
|
913 return; |
|
914 } |
|
915 |
|
916 if (numUpdated == 0) { |
|
917 document.getElementById("updates-noneFound").hidden = false; |
|
918 return; |
|
919 } |
|
920 |
|
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 } |
|
928 |
|
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 }; |
|
946 |
|
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 }; |
|
969 |
|
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 } |
|
978 |
|
979 if (pendingChecks == 0) |
|
980 updateStatus(); |
|
981 }); |
|
982 } |
|
983 }, |
|
984 |
|
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 }, |
|
1008 |
|
1009 cmd_debugItem: { |
|
1010 doCommand: function cmd_debugItem_doCommand(aAddon) { |
|
1011 BrowserToolboxProcess.init({ addonID: aAddon.id }); |
|
1012 }, |
|
1013 |
|
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 }, |
|
1022 |
|
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 }, |
|
1066 |
|
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 }, |
|
1081 |
|
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 }, |
|
1101 |
|
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 }, |
|
1121 |
|
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 } |
|
1132 |
|
1133 if (gViewController.currentViewObj == gDetailView) |
|
1134 gViewController.popState(doInstall); |
|
1135 else |
|
1136 doInstall(); |
|
1137 } |
|
1138 }, |
|
1139 |
|
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 }, |
|
1150 |
|
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 } |
|
1162 |
|
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 }, |
|
1175 |
|
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 }, |
|
1186 |
|
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) { } |
|
1201 |
|
1202 if (fp.show() != nsIFilePicker.returnOK) |
|
1203 return; |
|
1204 |
|
1205 var files = fp.files; |
|
1206 var installs = []; |
|
1207 |
|
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 } |
|
1217 |
|
1218 var file = files.getNext(); |
|
1219 AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) { |
|
1220 installs.push(aInstall); |
|
1221 buildNextInstall(); |
|
1222 }); |
|
1223 } |
|
1224 |
|
1225 buildNextInstall(); |
|
1226 } |
|
1227 }, |
|
1228 |
|
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 }, |
|
1249 |
|
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 }, |
|
1260 |
|
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 }, |
|
1273 |
|
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 }, |
|
1286 |
|
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 }, |
|
1299 |
|
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 }, |
|
1310 |
|
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 }, |
|
1321 |
|
1322 supportsCommand: function gVC_supportsCommand(aCommand) { |
|
1323 return (aCommand in this.commands); |
|
1324 }, |
|
1325 |
|
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 }, |
|
1332 |
|
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 }, |
|
1341 |
|
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 }, |
|
1356 |
|
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 }, |
|
1367 |
|
1368 onEvent: function gVC_onEvent() {} |
|
1369 }; |
|
1370 |
|
1371 function hasInlineOptions(aAddon) { |
|
1372 return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE || |
|
1373 aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO); |
|
1374 } |
|
1375 |
|
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 } |
|
1384 |
|
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 } |
|
1395 |
|
1396 |
|
1397 function hasPermission(aAddon, aPerm) { |
|
1398 var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()]; |
|
1399 return !!(aAddon.permissions & perm); |
|
1400 } |
|
1401 |
|
1402 |
|
1403 function isPending(aAddon, aAction) { |
|
1404 var action = AddonManager["PENDING_" + aAction.toUpperCase()]; |
|
1405 return !!(aAddon.pendingOperations & action); |
|
1406 } |
|
1407 |
|
1408 function isInState(aInstall, aState) { |
|
1409 var state = AddonManager["STATE_" + aState.toUpperCase()]; |
|
1410 return aInstall.state == state; |
|
1411 } |
|
1412 |
|
1413 function shouldShowVersionNumber(aAddon) { |
|
1414 if (!aAddon.version) |
|
1415 return false; |
|
1416 |
|
1417 // The version number is hidden for lightweight themes. |
|
1418 if (aAddon.type == "theme") |
|
1419 return !/@personas\.mozilla\.org$/.test(aAddon.id); |
|
1420 |
|
1421 return true; |
|
1422 } |
|
1423 |
|
1424 function createItem(aObj, aIsInstall, aIsRemote) { |
|
1425 let item = document.createElement("richlistitem"); |
|
1426 |
|
1427 item.setAttribute("class", "addon addon-view"); |
|
1428 item.setAttribute("name", aObj.name); |
|
1429 item.setAttribute("type", aObj.type); |
|
1430 item.setAttribute("remote", !!aIsRemote); |
|
1431 |
|
1432 if (aIsInstall) { |
|
1433 item.mInstall = aObj; |
|
1434 |
|
1435 if (aObj.state != AddonManager.STATE_INSTALLED) { |
|
1436 item.setAttribute("status", "installing"); |
|
1437 return item; |
|
1438 } |
|
1439 aObj = aObj.addon; |
|
1440 } |
|
1441 |
|
1442 item.mAddon = aObj; |
|
1443 |
|
1444 item.setAttribute("status", "installed"); |
|
1445 |
|
1446 // set only attributes needed for sorting and XBL binding, |
|
1447 // the binding handles the rest |
|
1448 item.setAttribute("value", aObj.id); |
|
1449 |
|
1450 if (aObj.type == "experiment") { |
|
1451 item.endDate = getExperimentEndDate(aObj); |
|
1452 } |
|
1453 |
|
1454 return item; |
|
1455 } |
|
1456 |
|
1457 function sortElements(aElements, aSortBy, aAscending) { |
|
1458 // aSortBy is an Array of attributes to sort by, in decending |
|
1459 // order of priority. |
|
1460 |
|
1461 const DATE_FIELDS = ["updateDate"]; |
|
1462 const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"]; |
|
1463 |
|
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 |
|
1482 |
|
1483 const UISTATE_ORDER = ["enabled", "pendingDisable", "pendingUninstall", |
|
1484 "disabled"]; |
|
1485 |
|
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 } |
|
1495 |
|
1496 function numberCompare(a, b) { |
|
1497 return a - b; |
|
1498 } |
|
1499 |
|
1500 function stringCompare(a, b) { |
|
1501 return a.localeCompare(b); |
|
1502 } |
|
1503 |
|
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]; |
|
1509 |
|
1510 return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b)); |
|
1511 } |
|
1512 |
|
1513 function getValue(aObj, aKey) { |
|
1514 if (!aObj) |
|
1515 return null; |
|
1516 |
|
1517 if (aObj.hasAttribute(aKey)) |
|
1518 return aObj.getAttribute(aKey); |
|
1519 |
|
1520 var addon = aObj.mAddon || aObj.mInstall; |
|
1521 if (!addon) |
|
1522 return null; |
|
1523 |
|
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 } |
|
1536 |
|
1537 return addon[aKey]; |
|
1538 } |
|
1539 |
|
1540 // aSortFuncs will hold the sorting functions that we'll |
|
1541 // use per element, in the correct order. |
|
1542 var aSortFuncs = []; |
|
1543 |
|
1544 for (let i = 0; i < aSortBy.length; i++) { |
|
1545 var sortBy = aSortBy[i]; |
|
1546 |
|
1547 aSortFuncs[i] = stringCompare; |
|
1548 |
|
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 } |
|
1556 |
|
1557 |
|
1558 aElements.sort(function elementsSort(a, b) { |
|
1559 if (!aAscending) |
|
1560 [a, b] = [b, a]; |
|
1561 |
|
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); |
|
1566 |
|
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); |
|
1575 |
|
1576 if (result != 0) |
|
1577 return result; |
|
1578 } |
|
1579 } |
|
1580 |
|
1581 // If we got here, then all values of a and b |
|
1582 // must have been equal. |
|
1583 return 0; |
|
1584 |
|
1585 }); |
|
1586 } |
|
1587 |
|
1588 function sortList(aList, aSortBy, aAscending) { |
|
1589 var elements = Array.slice(aList.childNodes, 0); |
|
1590 sortElements(elements, [aSortBy], aAscending); |
|
1591 |
|
1592 while (aList.listChild) |
|
1593 aList.removeChild(aList.lastChild); |
|
1594 |
|
1595 for (let element of elements) |
|
1596 aList.appendChild(element); |
|
1597 } |
|
1598 |
|
1599 function getAddonsAndInstalls(aType, aCallback) { |
|
1600 let addons = null, installs = null; |
|
1601 let types = (aType != null) ? [aType] : null; |
|
1602 |
|
1603 AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) { |
|
1604 addons = aAddonsList; |
|
1605 if (installs != null) |
|
1606 aCallback(addons, installs); |
|
1607 }); |
|
1608 |
|
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 }); |
|
1615 |
|
1616 if (addons != null) |
|
1617 aCallback(addons, installs) |
|
1618 }); |
|
1619 } |
|
1620 |
|
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 } |
|
1632 |
|
1633 for (let addon of items) |
|
1634 addon.uninstall(); |
|
1635 } |
|
1636 |
|
1637 var gCategories = { |
|
1638 node: null, |
|
1639 _search: null, |
|
1640 |
|
1641 initialize: function gCategories_initialize() { |
|
1642 this.node = document.getElementById("categories"); |
|
1643 this._search = this.get("addons://search/"); |
|
1644 |
|
1645 var types = AddonManager.addonTypes; |
|
1646 for (var type in types) |
|
1647 this.onTypeAdded(types[type]); |
|
1648 |
|
1649 AddonManager.addTypeListener(this); |
|
1650 |
|
1651 try { |
|
1652 this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY); |
|
1653 } catch (e) { } |
|
1654 |
|
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; |
|
1660 |
|
1661 var self = this; |
|
1662 this.node.addEventListener("select", function node_onSelected() { |
|
1663 self.maybeHideSearch(); |
|
1664 gViewController.loadView(self.node.selectedItem.value); |
|
1665 }, false); |
|
1666 |
|
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; |
|
1672 |
|
1673 if (gViewController.parseViewId(viewId).type == "search") { |
|
1674 viewId += encodeURIComponent(gHeader.searchQuery); |
|
1675 } |
|
1676 |
|
1677 gViewController.loadView(viewId); |
|
1678 } |
|
1679 }, false); |
|
1680 }, |
|
1681 |
|
1682 shutdown: function gCategories_shutdown() { |
|
1683 AddonManager.removeTypeListener(this); |
|
1684 }, |
|
1685 |
|
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; |
|
1690 |
|
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); |
|
1699 |
|
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 } |
|
1716 |
|
1717 this.node.insertBefore(category, node); |
|
1718 }, |
|
1719 |
|
1720 _removeCategory: function gCategories_removeCategory(aId) { |
|
1721 var category = document.getElementById("category-" + aId); |
|
1722 if (!category) |
|
1723 return; |
|
1724 |
|
1725 // If this category is currently selected then switch to the default view |
|
1726 if (this.node.selectedItem == category) |
|
1727 gViewController.replaceView(VIEW_DEFAULT); |
|
1728 |
|
1729 this.node.removeChild(category); |
|
1730 }, |
|
1731 |
|
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; |
|
1736 |
|
1737 var aViewId = "addons://" + aType.viewType + "/" + aType.id; |
|
1738 |
|
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 } |
|
1749 |
|
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); |
|
1755 |
|
1756 // Don't load view that is becoming hidden |
|
1757 if (hidden && aViewId == gViewController.currentViewId) |
|
1758 gViewController.loadView(VIEW_DEFAULT); |
|
1759 |
|
1760 item.hidden = hidden; |
|
1761 Services.prefs.setBoolPref(prefName, hidden); |
|
1762 |
|
1763 if (aAddonsList.length > 0 || aInstallsList.length > 0) { |
|
1764 notifyInitialized(); |
|
1765 return; |
|
1766 } |
|
1767 |
|
1768 gEventManager.registerInstallListener({ |
|
1769 onDownloadStarted: function gCategories_onDownloadStarted(aInstall) { |
|
1770 this._maybeShowCategory(aInstall); |
|
1771 }, |
|
1772 |
|
1773 onInstallStarted: function gCategories_onInstallStarted(aInstall) { |
|
1774 this._maybeShowCategory(aInstall); |
|
1775 }, |
|
1776 |
|
1777 onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) { |
|
1778 this._maybeShowCategory(aAddon); |
|
1779 }, |
|
1780 |
|
1781 onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { |
|
1782 this._maybeShowCategory(aAddon); |
|
1783 }, |
|
1784 |
|
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 }); |
|
1793 |
|
1794 notifyInitialized(); |
|
1795 }); |
|
1796 } |
|
1797 |
|
1798 this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority, |
|
1799 startHidden); |
|
1800 }, |
|
1801 |
|
1802 onTypeRemoved: function gCategories_onTypeRemoved(aType) { |
|
1803 this._removeCategory(aType.id); |
|
1804 }, |
|
1805 |
|
1806 get selected() { |
|
1807 return this.node.selectedItem ? this.node.selectedItem.value : null; |
|
1808 }, |
|
1809 |
|
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 } |
|
1816 |
|
1817 Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId); |
|
1818 |
|
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 } |
|
1825 |
|
1826 if (view.type == "search") |
|
1827 var item = this._search; |
|
1828 else |
|
1829 var item = this.get(aId); |
|
1830 |
|
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); |
|
1838 |
|
1839 this.maybeHideSearch(); |
|
1840 } |
|
1841 }, |
|
1842 |
|
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 }, |
|
1849 |
|
1850 setBadge: function gCategories_setBadge(aId, aCount) { |
|
1851 let item = this.get(aId); |
|
1852 if (item) |
|
1853 item.badgeCount = aCount; |
|
1854 }, |
|
1855 |
|
1856 maybeHideSearch: function gCategories_maybeHideSearch() { |
|
1857 var view = gViewController.parseViewId(this.node.selectedItem.value); |
|
1858 this._search.disabled = view.type != "search"; |
|
1859 } |
|
1860 }; |
|
1861 |
|
1862 |
|
1863 var gHeader = { |
|
1864 _search: null, |
|
1865 _dest: "", |
|
1866 |
|
1867 initialize: function gHeader_initialize() { |
|
1868 this._search = document.getElementById("header-search"); |
|
1869 |
|
1870 this._search.addEventListener("command", function search_onCommand(aEvent) { |
|
1871 var query = aEvent.target.value; |
|
1872 if (query.length == 0) |
|
1873 return; |
|
1874 |
|
1875 gViewController.loadView("addons://search/" + encodeURIComponent(query)); |
|
1876 }, false); |
|
1877 |
|
1878 function updateNavButtonVisibility() { |
|
1879 var shouldShow = gHeader.shouldShowNavButtons; |
|
1880 document.getElementById("back-btn").hidden = !shouldShow; |
|
1881 document.getElementById("forward-btn").hidden = !shouldShow; |
|
1882 } |
|
1883 |
|
1884 window.addEventListener("focus", function window_onFocus(aEvent) { |
|
1885 if (aEvent.target == window) |
|
1886 updateNavButtonVisibility(); |
|
1887 }, false); |
|
1888 |
|
1889 updateNavButtonVisibility(); |
|
1890 }, |
|
1891 |
|
1892 focusSearchBox: function gHeader_focusSearchBox() { |
|
1893 this._search.focus(); |
|
1894 }, |
|
1895 |
|
1896 onKeyPress: function gHeader_onKeyPress(aEvent) { |
|
1897 if (String.fromCharCode(aEvent.charCode) == "/") { |
|
1898 this.focusSearchBox(); |
|
1899 return; |
|
1900 } |
|
1901 |
|
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 }, |
|
1914 |
|
1915 get shouldShowNavButtons() { |
|
1916 var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
1917 .getInterface(Ci.nsIWebNavigation) |
|
1918 .QueryInterface(Ci.nsIDocShellTreeItem); |
|
1919 |
|
1920 // If there is no outer frame then make the buttons visible |
|
1921 if (docshellItem.rootTreeItem == docshellItem) |
|
1922 return true; |
|
1923 |
|
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; |
|
1931 |
|
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 } |
|
1942 |
|
1943 return false; |
|
1944 }, |
|
1945 |
|
1946 get searchQuery() { |
|
1947 return this._search.value; |
|
1948 }, |
|
1949 |
|
1950 set searchQuery(aQuery) { |
|
1951 this._search.value = aQuery; |
|
1952 }, |
|
1953 }; |
|
1954 |
|
1955 |
|
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: [], |
|
1967 |
|
1968 initialize: function gDiscoverView_initialize() { |
|
1969 this.enabled = isDiscoverEnabled(); |
|
1970 if (!this.enabled) { |
|
1971 gCategories.get("addons://discover/").hidden = true; |
|
1972 return; |
|
1973 } |
|
1974 |
|
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"); |
|
1979 |
|
1980 let compatMode = "normal"; |
|
1981 if (!AddonManager.checkCompatibility) |
|
1982 compatMode = "ignore"; |
|
1983 else if (AddonManager.strictCompatibility) |
|
1984 compatMode = "strict"; |
|
1985 |
|
1986 var url = Services.prefs.getCharPref(PREF_DISCOVERURL); |
|
1987 url = url.replace("%COMPATIBILITY_MODE%", compatMode); |
|
1988 url = Services.urlFormatter.formatURL(url); |
|
1989 |
|
1990 var self = this; |
|
1991 |
|
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 } |
|
2000 |
|
2001 self._browser.homePage = self.homepageURL.spec; |
|
2002 self._browser.addProgressListener(self); |
|
2003 |
|
2004 if (self.loaded) |
|
2005 self._loadURL(self.homepageURL.spec, false, notifyInitialized); |
|
2006 else |
|
2007 notifyInitialized(); |
|
2008 } |
|
2009 |
|
2010 if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) { |
|
2011 setURL(url); |
|
2012 return; |
|
2013 } |
|
2014 |
|
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 } |
|
2034 |
|
2035 setURL(url + "#" + JSON.stringify(list)); |
|
2036 }); |
|
2037 }, |
|
2038 |
|
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 }, |
|
2047 |
|
2048 show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) { |
|
2049 gViewController.updateCommands(); |
|
2050 |
|
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 } |
|
2056 |
|
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 } |
|
2065 |
|
2066 this.loaded = true; |
|
2067 |
|
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 } |
|
2074 |
|
2075 this._loadURL(this.homepageURL.spec, aIsRefresh, |
|
2076 gViewController.notifyViewChanged.bind(gViewController)); |
|
2077 }, |
|
2078 |
|
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 }, |
|
2085 |
|
2086 refresh: function gDiscoverView_refresh(aParam, aRequest, aState) { |
|
2087 this.show(aParam, aRequest, aState, true); |
|
2088 }, |
|
2089 |
|
2090 hide: function gDiscoverView_hide() { }, |
|
2091 |
|
2092 showError: function gDiscoverView_showError() { |
|
2093 this.node.selectedPanel = this._error; |
|
2094 }, |
|
2095 |
|
2096 _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) { |
|
2097 if (this._browser.currentURI.spec == aURL) { |
|
2098 if (aCallback) |
|
2099 aCallback(); |
|
2100 return; |
|
2101 } |
|
2102 |
|
2103 if (aCallback) |
|
2104 this._loadListeners.push(aCallback); |
|
2105 |
|
2106 var flags = 0; |
|
2107 if (!aKeepHistory) |
|
2108 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; |
|
2109 |
|
2110 this._browser.loadURIWithFlags(aURL, flags); |
|
2111 }, |
|
2112 |
|
2113 onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { |
|
2114 // Ignore the about:blank load |
|
2115 if (aLocation.spec == "about:blank") |
|
2116 return; |
|
2117 |
|
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); |
|
2123 |
|
2124 var state = { |
|
2125 view: "addons://discover/", |
|
2126 url: aLocation.spec |
|
2127 }; |
|
2128 |
|
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 } |
|
2136 |
|
2137 gViewController.updateCommands(); |
|
2138 |
|
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; |
|
2145 |
|
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 }, |
|
2150 |
|
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; |
|
2155 |
|
2156 // If the request was secure then it is ok |
|
2157 if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) |
|
2158 return; |
|
2159 |
|
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 }, |
|
2164 |
|
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; |
|
2172 |
|
2173 // Only care about the network events |
|
2174 if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK))) |
|
2175 return; |
|
2176 |
|
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; |
|
2180 |
|
2181 // Ignore anything except stop events |
|
2182 if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP))) |
|
2183 return; |
|
2184 |
|
2185 // Consider the successful load of about:blank as still loading |
|
2186 if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank") |
|
2187 return; |
|
2188 |
|
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 } |
|
2201 |
|
2202 var listeners = this._loadListeners; |
|
2203 this._loadListeners = []; |
|
2204 |
|
2205 for (let listener of listeners) |
|
2206 listener(); |
|
2207 }, |
|
2208 |
|
2209 onProgressChange: function gDiscoverView_onProgressChange() { }, |
|
2210 onStatusChange: function gDiscoverView_onStatusChange() { }, |
|
2211 |
|
2212 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, |
|
2213 Ci.nsISupportsWeakReference]), |
|
2214 |
|
2215 getSelectedAddon: function gDiscoverView_getSelectedAddon() null |
|
2216 }; |
|
2217 |
|
2218 |
|
2219 var gCachedAddons = {}; |
|
2220 |
|
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, |
|
2232 |
|
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"); |
|
2242 |
|
2243 if (!AddonManager.isInstallEnabled("application/x-xpinstall")) |
|
2244 this._filter.hidden = true; |
|
2245 |
|
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); |
|
2254 |
|
2255 this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false); |
|
2256 }, |
|
2257 |
|
2258 shutdown: function gSearchView_shutdown() { |
|
2259 if (AddonRepository.isSearching) |
|
2260 AddonRepository.cancelSearch(); |
|
2261 }, |
|
2262 |
|
2263 get isSearching() { |
|
2264 return this._pendingSearches > 0; |
|
2265 }, |
|
2266 |
|
2267 show: function gSearchView_show(aQuery, aRequest) { |
|
2268 gEventManager.registerInstallListener(this); |
|
2269 |
|
2270 this.showEmptyNotice(false); |
|
2271 this.showAllResultsLink(0); |
|
2272 this.showLoading(true); |
|
2273 this._sorters.showprice = false; |
|
2274 |
|
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; |
|
2283 |
|
2284 if (AddonRepository.isSearching) |
|
2285 AddonRepository.cancelSearch(); |
|
2286 |
|
2287 while (this._listBox.firstChild.localName == "richlistitem") |
|
2288 this._listBox.removeChild(this._listBox.firstChild); |
|
2289 |
|
2290 var self = this; |
|
2291 gCachedAddons = {}; |
|
2292 this._pendingSearches = 2; |
|
2293 this._sorters.setSort("relevancescore", false); |
|
2294 |
|
2295 var elements = []; |
|
2296 |
|
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 } |
|
2306 |
|
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 } |
|
2314 |
|
2315 elements.push(item); |
|
2316 } |
|
2317 } |
|
2318 |
|
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 } |
|
2326 |
|
2327 self._pendingSearches--; |
|
2328 self.updateView(); |
|
2329 |
|
2330 if (!self.isSearching) |
|
2331 gViewController.notifyViewChanged(); |
|
2332 } |
|
2333 |
|
2334 getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) { |
|
2335 if (gViewController && aRequest != gViewController.currentViewRequest) |
|
2336 return; |
|
2337 |
|
2338 createSearchResults(aAddons, false, false); |
|
2339 createSearchResults(aInstalls, true, false); |
|
2340 finishSearch(); |
|
2341 }); |
|
2342 |
|
2343 var maxRemoteResults = 0; |
|
2344 try { |
|
2345 maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS); |
|
2346 } catch(e) {} |
|
2347 |
|
2348 if (maxRemoteResults <= 0) { |
|
2349 finishSearch(0); |
|
2350 return; |
|
2351 } |
|
2352 |
|
2353 AddonRepository.searchAddons(aQuery, maxRemoteResults, { |
|
2354 searchFailed: function show_SearchFailed() { |
|
2355 if (gViewController && aRequest != gViewController.currentViewRequest) |
|
2356 return; |
|
2357 |
|
2358 self._lastRemoteTotal = 0; |
|
2359 |
|
2360 // XXXunf Better handling of AMO search failure. See bug 579502 |
|
2361 finishSearch(0); // Silently fail |
|
2362 }, |
|
2363 |
|
2364 searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) { |
|
2365 if (gViewController && aRequest != gViewController.currentViewRequest) |
|
2366 return; |
|
2367 |
|
2368 if (aTotalResults > maxRemoteResults) |
|
2369 self._lastRemoteTotal = aTotalResults; |
|
2370 else |
|
2371 self._lastRemoteTotal = 0; |
|
2372 |
|
2373 var createdCount = createSearchResults(aAddonsList, false, true); |
|
2374 finishSearch(createdCount); |
|
2375 } |
|
2376 }); |
|
2377 }, |
|
2378 |
|
2379 showLoading: function gSearchView_showLoading(aLoading) { |
|
2380 this._loading.hidden = !aLoading; |
|
2381 this._listBox.hidden = aLoading; |
|
2382 }, |
|
2383 |
|
2384 updateView: function gSearchView_updateView() { |
|
2385 var showLocal = this._filter.value == "local"; |
|
2386 |
|
2387 if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall")) |
|
2388 showLocal = true; |
|
2389 |
|
2390 this._listBox.setAttribute("local", showLocal); |
|
2391 this._listBox.setAttribute("remote", !showLocal); |
|
2392 |
|
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 } |
|
2404 |
|
2405 this.showEmptyNotice(isEmpty); |
|
2406 this.showAllResultsLink(this._lastRemoteTotal); |
|
2407 } |
|
2408 |
|
2409 gViewController.updateCommands(); |
|
2410 }, |
|
2411 |
|
2412 hide: function gSearchView_hide() { |
|
2413 gEventManager.unregisterInstallListener(this); |
|
2414 doPendingUninstalls(this._listBox); |
|
2415 }, |
|
2416 |
|
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 }, |
|
2425 |
|
2426 calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) { |
|
2427 var score = 0; |
|
2428 if (!aStr || aQuery.length == 0) |
|
2429 return score; |
|
2430 |
|
2431 aStr = aStr.trim().toLocaleLowerCase(); |
|
2432 var haystack = aStr.split(/\s+/); |
|
2433 var needles = aQuery.split(/\s+/); |
|
2434 |
|
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 } |
|
2449 |
|
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; |
|
2454 |
|
2455 return score * aMultiplier; |
|
2456 }, |
|
2457 |
|
2458 showEmptyNotice: function gSearchView_showEmptyNotice(aShow) { |
|
2459 this._emptyNotice.hidden = !aShow; |
|
2460 this._listBox.hidden = aShow; |
|
2461 }, |
|
2462 |
|
2463 showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) { |
|
2464 if (aTotalResults == 0) { |
|
2465 this._allResultsLink.hidden = true; |
|
2466 return; |
|
2467 } |
|
2468 |
|
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); |
|
2473 |
|
2474 this._allResultsLink.setAttribute("href", |
|
2475 AddonRepository.getSearchURL(this._lastQuery)); |
|
2476 this._allResultsLink.hidden = false; |
|
2477 }, |
|
2478 |
|
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 } |
|
2491 |
|
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 } |
|
2503 |
|
2504 }, |
|
2505 |
|
2506 onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) { |
|
2507 var footer = this._listBox.lastChild; |
|
2508 this._listBox.removeChild(footer); |
|
2509 |
|
2510 sortList(this._listBox, aSortBy, aAscending); |
|
2511 this.updateListAttributes(); |
|
2512 |
|
2513 this._listBox.appendChild(footer); |
|
2514 }, |
|
2515 |
|
2516 onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) { |
|
2517 this.removeInstall(aInstall); |
|
2518 }, |
|
2519 |
|
2520 onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) { |
|
2521 this.removeInstall(aInstall); |
|
2522 }, |
|
2523 |
|
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 }, |
|
2532 |
|
2533 getSelectedAddon: function gSearchView_getSelectedAddon() { |
|
2534 var item = this._listBox.selectedItem; |
|
2535 if (item) |
|
2536 return item.mAddon; |
|
2537 return null; |
|
2538 }, |
|
2539 |
|
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 }; |
|
2550 |
|
2551 |
|
2552 var gListView = { |
|
2553 node: null, |
|
2554 _listBox: null, |
|
2555 _emptyNotice: null, |
|
2556 _type: null, |
|
2557 |
|
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"); |
|
2562 |
|
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 }, |
|
2572 |
|
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); |
|
2576 |
|
2577 this._type = aType; |
|
2578 this.node.setAttribute("type", aType); |
|
2579 this.showEmptyNotice(false); |
|
2580 |
|
2581 while (this._listBox.itemCount > 0) |
|
2582 this._listBox.removeItemAt(0); |
|
2583 |
|
2584 var self = this; |
|
2585 getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) { |
|
2586 if (gViewController && aRequest != gViewController.currentViewRequest) |
|
2587 return; |
|
2588 |
|
2589 var elements = []; |
|
2590 |
|
2591 for (let addonItem of aAddonsList) |
|
2592 elements.push(createItem(addonItem)); |
|
2593 |
|
2594 for (let installItem of aInstallsList) |
|
2595 elements.push(createItem(installItem, true)); |
|
2596 |
|
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 } |
|
2603 |
|
2604 gEventManager.registerInstallListener(self); |
|
2605 gViewController.updateCommands(); |
|
2606 gViewController.notifyViewChanged(); |
|
2607 }); |
|
2608 }, |
|
2609 |
|
2610 hide: function gListView_hide() { |
|
2611 gEventManager.unregisterInstallListener(this); |
|
2612 doPendingUninstalls(this._listBox); |
|
2613 }, |
|
2614 |
|
2615 showEmptyNotice: function gListView_showEmptyNotice(aShow) { |
|
2616 this._emptyNotice.hidden = !aShow; |
|
2617 }, |
|
2618 |
|
2619 onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) { |
|
2620 sortList(this._listBox, aSortBy, aAscending); |
|
2621 }, |
|
2622 |
|
2623 onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { |
|
2624 // The existing list item will take care of upgrade installs |
|
2625 if (aExistingAddon) |
|
2626 return; |
|
2627 |
|
2628 this.addItem(aAddon); |
|
2629 }, |
|
2630 |
|
2631 onDownloadStarted: function gListView_onDownloadStarted(aInstall) { |
|
2632 this.addItem(aInstall, true); |
|
2633 }, |
|
2634 |
|
2635 onInstallStarted: function gListView_onInstallStarted(aInstall) { |
|
2636 this.addItem(aInstall, true); |
|
2637 }, |
|
2638 |
|
2639 onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) { |
|
2640 this.removeItem(aInstall, true); |
|
2641 }, |
|
2642 |
|
2643 onInstallCancelled: function gListView_onInstallCancelled(aInstall) { |
|
2644 this.removeItem(aInstall, true); |
|
2645 }, |
|
2646 |
|
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); |
|
2652 |
|
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 }, |
|
2660 |
|
2661 addItem: function gListView_addItem(aObj, aIsInstall) { |
|
2662 if (aObj.type != this._type) |
|
2663 return; |
|
2664 |
|
2665 if (aIsInstall && aObj.existingAddon) |
|
2666 return; |
|
2667 |
|
2668 let prop = aIsInstall ? "mInstall" : "mAddon"; |
|
2669 for (let item of this._listBox.childNodes) { |
|
2670 if (item[prop] == aObj) |
|
2671 return; |
|
2672 } |
|
2673 |
|
2674 let item = createItem(aObj, aIsInstall); |
|
2675 this._listBox.insertBefore(item, this._listBox.firstChild); |
|
2676 this.showEmptyNotice(false); |
|
2677 }, |
|
2678 |
|
2679 removeItem: function gListView_removeItem(aObj, aIsInstall) { |
|
2680 let prop = aIsInstall ? "mInstall" : "mAddon"; |
|
2681 |
|
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 }, |
|
2690 |
|
2691 getSelectedAddon: function gListView_getSelectedAddon() { |
|
2692 var item = this._listBox.selectedItem; |
|
2693 if (item) |
|
2694 return item.mAddon; |
|
2695 return null; |
|
2696 }, |
|
2697 |
|
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 }; |
|
2708 |
|
2709 |
|
2710 var gDetailView = { |
|
2711 node: null, |
|
2712 _addon: null, |
|
2713 _loadingTimer: null, |
|
2714 _autoUpdate: null, |
|
2715 |
|
2716 initialize: function gDetailView_initialize() { |
|
2717 this.node = document.getElementById("detail-view"); |
|
2718 |
|
2719 this._autoUpdate = document.getElementById("detail-autoUpdate"); |
|
2720 |
|
2721 var self = this; |
|
2722 this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() { |
|
2723 self._addon.applyBackgroundUpdates = self._autoUpdate.value; |
|
2724 }, true); |
|
2725 }, |
|
2726 |
|
2727 shutdown: function gDetailView_shutdown() { |
|
2728 AddonManager.removeManagerListener(this); |
|
2729 }, |
|
2730 |
|
2731 onUpdateModeChanged: function gDetailView_onUpdateModeChanged() { |
|
2732 this.onPropertyChanged(["applyBackgroundUpdates"]); |
|
2733 }, |
|
2734 |
|
2735 _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) { |
|
2736 AddonManager.addManagerListener(this); |
|
2737 this.clearLoading(); |
|
2738 |
|
2739 this._addon = aAddon; |
|
2740 gEventManager.registerAddonListener(this, aAddon.id); |
|
2741 gEventManager.registerInstallListener(this); |
|
2742 |
|
2743 this.node.setAttribute("type", aAddon.type); |
|
2744 |
|
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); |
|
2749 |
|
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); |
|
2754 |
|
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 } |
|
2762 |
|
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 } |
|
2779 |
|
2780 var desc = document.getElementById("detail-desc"); |
|
2781 desc.textContent = aAddon.description; |
|
2782 |
|
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 } |
|
2790 |
|
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 } |
|
2806 |
|
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 } |
|
2814 |
|
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 } |
|
2822 |
|
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 } |
|
2841 |
|
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 } |
|
2849 |
|
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 } |
|
2861 |
|
2862 document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL; |
|
2863 |
|
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 } |
|
2873 |
|
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 } |
|
2881 |
|
2882 var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID; |
|
2883 document.getElementById("detail-updates-row").hidden = !canUpdate; |
|
2884 |
|
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 } |
|
2894 |
|
2895 document.getElementById("detail-prefs-btn").hidden = !aIsRemote && |
|
2896 !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon); |
|
2897 |
|
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 } |
|
2908 |
|
2909 if (this._addon.type == "experiment") { |
|
2910 let prefix = "details.experiment."; |
|
2911 let active = this._addon.isActive; |
|
2912 |
|
2913 let stateKey = prefix + "state." + (active ? "active" : "complete"); |
|
2914 let node = document.getElementById("detail-experiment-state"); |
|
2915 node.value = gStrings.ext.GetStringFromName(stateKey); |
|
2916 |
|
2917 let now = Date.now(); |
|
2918 let end = getExperimentEndDate(this._addon); |
|
2919 let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); |
|
2920 |
|
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 } |
|
2933 |
|
2934 document.getElementById("detail-experiment-time").value = timeMessage; |
|
2935 } |
|
2936 |
|
2937 this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() { |
|
2938 this.updateState(); |
|
2939 gViewController.notifyViewChanged(); |
|
2940 }).bind(this)); |
|
2941 }, |
|
2942 |
|
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 } |
|
2950 |
|
2951 var self = this; |
|
2952 this._loadingTimer = setTimeout(function loadTimeOutTimer() { |
|
2953 self.node.setAttribute("loading-extended", true); |
|
2954 }, LOADING_MSG_DELAY); |
|
2955 |
|
2956 var view = gViewController.currentViewId; |
|
2957 |
|
2958 AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) { |
|
2959 if (gViewController && aRequest != gViewController.currentViewRequest) |
|
2960 return; |
|
2961 |
|
2962 if (aAddon) { |
|
2963 self._updateView(aAddon, false, scrollToPreferences); |
|
2964 return; |
|
2965 } |
|
2966 |
|
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 } |
|
2976 |
|
2977 if (aAddonId in gCachedAddons) { |
|
2978 self._updateView(gCachedAddons[aAddonId], true); |
|
2979 return; |
|
2980 } |
|
2981 |
|
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 }, |
|
2989 |
|
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 } |
|
2999 |
|
3000 gEventManager.unregisterAddonListener(this, this._addon.id); |
|
3001 gEventManager.unregisterInstallListener(this); |
|
3002 this._addon = null; |
|
3003 |
|
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 }, |
|
3009 |
|
3010 updateState: function gDetailView_updateState() { |
|
3011 gViewController.updateCommands(); |
|
3012 |
|
3013 var pending = this._addon.pendingOperations; |
|
3014 if (pending != AddonManager.PENDING_NONE) { |
|
3015 this.node.removeAttribute("notification"); |
|
3016 |
|
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 } |
|
3024 |
|
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"); |
|
3032 |
|
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 } |
|
3095 |
|
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 } |
|
3116 |
|
3117 this.node.setAttribute("active", this._addon.isActive); |
|
3118 }, |
|
3119 |
|
3120 clearLoading: function gDetailView_clearLoading() { |
|
3121 if (this._loadingTimer) { |
|
3122 clearTimeout(this._loadingTimer); |
|
3123 this._loadingTimer = null; |
|
3124 } |
|
3125 |
|
3126 this.node.removeAttribute("loading-extended"); |
|
3127 }, |
|
3128 |
|
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 }, |
|
3135 |
|
3136 fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) { |
|
3137 this.emptySettingsRows(); |
|
3138 if (!hasInlineOptions(this._addon)) { |
|
3139 if (aCallback) |
|
3140 aCallback(); |
|
3141 return; |
|
3142 } |
|
3143 |
|
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 } |
|
3159 |
|
3160 var rows = document.getElementById("detail-downloads").parentNode; |
|
3161 |
|
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"); |
|
3169 |
|
3170 var firstSetting = null; |
|
3171 for (var setting of settings) { |
|
3172 |
|
3173 var desc = stripTextNodes(setting).trim(); |
|
3174 if (!setting.hasAttribute("desc")) |
|
3175 setting.setAttribute("desc", desc); |
|
3176 |
|
3177 var type = setting.getAttribute("type"); |
|
3178 if (type == "file" || type == "directory") |
|
3179 setting.setAttribute("fullpath", "true"); |
|
3180 |
|
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 } |
|
3187 |
|
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 } |
|
3195 |
|
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 }, |
|
3234 |
|
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")); |
|
3242 |
|
3243 let detailViewBoxObject = gDetailView.node.boxObject; |
|
3244 top -= detailViewBoxObject.y; |
|
3245 |
|
3246 detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject); |
|
3247 detailViewBoxObject.scrollTo(0, top); |
|
3248 } |
|
3249 }, |
|
3250 |
|
3251 getSelectedAddon: function gDetailView_getSelectedAddon() { |
|
3252 return this._addon; |
|
3253 }, |
|
3254 |
|
3255 onEnabling: function gDetailView_onEnabling() { |
|
3256 this.updateState(); |
|
3257 }, |
|
3258 |
|
3259 onEnabled: function gDetailView_onEnabled() { |
|
3260 this.updateState(); |
|
3261 this.fillSettingsRows(); |
|
3262 }, |
|
3263 |
|
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 }, |
|
3272 |
|
3273 onDisabled: function gDetailView_onDisabled() { |
|
3274 this.updateState(); |
|
3275 this.emptySettingsRows(); |
|
3276 }, |
|
3277 |
|
3278 onUninstalling: function gDetailView_onUninstalling() { |
|
3279 this.updateState(); |
|
3280 }, |
|
3281 |
|
3282 onUninstalled: function gDetailView_onUninstalled() { |
|
3283 gViewController.popState(); |
|
3284 }, |
|
3285 |
|
3286 onOperationCancelled: function gDetailView_onOperationCancelled() { |
|
3287 this.updateState(); |
|
3288 }, |
|
3289 |
|
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 } |
|
3296 |
|
3297 if (aProperties.indexOf("appDisabled") != -1 || |
|
3298 aProperties.indexOf("userDisabled") != -1) |
|
3299 this.updateState(); |
|
3300 }, |
|
3301 |
|
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; |
|
3306 |
|
3307 if (!aNeedsRestart) |
|
3308 this._updateView(aAddon, false); |
|
3309 else |
|
3310 this.updateState(); |
|
3311 }, |
|
3312 |
|
3313 onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) { |
|
3314 if (aInstall.addon.id == this._addon.id) |
|
3315 gViewController.popState(); |
|
3316 } |
|
3317 }; |
|
3318 |
|
3319 |
|
3320 var gUpdatesView = { |
|
3321 node: null, |
|
3322 _listBox: null, |
|
3323 _emptyNotice: null, |
|
3324 _sorters: null, |
|
3325 _updateSelected: null, |
|
3326 _categoryItem: null, |
|
3327 |
|
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; |
|
3334 |
|
3335 this._categoryItem = gCategories.get("addons://updates/available"); |
|
3336 |
|
3337 this._updateSelected = document.getElementById("update-selected-btn"); |
|
3338 this._updateSelected.addEventListener("command", function updateSelected_onCommand() { |
|
3339 gUpdatesView.installSelected(); |
|
3340 }, false); |
|
3341 |
|
3342 this.updateAvailableCount(true); |
|
3343 |
|
3344 AddonManager.addAddonListener(this); |
|
3345 AddonManager.addInstallListener(this); |
|
3346 }, |
|
3347 |
|
3348 shutdown: function gUpdatesView_shutdown() { |
|
3349 AddonManager.removeAddonListener(this); |
|
3350 AddonManager.removeInstallListener(this); |
|
3351 }, |
|
3352 |
|
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); |
|
3357 |
|
3358 while (this._listBox.itemCount > 0) |
|
3359 this._listBox.removeItemAt(0); |
|
3360 |
|
3361 this.node.setAttribute("updatetype", aType); |
|
3362 if (aType == "recent") |
|
3363 this._showRecentUpdates(aRequest); |
|
3364 else |
|
3365 this._showAvailableUpdates(false, aRequest); |
|
3366 }, |
|
3367 |
|
3368 hide: function gUpdatesView_hide() { |
|
3369 this._updateSelected.hidden = true; |
|
3370 this._categoryItem.disabled = this._categoryItem.badgeCount == 0; |
|
3371 doPendingUninstalls(this._listBox); |
|
3372 }, |
|
3373 |
|
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; |
|
3379 |
|
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; |
|
3385 |
|
3386 elements.push(createItem(addon)); |
|
3387 } |
|
3388 |
|
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 } |
|
3395 |
|
3396 gViewController.notifyViewChanged(); |
|
3397 }); |
|
3398 }, |
|
3399 |
|
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; |
|
3405 |
|
3406 var self = this; |
|
3407 AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) { |
|
3408 if (!aIsRefresh && gViewController && aRequest && |
|
3409 aRequest != gViewController.currentViewRequest) |
|
3410 return; |
|
3411 |
|
3412 if (aIsRefresh) { |
|
3413 self.showEmptyNotice(false); |
|
3414 self._updateSelected.hidden = true; |
|
3415 |
|
3416 while (self._listBox.itemCount > 0) |
|
3417 self._listBox.removeItemAt(0); |
|
3418 } |
|
3419 |
|
3420 var elements = []; |
|
3421 |
|
3422 for (let install of aInstallsList) { |
|
3423 if (!self.isManualUpdate(install)) |
|
3424 continue; |
|
3425 |
|
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 } |
|
3433 |
|
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 } |
|
3441 |
|
3442 // ensure badge count is in sync |
|
3443 self._categoryItem.badgeCount = self._listBox.itemCount; |
|
3444 |
|
3445 gViewController.notifyViewChanged(); |
|
3446 }); |
|
3447 }, |
|
3448 |
|
3449 showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) { |
|
3450 this._emptyNotice.hidden = !aShow; |
|
3451 }, |
|
3452 |
|
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 }, |
|
3460 |
|
3461 maybeRefresh: function gUpdatesView_maybeRefresh() { |
|
3462 if (gViewController.currentViewId == "addons://updates/available") |
|
3463 this._showAvailableUpdates(true); |
|
3464 this.updateAvailableCount(); |
|
3465 }, |
|
3466 |
|
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 }, |
|
3482 |
|
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 }, |
|
3492 |
|
3493 installSelected: function gUpdatesView_installSelected() { |
|
3494 for (let item of this._listBox.childNodes) { |
|
3495 if (item.includeUpdate) |
|
3496 item.upgrade(); |
|
3497 } |
|
3498 |
|
3499 this._updateSelected.disabled = true; |
|
3500 }, |
|
3501 |
|
3502 getSelectedAddon: function gUpdatesView_getSelectedAddon() { |
|
3503 var item = this._listBox.selectedItem; |
|
3504 if (item) |
|
3505 return item.mAddon; |
|
3506 return null; |
|
3507 }, |
|
3508 |
|
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 }, |
|
3518 |
|
3519 onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) { |
|
3520 sortList(this._listBox, aSortBy, aAscending); |
|
3521 }, |
|
3522 |
|
3523 onNewInstall: function gUpdatesView_onNewInstall(aInstall) { |
|
3524 if (!this.isManualUpdate(aInstall)) |
|
3525 return; |
|
3526 this.maybeRefresh(); |
|
3527 }, |
|
3528 |
|
3529 onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) { |
|
3530 this.updateAvailableCount(); |
|
3531 }, |
|
3532 |
|
3533 onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) { |
|
3534 if (!this.isManualUpdate(aInstall)) |
|
3535 return; |
|
3536 this.maybeRefresh(); |
|
3537 }, |
|
3538 |
|
3539 onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) { |
|
3540 if (aProperties.indexOf("applyBackgroundUpdates") != -1) |
|
3541 this.updateAvailableCount(); |
|
3542 } |
|
3543 }; |
|
3544 |
|
3545 function debuggingPrefChanged() { |
|
3546 gViewController.updateState(); |
|
3547 gViewController.updateCommands(); |
|
3548 gViewController.notifyViewChanged(); |
|
3549 } |
|
3550 |
|
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 }, |
|
3559 |
|
3560 onDrop: function gDragDrop_onDrop(aEvent) { |
|
3561 var dataTransfer = aEvent.dataTransfer; |
|
3562 var urls = []; |
|
3563 |
|
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 } |
|
3571 |
|
3572 url = dataTransfer.mozGetDataAt("text/x-moz-url", i); |
|
3573 if (url) { |
|
3574 urls.push(url.split("\n")[0]); |
|
3575 continue; |
|
3576 } |
|
3577 |
|
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 } |
|
3584 |
|
3585 var pos = 0; |
|
3586 var installs = []; |
|
3587 |
|
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 } |
|
3597 |
|
3598 AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) { |
|
3599 installs.push(aInstall); |
|
3600 buildNextInstall(); |
|
3601 }, "application/x-xpinstall"); |
|
3602 } |
|
3603 |
|
3604 buildNextInstall(); |
|
3605 |
|
3606 aEvent.preventDefault(); |
|
3607 } |
|
3608 }; |