Downloads.jsm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  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. "use strict";
  5. const EXPORTED_SYMBOLS = ["S4EDownloadService"];
  6. const CC = Components.classes;
  7. const CI = Components.interfaces;
  8. const CU = Components.utils;
  9. CU.import("resource://gre/modules/Services.jsm");
  10. CU.import("resource://gre/modules/PluralForm.jsm");
  11. CU.import("resource://gre/modules/DownloadUtils.jsm");
  12. CU.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
  13. CU.import("resource://gre/modules/XPCOMUtils.jsm");
  14. function S4EDownloadService(window, gBrowser, service, getters)
  15. {
  16. this._window = window;
  17. this._gBrowser = gBrowser;
  18. this._service = service;
  19. this._getters = getters;
  20. this._handler = new JSTransferHandler(this._window, this);
  21. }
  22. S4EDownloadService.prototype =
  23. {
  24. _window: null,
  25. _gBrowser: null,
  26. _service: null,
  27. _getters: null,
  28. _handler: null,
  29. _listening: false,
  30. _binding: false,
  31. _customizing: false,
  32. _lastTime: Infinity,
  33. _dlActive: false,
  34. _dlPaused: false,
  35. _dlFinished: false,
  36. _dlCountStr: null,
  37. _dlTimeStr: null,
  38. _dlProgressAvg: 0,
  39. _dlProgressMax: 0,
  40. _dlProgressMin: 0,
  41. _dlProgressType: "active",
  42. _dlNotifyTimer: 0,
  43. _dlNotifyGlowTimer: 0,
  44. init: function()
  45. {
  46. if(!this._getters.downloadButton)
  47. {
  48. this.uninit();
  49. return;
  50. }
  51. if(this._listening)
  52. {
  53. return;
  54. }
  55. this._handler.start();
  56. this._listening = true;
  57. this._lastTime = Infinity;
  58. this.updateBinding();
  59. this.updateStatus();
  60. },
  61. uninit: function()
  62. {
  63. if(!this._listening)
  64. {
  65. return;
  66. }
  67. this._listening = false;
  68. this._handler.stop();
  69. this.releaseBinding();
  70. },
  71. destroy: function()
  72. {
  73. this.uninit();
  74. this._handler.destroy();
  75. ["_window", "_gBrowser", "_service", "_getters", "_handler"].forEach(function(prop)
  76. {
  77. delete this[prop];
  78. }, this);
  79. },
  80. updateBinding: function()
  81. {
  82. if(!this._listening)
  83. {
  84. this.releaseBinding();
  85. return;
  86. }
  87. switch(this._service.downloadButtonAction)
  88. {
  89. case 1: // Default
  90. this.attachBinding();
  91. break;
  92. default:
  93. this.releaseBinding();
  94. break;
  95. }
  96. },
  97. attachBinding: function()
  98. {
  99. if(this._binding)
  100. {
  101. return;
  102. }
  103. let db = this._window.DownloadsButton;
  104. db._getAnchorS4EBackup = db.getAnchor;
  105. db.getAnchor = this.getAnchor.bind(this);
  106. db._releaseAnchorS4EBackup = db.releaseAnchor;
  107. db.releaseAnchor = function() {};
  108. this._binding = true;
  109. },
  110. releaseBinding: function()
  111. {
  112. if(!this._binding)
  113. {
  114. return;
  115. }
  116. let db = this._window.DownloadsButton;
  117. db.getAnchor = db._getAnchorS4EBackup;
  118. db.releaseAnchor = db._releaseAnchorS4EBackup;
  119. this._binding = false;
  120. },
  121. customizing: function(val)
  122. {
  123. this._customizing = val;
  124. },
  125. updateStatus: function(lastFinished)
  126. {
  127. if(!this._getters.downloadButton)
  128. {
  129. this.uninit();
  130. return;
  131. }
  132. let numActive = 0;
  133. let numPaused = 0;
  134. let activeTotalSize = 0;
  135. let activeTransferred = 0;
  136. let activeMaxProgress = -Infinity;
  137. let activeMinProgress = Infinity;
  138. let pausedTotalSize = 0;
  139. let pausedTransferred = 0;
  140. let pausedMaxProgress = -Infinity;
  141. let pausedMinProgress = Infinity;
  142. let maxTime = -Infinity;
  143. let dls = ((this.isPrivateWindow) ? this._handler.activePrivateEntries() : this._handler.activeEntries());
  144. for(let dl of dls)
  145. {
  146. if(dl.state == CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING)
  147. {
  148. numActive++;
  149. if(dl.size > 0)
  150. {
  151. if(dl.speed > 0)
  152. {
  153. maxTime = Math.max(maxTime, (dl.size - dl.transferred) / dl.speed);
  154. }
  155. activeTotalSize += dl.size;
  156. activeTransferred += dl.transferred;
  157. let currentProgress = ((dl.transferred * 100) / dl.size);
  158. activeMaxProgress = Math.max(activeMaxProgress, currentProgress);
  159. activeMinProgress = Math.min(activeMinProgress, currentProgress);
  160. }
  161. }
  162. else if(dl.state == CI.nsIDownloadManager.DOWNLOAD_PAUSED)
  163. {
  164. numPaused++;
  165. if(dl.size > 0)
  166. {
  167. pausedTotalSize += dl.size;
  168. pausedTransferred += dl.transferred;
  169. let currentProgress = ((dl.transferred * 100) / dl.size);
  170. pausedMaxProgress = Math.max(pausedMaxProgress, currentProgress);
  171. pausedMinProgress = Math.min(pausedMinProgress, currentProgress);
  172. }
  173. }
  174. }
  175. if((numActive + numPaused) == 0)
  176. {
  177. this._dlActive = false;
  178. this._dlFinished = lastFinished;
  179. this.updateButton();
  180. this._lastTime = Infinity;
  181. return;
  182. }
  183. let dlPaused = (numActive == 0);
  184. let dlStatus = ((dlPaused) ? this._getters.strings.getString("pausedDownloads")
  185. : this._getters.strings.getString("activeDownloads"));
  186. let dlCount = ((dlPaused) ? numPaused : numActive);
  187. let dlTotalSize = ((dlPaused) ? pausedTotalSize : activeTotalSize);
  188. let dlTransferred = ((dlPaused) ? pausedTransferred : activeTransferred);
  189. let dlMaxProgress = ((dlPaused) ? pausedMaxProgress : activeMaxProgress);
  190. let dlMinProgress = ((dlPaused) ? pausedMinProgress : activeMinProgress);
  191. let dlProgressType = ((dlPaused) ? "paused" : "active");
  192. [this._dlTimeStr, this._lastTime] = DownloadUtils.getTimeLeft(maxTime, this._lastTime);
  193. this._dlCountStr = PluralForm.get(dlCount, dlStatus).replace("#1", dlCount);
  194. this._dlProgressAvg = ((dlTotalSize == 0) ? 100 : ((dlTransferred * 100) / dlTotalSize));
  195. this._dlProgressMax = ((dlTotalSize == 0) ? 100 : dlMaxProgress);
  196. this._dlProgressMin = ((dlTotalSize == 0) ? 100 : dlMinProgress);
  197. this._dlProgressType = dlProgressType + ((dlTotalSize == 0) ? "-unknown" : "");
  198. this._dlPaused = dlPaused;
  199. this._dlActive = true;
  200. this._dlFinished = false;
  201. this.updateButton();
  202. },
  203. updateButton: function()
  204. {
  205. let download_button = this._getters.downloadButton;
  206. let download_tooltip = this._getters.downloadButtonTooltip;
  207. let download_progress = this._getters.downloadButtonProgress;
  208. let download_label = this._getters.downloadButtonLabel;
  209. if(!download_button)
  210. {
  211. return;
  212. }
  213. if(!this._dlActive)
  214. {
  215. download_button.collapsed = true;
  216. download_label.value = download_tooltip.label = this._getters.strings.getString("noDownloads");
  217. download_progress.collapsed = true;
  218. download_progress.value = 0;
  219. if(this._dlFinished && this._handler.hasPBAPI && !this.isUIShowing)
  220. {
  221. this.callAttention(download_button);
  222. }
  223. return;
  224. }
  225. switch(this._service.downloadProgress)
  226. {
  227. case 2:
  228. download_progress.value = this._dlProgressMax;
  229. break;
  230. case 3:
  231. download_progress.value = this._dlProgressMin;
  232. break;
  233. default:
  234. download_progress.value = this._dlProgressAvg;
  235. break;
  236. }
  237. download_progress.setAttribute("pmType", this._dlProgressType);
  238. download_progress.collapsed = (this._service.downloadProgress == 0);
  239. download_label.value = this.buildString(this._service.downloadLabel);
  240. download_tooltip.label = this.buildString(this._service.downloadTooltip);
  241. this.clearAttention(download_button);
  242. download_button.collapsed = false;
  243. },
  244. callAttention: function(download_button)
  245. {
  246. if(this._dlNotifyGlowTimer != 0)
  247. {
  248. this._window.clearTimeout(this._dlNotifyGlowTimer);
  249. this._dlNotifyGlowTimer = 0;
  250. }
  251. download_button.setAttribute("attention", "true");
  252. if(this._service.downloadNotifyTimeout)
  253. {
  254. this._dlNotifyGlowTimer = this._window.setTimeout(function(self, button)
  255. {
  256. self._dlNotifyGlowTimer = 0;
  257. button.removeAttribute("attention");
  258. }, this._service.downloadNotifyTimeout, this, download_button);
  259. }
  260. },
  261. clearAttention: function(download_button)
  262. {
  263. if(this._dlNotifyGlowTimer != 0)
  264. {
  265. this._window.clearTimeout(this._dlNotifyGlowTimer);
  266. this._dlNotifyGlowTimer = 0;
  267. }
  268. download_button.removeAttribute("attention");
  269. },
  270. notify: function()
  271. {
  272. if(this._dlNotifyTimer == 0 && this._service.downloadNotifyAnimate)
  273. {
  274. let download_button_anchor = this._getters.downloadButtonAnchor;
  275. let download_notify_anchor = this._getters.downloadNotifyAnchor;
  276. if(download_button_anchor)
  277. {
  278. if(!download_notify_anchor.style.transform)
  279. {
  280. let bAnchorRect = download_button_anchor.getBoundingClientRect();
  281. let nAnchorRect = download_notify_anchor.getBoundingClientRect();
  282. let translateX = bAnchorRect.left - nAnchorRect.left;
  283. translateX += .5 * (bAnchorRect.width - nAnchorRect.width);
  284. let translateY = bAnchorRect.top - nAnchorRect.top;
  285. translateY += .5 * (bAnchorRect.height - nAnchorRect.height);
  286. download_notify_anchor.style.transform = "translate(" + translateX + "px, " + translateY + "px)";
  287. }
  288. download_notify_anchor.setAttribute("notification", "finish");
  289. this._dlNotifyTimer = this._window.setTimeout(function(self, anchor)
  290. {
  291. self._dlNotifyTimer = 0;
  292. anchor.removeAttribute("notification");
  293. anchor.style.transform = "";
  294. }, 1000, this, download_notify_anchor);
  295. }
  296. }
  297. },
  298. clearFinished: function()
  299. {
  300. this._dlFinished = false;
  301. let download_button = this._getters.downloadButton;
  302. if(download_button)
  303. {
  304. this.clearAttention(download_button);
  305. }
  306. },
  307. getAnchor: function(aCallback)
  308. {
  309. if(this._customizing)
  310. {
  311. aCallback(null);
  312. return;
  313. }
  314. aCallback(this._getters.downloadButtonAnchor);
  315. },
  316. openUI: function(aEvent)
  317. {
  318. this.clearFinished();
  319. switch(this._service.downloadButtonAction)
  320. {
  321. case 1: // Firefox Default
  322. this._handler.openUINative();
  323. break;
  324. case 2: // Show Library
  325. this._window.PlacesCommandHook.showPlacesOrganizer("Downloads");
  326. break;
  327. case 3: // Show Tab
  328. let found = this._gBrowser.browsers.some(function(browser, index)
  329. {
  330. if("about:downloads" == browser.currentURI.spec)
  331. {
  332. this._gBrowser.selectedTab = this._gBrowser.tabContainer.childNodes[index];
  333. return true;
  334. }
  335. }, this);
  336. if(!found)
  337. {
  338. this._window.openUILinkIn("about:downloads", "tab");
  339. }
  340. break;
  341. case 4: // External Command
  342. let command = this._service.downloadButtonActionCommand;
  343. if(commend)
  344. {
  345. this._window.goDoCommand(command);
  346. }
  347. break;
  348. default: // Nothing
  349. break;
  350. }
  351. aEvent.stopPropagation();
  352. },
  353. get isPrivateWindow()
  354. {
  355. return this._handler.hasPBAPI && PrivateBrowsingUtils.isWindowPrivate(this._window);
  356. },
  357. get isUIShowing()
  358. {
  359. switch(this._service.downloadButtonAction)
  360. {
  361. case 1: // Firefox Default
  362. return this._handler.isUIShowingNative;
  363. case 2: // Show Library
  364. var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
  365. if(organizer)
  366. {
  367. let selectedNode = organizer.PlacesOrganizer._places.selectedNode;
  368. let downloadsItemId = organizer.PlacesUIUtils.leftPaneQueries["Downloads"];
  369. return selectedNode && selectedNode.itemId === downloadsItemId;
  370. }
  371. return false;
  372. case 3: // Show tab
  373. let currentURI = this._gBrowser.currentURI;
  374. return currentURI && currentURI.spec == "about:downloads";
  375. default: // Nothing
  376. return false;
  377. }
  378. },
  379. buildString: function(mode)
  380. {
  381. switch(mode)
  382. {
  383. case 0:
  384. return this._dlCountStr;
  385. case 1:
  386. return ((this._dlPaused) ? this._dlCountStr : this._dlTimeStr);
  387. default:
  388. let compStr = this._dlCountStr;
  389. if(!this._dlPaused)
  390. {
  391. compStr += " (" + this._dlTimeStr + ")";
  392. }
  393. return compStr;
  394. }
  395. }
  396. };
  397. function JSTransferHandler(window, downloadService)
  398. {
  399. this._window = window;
  400. let api = CU.import("resource://gre/modules/Downloads.jsm", {}).Downloads;
  401. this._activePublic = new JSTransferListener(downloadService, api.getList(api.PUBLIC), false);
  402. this._activePrivate = new JSTransferListener(downloadService, api.getList(api.PRIVATE), true);
  403. }
  404. JSTransferHandler.prototype =
  405. {
  406. _window: null,
  407. _activePublic: null,
  408. _activePrivate: null,
  409. destroy: function()
  410. {
  411. this._activePublic.destroy();
  412. this._activePrivate.destroy();
  413. ["_window", "_activePublic", "_activePrivate"].forEach(function(prop)
  414. {
  415. delete this[prop];
  416. }, this);
  417. },
  418. start: function()
  419. {
  420. this._activePublic.start();
  421. this._activePrivate.start();
  422. },
  423. stop: function()
  424. {
  425. this._activePublic.stop();
  426. this._activePrivate.stop();
  427. },
  428. get hasPBAPI()
  429. {
  430. return true;
  431. },
  432. openUINative: function()
  433. {
  434. this._window.DownloadsPanel.showPanel();
  435. },
  436. get isUIShowingNative()
  437. {
  438. return this._window.DownloadsPanel.isPanelShowing;
  439. },
  440. activeEntries: function()
  441. {
  442. return this._activePublic.downloads();
  443. },
  444. activePrivateEntries: function()
  445. {
  446. return this._activePrivate.downloads();
  447. }
  448. };
  449. function JSTransferListener(downloadService, listPromise, isPrivate)
  450. {
  451. this._downloadService = downloadService;
  452. this._isPrivate = isPrivate;
  453. this._downloads = new Map();
  454. listPromise.then(this.initList.bind(this)).then(null, CU.reportError);
  455. }
  456. JSTransferListener.prototype =
  457. {
  458. _downloadService: null,
  459. _list: null,
  460. _downloads: null,
  461. _isPrivate: false,
  462. _wantsStart: false,
  463. initList: function(list)
  464. {
  465. this._list = list;
  466. if(this._wantsStart) {
  467. this.start();
  468. }
  469. this._list.getAll().then(this.initDownloads.bind(this)).then(null, CU.reportError);
  470. },
  471. initDownloads: function(downloads)
  472. {
  473. downloads.forEach(function(download)
  474. {
  475. this.onDownloadAdded(download);
  476. }, this);
  477. },
  478. destroy: function()
  479. {
  480. this._downloads.clear();
  481. ["_downloadService", "_list", "_downloads"].forEach(function(prop)
  482. {
  483. delete this[prop];
  484. }, this);
  485. },
  486. start: function()
  487. {
  488. if(!this._list)
  489. {
  490. this._wantsStart = true;
  491. return;
  492. }
  493. this._list.addView(this);
  494. },
  495. stop: function()
  496. {
  497. if(!this._list)
  498. {
  499. this._wantsStart = false;
  500. return;
  501. }
  502. this._list.removeView(this);
  503. },
  504. downloads: function()
  505. {
  506. return this._downloads.values();
  507. },
  508. convertToState: function(dl)
  509. {
  510. if(dl.succeeded)
  511. {
  512. return CI.nsIDownloadManager.DOWNLOAD_FINISHED;
  513. }
  514. if(dl.error && dl.error.becauseBlockedByParentalControls)
  515. {
  516. return CI.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL;
  517. }
  518. if(dl.error)
  519. {
  520. return CI.nsIDownloadManager.DOWNLOAD_FAILED;
  521. }
  522. if(dl.canceled && dl.hasPartialData)
  523. {
  524. return CI.nsIDownloadManager.DOWNLOAD_PAUSED;
  525. }
  526. if(dl.canceled)
  527. {
  528. return CI.nsIDownloadManager.DOWNLOAD_CANCELED;
  529. }
  530. if(dl.stopped)
  531. {
  532. return CI.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
  533. }
  534. return CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
  535. },
  536. onDownloadAdded: function(aDownload)
  537. {
  538. let dl = this._downloads.get(aDownload);
  539. if(!dl)
  540. {
  541. dl = {};
  542. this._downloads.set(aDownload, dl);
  543. }
  544. dl.state = this.convertToState(aDownload);
  545. dl.size = aDownload.totalBytes;
  546. dl.speed = aDownload.speed;
  547. dl.transferred = aDownload.currentBytes;
  548. },
  549. onDownloadChanged: function(aDownload)
  550. {
  551. this.onDownloadAdded(aDownload);
  552. if(this._isPrivate != this._downloadService.isPrivateWindow)
  553. {
  554. return;
  555. }
  556. this._downloadService.updateStatus(aDownload.succeeded);
  557. if(aDownload.succeeded)
  558. {
  559. this._downloadService.notify()
  560. }
  561. },
  562. onDownloadRemoved: function(aDownload)
  563. {
  564. this._downloads.delete(aDownload);
  565. }
  566. };