forked from mirrors/gecko-dev
		
	***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8
This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:
  ChromeUtils.import("resource://gre/modules/Services.jsm");
is approximately the same as the following, in the new model:
  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs
This was done using the followng script:
https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8
Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D16750
--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
		
	
			
		
			
				
	
	
		
			1483 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1483 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set ts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
/* eslint-env mozilla/browser-window */
 | 
						|
 | 
						|
/**
 | 
						|
 * Handles the Downloads panel user interface for each browser window.
 | 
						|
 *
 | 
						|
 * This file includes the following constructors and global objects:
 | 
						|
 *
 | 
						|
 * DownloadsPanel
 | 
						|
 * Main entry point for the downloads panel interface.
 | 
						|
 *
 | 
						|
 * DownloadsView
 | 
						|
 * Builds and updates the downloads list widget, responding to changes in the
 | 
						|
 * download state and real-time data.  In addition, handles part of the user
 | 
						|
 * interaction events raised by the downloads list widget.
 | 
						|
 *
 | 
						|
 * DownloadsViewItem
 | 
						|
 * Builds and updates a single item in the downloads list widget, responding to
 | 
						|
 * changes in the download state and real-time data, and handles the user
 | 
						|
 * interaction events related to a single item in the downloads list widgets.
 | 
						|
 *
 | 
						|
 * DownloadsViewController
 | 
						|
 * Handles part of the user interaction events raised by the downloads list
 | 
						|
 * widget, in particular the "commands" that apply to multiple items, and
 | 
						|
 * dispatches the commands that apply to individual items.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * A few words on focus and focusrings
 | 
						|
 *
 | 
						|
 * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we
 | 
						|
 * basically suppress most if not all XUL-level focusrings, and style/draw
 | 
						|
 * them ourselves (using :focus instead of -moz-focusring). There are a few
 | 
						|
 * reasons for this:
 | 
						|
 *
 | 
						|
 * 1) Richlists on OSX don't have focusrings; instead, they are shown as
 | 
						|
 *    selected. This makes for some ambiguity when we have a focused/selected
 | 
						|
 *    item in the list, and the mouse is hovering a completed download (which
 | 
						|
 *    highlights).
 | 
						|
 * 2) Windows doesn't show focusrings until after the first time that tab is
 | 
						|
 *    pressed (and by then you're focusing the second item in the panel).
 | 
						|
 * 3) Richlistbox sets -moz-focusring even when we select it with a mouse.
 | 
						|
 *
 | 
						|
 * In general, the desired behaviour is to focus the first item after pressing
 | 
						|
 * tab/down, and show that focus with a ring. Then, if the mouse moves over
 | 
						|
 * the panel, to hide that focus ring; essentially resetting us to the state
 | 
						|
 * before pressing the key.
 | 
						|
 *
 | 
						|
 * We end up capturing the tab/down key events, and preventing their default
 | 
						|
 * behaviour. We then set a "keyfocus" attribute on the panel, which allows
 | 
						|
 * us to draw a ring around the currently focused element. If the panel is
 | 
						|
 * closed or the mouse moves over the panel, we remove the attribute.
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | 
						|
var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
 | 
						|
ChromeUtils.defineModuleGetter(this, "DownloadsViewUI",
 | 
						|
                               "resource:///modules/DownloadsViewUI.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "DownloadsSubview",
 | 
						|
                               "resource:///modules/DownloadsSubview.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "FileUtils",
 | 
						|
                               "resource://gre/modules/FileUtils.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "NetUtil",
 | 
						|
                               "resource://gre/modules/NetUtil.jsm");
 | 
						|
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
 | 
						|
                               "resource://gre/modules/PlacesUtils.jsm");
 | 
						|
 | 
						|
// DownloadsPanel
 | 
						|
 | 
						|
/**
 | 
						|
 * Main entry point for the downloads panel interface.
 | 
						|
 */
 | 
						|
var DownloadsPanel = {
 | 
						|
  // Initialization and termination
 | 
						|
 | 
						|
  /**
 | 
						|
   * Internal state of the downloads panel, based on one of the kState
 | 
						|
   * constants.  This is not the same state as the XUL panel element.
 | 
						|
   */
 | 
						|
  _state: 0,
 | 
						|
 | 
						|
  /** The panel is not linked to downloads data yet. */
 | 
						|
  get kStateUninitialized() {
 | 
						|
    return 0;
 | 
						|
  },
 | 
						|
  /** This object is linked to data, but the panel is invisible. */
 | 
						|
  get kStateHidden() {
 | 
						|
    return 1;
 | 
						|
  },
 | 
						|
  /** The panel will be shown as soon as possible. */
 | 
						|
  get kStateWaitingData() {
 | 
						|
    return 2;
 | 
						|
  },
 | 
						|
  /** The panel is open. */
 | 
						|
  get kStateShown() {
 | 
						|
    return 3;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Starts loading the download data in background, without opening the panel.
 | 
						|
   * Use showPanel instead to load the data and open the panel at the same time.
 | 
						|
   */
 | 
						|
  initialize() {
 | 
						|
    DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window.");
 | 
						|
    if (this._state != this.kStateUninitialized) {
 | 
						|
      DownloadsCommon.log("DownloadsPanel is already initialized.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this._state = this.kStateHidden;
 | 
						|
 | 
						|
    window.addEventListener("unload", this.onWindowUnload);
 | 
						|
 | 
						|
    // Load and resume active downloads if required.  If there are downloads to
 | 
						|
    // be shown in the panel, they will be loaded asynchronously.
 | 
						|
    DownloadsCommon.initializeAllDataLinks();
 | 
						|
 | 
						|
    // Now that data loading has eventually started, load the required XUL
 | 
						|
    // elements and initialize our views.
 | 
						|
 | 
						|
    this.panel.hidden = false;
 | 
						|
    DownloadsViewController.initialize();
 | 
						|
    DownloadsCommon.log("Attaching DownloadsView...");
 | 
						|
    DownloadsCommon.getData(window).addView(DownloadsView);
 | 
						|
    DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
 | 
						|
                   .addView(DownloadsSummary);
 | 
						|
    DownloadsCommon.log("DownloadsView attached - the panel for this window",
 | 
						|
                        "should now see download items come in.");
 | 
						|
    DownloadsPanel._attachEventListeners();
 | 
						|
    DownloadsCommon.log("DownloadsPanel initialized.");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Closes the downloads panel and frees the internal resources related to the
 | 
						|
   * downloads.  The downloads panel can be reopened later, even after this
 | 
						|
   * function has been called.
 | 
						|
   */
 | 
						|
  terminate() {
 | 
						|
    DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window.");
 | 
						|
    if (this._state == this.kStateUninitialized) {
 | 
						|
      DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    window.removeEventListener("unload", this.onWindowUnload);
 | 
						|
 | 
						|
    // Ensure that the panel is closed before shutting down.
 | 
						|
    this.hidePanel();
 | 
						|
 | 
						|
    DownloadsViewController.terminate();
 | 
						|
    DownloadsCommon.getData(window).removeView(DownloadsView);
 | 
						|
    DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
 | 
						|
                   .removeView(DownloadsSummary);
 | 
						|
    this._unattachEventListeners();
 | 
						|
 | 
						|
    this._state = this.kStateUninitialized;
 | 
						|
 | 
						|
    DownloadsSummary.active = false;
 | 
						|
    DownloadsCommon.log("DownloadsPanel terminated.");
 | 
						|
  },
 | 
						|
 | 
						|
  // Panel interface
 | 
						|
 | 
						|
  /**
 | 
						|
   * Main panel element in the browser window.
 | 
						|
   */
 | 
						|
  get panel() {
 | 
						|
    delete this.panel;
 | 
						|
    return this.panel = document.getElementById("downloadsPanel");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Starts opening the downloads panel interface, anchored to the downloads
 | 
						|
   * button of the browser window.  The list of downloads to display is
 | 
						|
   * initialized the first time this method is called, and the panel is shown
 | 
						|
   * only when data is ready.
 | 
						|
   */
 | 
						|
  showPanel() {
 | 
						|
    DownloadsCommon.log("Opening the downloads panel.");
 | 
						|
 | 
						|
    if (this.isPanelShowing) {
 | 
						|
      DownloadsCommon.log("Panel is already showing - focusing instead.");
 | 
						|
      this._focusPanel();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // As a belt-and-suspenders check, ensure the button is not hidden.
 | 
						|
    DownloadsButton.unhide();
 | 
						|
 | 
						|
    this.initialize();
 | 
						|
    // Delay displaying the panel because this function will sometimes be
 | 
						|
    // called while another window is closing (like the window for selecting
 | 
						|
    // whether to save or open the file), and that would cause the panel to
 | 
						|
    // close immediately.
 | 
						|
    setTimeout(() => this._openPopupIfDataReady(), 0);
 | 
						|
 | 
						|
    DownloadsCommon.log("Waiting for the downloads panel to appear.");
 | 
						|
    this._state = this.kStateWaitingData;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Hides the downloads panel, if visible, but keeps the internal state so that
 | 
						|
   * the panel can be reopened quickly if required.
 | 
						|
   */
 | 
						|
  hidePanel() {
 | 
						|
    DownloadsCommon.log("Closing the downloads panel.");
 | 
						|
 | 
						|
    if (!this.isPanelShowing) {
 | 
						|
      DownloadsCommon.log("Downloads panel is not showing - nothing to do.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    PanelMultiView.hidePopup(this.panel);
 | 
						|
 | 
						|
    // Ensure that we allow the panel to be reopened.  Note that, if the popup
 | 
						|
    // was open, then the onPopupHidden event handler has already updated the
 | 
						|
    // current state, otherwise we must update the state ourselves.
 | 
						|
    this._state = this.kStateHidden;
 | 
						|
    DownloadsCommon.log("Downloads panel is now closed.");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates whether the panel is shown or will be shown.
 | 
						|
   */
 | 
						|
  get isPanelShowing() {
 | 
						|
    return this._state == this.kStateWaitingData ||
 | 
						|
           this._state == this.kStateShown;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns whether the user has started keyboard navigation.
 | 
						|
   */
 | 
						|
  get keyFocusing() {
 | 
						|
    return this.panel.hasAttribute("keyfocus");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Set to true if the user has started keyboard navigation, and we should be
 | 
						|
   * showing focusrings in the panel. Also adds a mousemove event handler to
 | 
						|
   * the panel which disables keyFocusing.
 | 
						|
   */
 | 
						|
  set keyFocusing(aValue) {
 | 
						|
    if (aValue) {
 | 
						|
      this.panel.setAttribute("keyfocus", "true");
 | 
						|
      this.panel.addEventListener("mousemove", this);
 | 
						|
    } else {
 | 
						|
      this.panel.removeAttribute("keyfocus");
 | 
						|
      this.panel.removeEventListener("mousemove", this);
 | 
						|
    }
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Handles the mousemove event for the panel, which disables focusring
 | 
						|
   * visualization.
 | 
						|
   */
 | 
						|
  handleEvent(aEvent) {
 | 
						|
    switch (aEvent.type) {
 | 
						|
      case "mousemove":
 | 
						|
        this.keyFocusing = false;
 | 
						|
        break;
 | 
						|
      case "keydown":
 | 
						|
        this._onKeyDown(aEvent);
 | 
						|
        break;
 | 
						|
      case "keypress":
 | 
						|
        this._onKeyPress(aEvent);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // Callback functions from DownloadsView
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called after data loading finished.
 | 
						|
   */
 | 
						|
  onViewLoadCompleted() {
 | 
						|
    this._openPopupIfDataReady();
 | 
						|
  },
 | 
						|
 | 
						|
  // User interface event functions
 | 
						|
 | 
						|
  onWindowUnload() {
 | 
						|
    // This function is registered as an event listener, we can't use "this".
 | 
						|
    DownloadsPanel.terminate();
 | 
						|
  },
 | 
						|
 | 
						|
  onPopupShown(aEvent) {
 | 
						|
    // Ignore events raised by nested popups.
 | 
						|
    if (aEvent.target != aEvent.currentTarget) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Downloads panel has shown.");
 | 
						|
    this._state = this.kStateShown;
 | 
						|
 | 
						|
    // Since at most one popup is open at any given time, we can set globally.
 | 
						|
    DownloadsCommon.getIndicatorData(window).attentionSuppressed = true;
 | 
						|
 | 
						|
    // Ensure that the first item is selected when the panel is focused.
 | 
						|
    if (DownloadsView.richListBox.itemCount > 0 &&
 | 
						|
        DownloadsView.richListBox.selectedIndex == -1) {
 | 
						|
      DownloadsView.richListBox.selectedIndex = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    this._focusPanel();
 | 
						|
  },
 | 
						|
 | 
						|
  onPopupHidden(aEvent) {
 | 
						|
    // Ignore events raised by nested popups.
 | 
						|
    if (aEvent.target != aEvent.currentTarget) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Downloads panel has hidden.");
 | 
						|
 | 
						|
    // Removes the keyfocus attribute so that we stop handling keyboard
 | 
						|
    // navigation.
 | 
						|
    this.keyFocusing = false;
 | 
						|
 | 
						|
    // Since at most one popup is open at any given time, we can set globally.
 | 
						|
    DownloadsCommon.getIndicatorData(window).attentionSuppressed = false;
 | 
						|
 | 
						|
    // Allow the anchor to be hidden.
 | 
						|
    DownloadsButton.releaseAnchor();
 | 
						|
 | 
						|
    // Allow the panel to be reopened.
 | 
						|
    this._state = this.kStateHidden;
 | 
						|
  },
 | 
						|
 | 
						|
  // Related operations
 | 
						|
 | 
						|
  /**
 | 
						|
   * Shows or focuses the user interface dedicated to downloads history.
 | 
						|
   */
 | 
						|
  showDownloadsHistory() {
 | 
						|
    DownloadsCommon.log("Showing download history.");
 | 
						|
    // Hide the panel before showing another window, otherwise focus will return
 | 
						|
    // to the browser window when the panel closes automatically.
 | 
						|
    this.hidePanel();
 | 
						|
 | 
						|
    BrowserDownloadsUI();
 | 
						|
  },
 | 
						|
 | 
						|
  // Internal functions
 | 
						|
 | 
						|
  /**
 | 
						|
   * Attach event listeners to a panel element. These listeners should be
 | 
						|
   * removed in _unattachEventListeners. This is called automatically after the
 | 
						|
   * panel has successfully loaded.
 | 
						|
   */
 | 
						|
  _attachEventListeners() {
 | 
						|
    // Handle keydown to support accel-V.
 | 
						|
    this.panel.addEventListener("keydown", this);
 | 
						|
    // Handle keypress to be able to preventDefault() events before they reach
 | 
						|
    // the richlistbox, for keyboard navigation.
 | 
						|
    this.panel.addEventListener("keypress", this);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Unattach event listeners that were added in _attachEventListeners. This
 | 
						|
   * is called automatically on panel termination.
 | 
						|
   */
 | 
						|
  _unattachEventListeners() {
 | 
						|
    this.panel.removeEventListener("keydown", this);
 | 
						|
    this.panel.removeEventListener("keypress", this);
 | 
						|
  },
 | 
						|
 | 
						|
  _onKeyPress(aEvent) {
 | 
						|
    // Handle unmodified keys only.
 | 
						|
    if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let richListBox = DownloadsView.richListBox;
 | 
						|
 | 
						|
    // If the user has pressed the tab, up, or down cursor key, start keyboard
 | 
						|
    // navigation, thus enabling focusrings in the panel.  Keyboard navigation
 | 
						|
    // is automatically disabled if the user moves the mouse on the panel, or
 | 
						|
    // if the panel is closed.
 | 
						|
    if ((aEvent.keyCode == aEvent.DOM_VK_TAB ||
 | 
						|
         aEvent.keyCode == aEvent.DOM_VK_UP ||
 | 
						|
         aEvent.keyCode == aEvent.DOM_VK_DOWN) &&
 | 
						|
        !this.keyFocusing) {
 | 
						|
      this.keyFocusing = true;
 | 
						|
      // Ensure there's a selection, we will show the focus ring around it and
 | 
						|
      // prevent the richlistbox from changing the selection.
 | 
						|
      if (DownloadsView.richListBox.selectedIndex == -1) {
 | 
						|
        DownloadsView.richListBox.selectedIndex = 0;
 | 
						|
      }
 | 
						|
      aEvent.preventDefault();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aEvent.keyCode == aEvent.DOM_VK_DOWN) {
 | 
						|
      // If the last element in the list is selected, or the footer is already
 | 
						|
      // focused, focus the footer.
 | 
						|
      if (richListBox.selectedItem === richListBox.lastElementChild ||
 | 
						|
          document.activeElement.parentNode.id === "downloadsFooter") {
 | 
						|
        DownloadsFooter.focus();
 | 
						|
        aEvent.preventDefault();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Pass keypress events to the richlistbox view when it's focused.
 | 
						|
    if (document.activeElement === richListBox) {
 | 
						|
      DownloadsView.onDownloadKeyPress(aEvent);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Keydown listener that listens for the keys to start key focusing, as well
 | 
						|
   * as the the accel-V "paste" event, which initiates a file download if the
 | 
						|
   * pasted item can be resolved to a URI.
 | 
						|
   */
 | 
						|
  _onKeyDown(aEvent) {
 | 
						|
    // If the footer is focused and the downloads list has at least 1 element
 | 
						|
    // in it, focus the last element in the list when going up.
 | 
						|
    if (aEvent.keyCode == aEvent.DOM_VK_UP &&
 | 
						|
        document.activeElement.parentNode.id === "downloadsFooter" &&
 | 
						|
        DownloadsView.richListBox.firstElementChild) {
 | 
						|
      DownloadsView.richListBox.focus();
 | 
						|
      DownloadsView.richListBox.selectedItem = DownloadsView.richListBox.lastElementChild;
 | 
						|
      aEvent.preventDefault();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let pasting = aEvent.keyCode == aEvent.DOM_VK_V &&
 | 
						|
                  aEvent.getModifierState("Accel");
 | 
						|
 | 
						|
    if (!pasting) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Received a paste event.");
 | 
						|
 | 
						|
    let trans = Cc["@mozilla.org/widget/transferable;1"]
 | 
						|
                  .createInstance(Ci.nsITransferable);
 | 
						|
    trans.init(null);
 | 
						|
    let flavors = ["text/x-moz-url", "text/unicode"];
 | 
						|
    flavors.forEach(trans.addDataFlavor);
 | 
						|
    Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
 | 
						|
    // Getting the data or creating the nsIURI might fail
 | 
						|
    try {
 | 
						|
      let data = {};
 | 
						|
      trans.getAnyTransferData({}, data);
 | 
						|
      let [url, name] = data.value
 | 
						|
                            .QueryInterface(Ci.nsISupportsString)
 | 
						|
                            .data
 | 
						|
                            .split("\n");
 | 
						|
      if (!url) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let uri = NetUtil.newURI(url);
 | 
						|
      DownloadsCommon.log("Pasted URL seems valid. Starting download.");
 | 
						|
      DownloadURL(uri.spec, name, document);
 | 
						|
    } catch (ex) {}
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Move focus to the main element in the downloads panel, unless another
 | 
						|
   * element in the panel is already focused.
 | 
						|
   */
 | 
						|
  _focusPanel() {
 | 
						|
    // We may be invoked while the panel is still waiting to be shown.
 | 
						|
    if (this._state != this.kStateShown) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let element = document.commandDispatcher.focusedElement;
 | 
						|
    while (element && element != this.panel) {
 | 
						|
      element = element.parentNode;
 | 
						|
    }
 | 
						|
    if (!element) {
 | 
						|
      if (DownloadsView.richListBox.itemCount > 0) {
 | 
						|
        DownloadsView.richListBox.focus();
 | 
						|
      } else {
 | 
						|
        DownloadsFooter.focus();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Opens the downloads panel when data is ready to be displayed.
 | 
						|
   */
 | 
						|
  _openPopupIfDataReady() {
 | 
						|
    // We don't want to open the popup if we already displayed it, or if we are
 | 
						|
    // still loading data.
 | 
						|
    if (this._state != this.kStateWaitingData || DownloadsView.loading) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // At this point, if the window is minimized, opening the panel could fail
 | 
						|
    // without any notification, and there would be no way to either open or
 | 
						|
    // close the panel any more.  To prevent this, check if the window is
 | 
						|
    // minimized and in that case force the panel to the closed state.
 | 
						|
    if (window.windowState == window.STATE_MINIMIZED) {
 | 
						|
      this._state = this.kStateHidden;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ensure the anchor is visible.  If that is not possible, show the panel
 | 
						|
    // anchored to the top area of the window, near the default anchor position.
 | 
						|
    let anchor = DownloadsButton.getAnchor();
 | 
						|
 | 
						|
    if (!anchor) {
 | 
						|
      DownloadsCommon.error("Downloads button cannot be found.");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let onBookmarksToolbar = !!anchor.closest("#PersonalToolbar");
 | 
						|
    this.panel.classList.toggle("bookmarks-toolbar", onBookmarksToolbar);
 | 
						|
 | 
						|
    // When the panel is opened, we check if the target files of visible items
 | 
						|
    // still exist, and update the allowed items interactions accordingly.  We
 | 
						|
    // do these checks on a background thread, and don't prevent the panel to
 | 
						|
    // be displayed while these checks are being performed.
 | 
						|
    for (let viewItem of DownloadsView._visibleViewItems.values()) {
 | 
						|
      viewItem.download.refresh().catch(Cu.reportError);
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Opening downloads panel popup.");
 | 
						|
    PanelMultiView.openPopup(this.panel, anchor, "bottomcenter topright",
 | 
						|
                             0, 0, false, null).catch(Cu.reportError);
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel);
 | 
						|
 | 
						|
// DownloadsView
 | 
						|
 | 
						|
/**
 | 
						|
 * Builds and updates the downloads list widget, responding to changes in the
 | 
						|
 * download state and real-time data.  In addition, handles part of the user
 | 
						|
 * interaction events raised by the downloads list widget.
 | 
						|
 */
 | 
						|
var DownloadsView = {
 | 
						|
  // Functions handling download items in the list
 | 
						|
 | 
						|
  /**
 | 
						|
   * Maximum number of items shown by the list at any given time.
 | 
						|
   */
 | 
						|
  kItemCountLimit: 5,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates whether there is an open contextMenu for a download item.
 | 
						|
   */
 | 
						|
  contextMenuOpen: false,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates whether there is a DownloadsBlockedSubview open.
 | 
						|
   */
 | 
						|
  subViewOpen: false,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates whether we are still loading downloads data asynchronously.
 | 
						|
   */
 | 
						|
  loading: false,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Ordered array of all Download objects.  We need to keep this array because
 | 
						|
   * only a limited number of items are shown at once, and if an item that is
 | 
						|
   * currently visible is removed from the list, we might need to take another
 | 
						|
   * item from the array and make it appear at the bottom.
 | 
						|
   */
 | 
						|
  _downloads: [],
 | 
						|
 | 
						|
  /**
 | 
						|
   * Associates the visible Download objects with their corresponding
 | 
						|
   * DownloadsViewItem object.  There is a limited number of view items in the
 | 
						|
   * panel at any given time.
 | 
						|
   */
 | 
						|
  _visibleViewItems: new Map(),
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when the number of items in the list changes.
 | 
						|
   */
 | 
						|
  _itemCountChanged() {
 | 
						|
    DownloadsCommon.log("The downloads item count has changed - we are tracking",
 | 
						|
                        this._downloads.length, "downloads in total.");
 | 
						|
    let count = this._downloads.length;
 | 
						|
    let hiddenCount = count - this.kItemCountLimit;
 | 
						|
 | 
						|
    if (count > 0) {
 | 
						|
      DownloadsCommon.log("Setting the panel's hasdownloads attribute to true.");
 | 
						|
      DownloadsPanel.panel.setAttribute("hasdownloads", "true");
 | 
						|
    } else {
 | 
						|
      DownloadsCommon.log("Removing the panel's hasdownloads attribute.");
 | 
						|
      DownloadsPanel.panel.removeAttribute("hasdownloads");
 | 
						|
    }
 | 
						|
 | 
						|
    // If we've got some hidden downloads, we should activate the
 | 
						|
    // DownloadsSummary. The DownloadsSummary will determine whether or not
 | 
						|
    // it's appropriate to actually display the summary.
 | 
						|
    DownloadsSummary.active = hiddenCount > 0;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the list of downloads.
 | 
						|
   */
 | 
						|
  get richListBox() {
 | 
						|
    delete this.richListBox;
 | 
						|
    return this.richListBox = document.getElementById("downloadsListBox");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the button for showing more downloads.
 | 
						|
   */
 | 
						|
  get downloadsHistory() {
 | 
						|
    delete this.downloadsHistory;
 | 
						|
    return this.downloadsHistory = document.getElementById("downloadsHistory");
 | 
						|
  },
 | 
						|
 | 
						|
  // Callback functions from DownloadsData
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called before multiple downloads are about to be loaded.
 | 
						|
   */
 | 
						|
  onDownloadBatchStarting() {
 | 
						|
    DownloadsCommon.log("onDownloadBatchStarting called for DownloadsView.");
 | 
						|
    this.loading = true;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called after data loading finished.
 | 
						|
   */
 | 
						|
  onDownloadBatchEnded() {
 | 
						|
    DownloadsCommon.log("onDownloadBatchEnded called for DownloadsView.");
 | 
						|
 | 
						|
    this.loading = false;
 | 
						|
 | 
						|
    // We suppressed item count change notifications during the batch load, at
 | 
						|
    // this point we should just call the function once.
 | 
						|
    this._itemCountChanged();
 | 
						|
 | 
						|
    // Notify the panel that all the initially available downloads have been
 | 
						|
    // loaded.  This ensures that the interface is visible, if still required.
 | 
						|
    DownloadsPanel.onViewLoadCompleted();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when a new download data item is available, either during the
 | 
						|
   * asynchronous data load or when a new download is started.
 | 
						|
   *
 | 
						|
   * @param aDownload
 | 
						|
   *        Download object that was just added.
 | 
						|
   */
 | 
						|
  onDownloadAdded(download) {
 | 
						|
    DownloadsCommon.log("A new download data item was added");
 | 
						|
 | 
						|
    this._downloads.unshift(download);
 | 
						|
 | 
						|
    // The newly added item is visible in the panel and we must add the
 | 
						|
    // corresponding element. If the list overflows, remove the last item from
 | 
						|
    // the panel to make room for the new one that we just added at the top.
 | 
						|
    this._addViewItem(download, true);
 | 
						|
    if (this._downloads.length > this.kItemCountLimit) {
 | 
						|
      this._removeViewItem(this._downloads[this.kItemCountLimit]);
 | 
						|
    }
 | 
						|
 | 
						|
    // For better performance during batch loads, don't update the count for
 | 
						|
    // every item, because the interface won't be visible until load finishes.
 | 
						|
    if (!this.loading) {
 | 
						|
      this._itemCountChanged();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onDownloadChanged(download) {
 | 
						|
    let viewItem = this._visibleViewItems.get(download);
 | 
						|
    if (viewItem) {
 | 
						|
      viewItem.onChanged();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when a data item is removed.  Ensures that the widget associated
 | 
						|
   * with the view item is removed from the user interface.
 | 
						|
   *
 | 
						|
   * @param download
 | 
						|
   *        Download object that is being removed.
 | 
						|
   */
 | 
						|
  onDownloadRemoved(download) {
 | 
						|
    DownloadsCommon.log("A download data item was removed.");
 | 
						|
 | 
						|
    let itemIndex = this._downloads.indexOf(download);
 | 
						|
    this._downloads.splice(itemIndex, 1);
 | 
						|
 | 
						|
    if (itemIndex < this.kItemCountLimit) {
 | 
						|
      // The item to remove is visible in the panel.
 | 
						|
      this._removeViewItem(download);
 | 
						|
      if (this._downloads.length >= this.kItemCountLimit) {
 | 
						|
        // Reinsert the next item into the panel.
 | 
						|
        this._addViewItem(this._downloads[this.kItemCountLimit - 1], false);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this._itemCountChanged();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Associates each richlistitem for a download with its corresponding
 | 
						|
   * DownloadsViewItem object.
 | 
						|
   */
 | 
						|
  _itemsForElements: new Map(),
 | 
						|
 | 
						|
  itemForElement(element) {
 | 
						|
    return this._itemsForElements.get(element);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Creates a new view item associated with the specified data item, and adds
 | 
						|
   * it to the top or the bottom of the list.
 | 
						|
   */
 | 
						|
  _addViewItem(download, aNewest) {
 | 
						|
    DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
 | 
						|
                        "aNewest =", aNewest);
 | 
						|
 | 
						|
    let element = document.createXULElement("richlistitem");
 | 
						|
    let viewItem = new DownloadsViewItem(download, element);
 | 
						|
    this._visibleViewItems.set(download, viewItem);
 | 
						|
    this._itemsForElements.set(element, viewItem);
 | 
						|
    if (aNewest) {
 | 
						|
      this.richListBox.insertBefore(element, this.richListBox.firstElementChild);
 | 
						|
    } else {
 | 
						|
      this.richListBox.appendChild(element);
 | 
						|
    }
 | 
						|
    viewItem.ensureActive();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes the view item associated with the specified data item.
 | 
						|
   */
 | 
						|
  _removeViewItem(download) {
 | 
						|
    DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
 | 
						|
    let element = this._visibleViewItems.get(download).element;
 | 
						|
    let previousSelectedIndex = this.richListBox.selectedIndex;
 | 
						|
    this.richListBox.removeChild(element);
 | 
						|
    if (previousSelectedIndex != -1) {
 | 
						|
      this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
 | 
						|
                                                this.richListBox.itemCount - 1);
 | 
						|
    }
 | 
						|
    this._visibleViewItems.delete(download);
 | 
						|
    this._itemsForElements.delete(element);
 | 
						|
  },
 | 
						|
 | 
						|
  // User interface event functions
 | 
						|
 | 
						|
  onDownloadClick(aEvent) {
 | 
						|
    // Handle primary clicks only, and exclude the action button.
 | 
						|
    if (aEvent.button == 0 &&
 | 
						|
        !aEvent.originalTarget.hasAttribute("oncommand")) {
 | 
						|
      let target = aEvent.target;
 | 
						|
      while (target.nodeName != "richlistitem") {
 | 
						|
        target = target.parentNode;
 | 
						|
      }
 | 
						|
      let download = DownloadsView.itemForElement(target).download;
 | 
						|
      if (download.hasBlockedData) {
 | 
						|
        goDoCommand("downloadsCmd_showBlockedInfo");
 | 
						|
      } else {
 | 
						|
        goDoCommand("downloadsCmd_open");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onDownloadButton(event) {
 | 
						|
    let target = event.target.closest("richlistitem");
 | 
						|
    DownloadsView.itemForElement(target).onButton();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Handles keypress events on a download item.
 | 
						|
   */
 | 
						|
  onDownloadKeyPress(aEvent) {
 | 
						|
    // Pressing the key on buttons should not invoke the action because the
 | 
						|
    // event has already been handled by the button itself.
 | 
						|
    if (aEvent.originalTarget.hasAttribute("command") ||
 | 
						|
        aEvent.originalTarget.hasAttribute("oncommand")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aEvent.charCode == " ".charCodeAt(0)) {
 | 
						|
      goDoCommand("downloadsCmd_pauseResume");
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
 | 
						|
      goDoCommand("downloadsCmd_doDefault");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Event handlers to keep track of context menu state (open/closed) for
 | 
						|
   * download items.
 | 
						|
   */
 | 
						|
  onContextPopupShown(aEvent) {
 | 
						|
    // Ignore events raised by nested popups.
 | 
						|
    if (aEvent.target != aEvent.currentTarget) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Context menu has shown.");
 | 
						|
    this.contextMenuOpen = true;
 | 
						|
  },
 | 
						|
 | 
						|
  onContextPopupHidden(aEvent) {
 | 
						|
    // Ignore events raised by nested popups.
 | 
						|
    if (aEvent.target != aEvent.currentTarget) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsCommon.log("Context menu has hidden.");
 | 
						|
    this.contextMenuOpen = false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Mouse listeners to handle selection on hover.
 | 
						|
   */
 | 
						|
  onDownloadMouseOver(aEvent) {
 | 
						|
    let item = aEvent.target.closest("richlistitem,richlistbox");
 | 
						|
    if (item.localName != "richlistitem") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aEvent.target.classList.contains("downloadButton")) {
 | 
						|
      item.classList.add("downloadHoveringButton");
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this.contextMenuOpen && !this.subViewOpen) {
 | 
						|
      this.richListBox.selectedItem = item;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onDownloadMouseOut(aEvent) {
 | 
						|
    let item = aEvent.target.closest("richlistitem,richlistbox");
 | 
						|
    if (item.localName != "richlistitem") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aEvent.target.classList.contains("downloadButton")) {
 | 
						|
      item.classList.remove("downloadHoveringButton");
 | 
						|
    }
 | 
						|
 | 
						|
    // If the destination element is outside of the richlistitem, clear the
 | 
						|
    // selection.
 | 
						|
    if (!this.contextMenuOpen && !this.subViewOpen &&
 | 
						|
        !item.contains(aEvent.relatedTarget)) {
 | 
						|
      this.richListBox.selectedIndex = -1;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onDownloadContextMenu(aEvent) {
 | 
						|
    let element = this.richListBox.selectedItem;
 | 
						|
    if (!element) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    DownloadsViewController.updateCommands();
 | 
						|
 | 
						|
    // Set the state attribute so that only the appropriate items are displayed.
 | 
						|
    let contextMenu = document.getElementById("downloadsContextMenu");
 | 
						|
    contextMenu.setAttribute("state", element.getAttribute("state"));
 | 
						|
    if (element.hasAttribute("exists")) {
 | 
						|
      contextMenu.setAttribute("exists", "true");
 | 
						|
    } else {
 | 
						|
      contextMenu.removeAttribute("exists");
 | 
						|
    }
 | 
						|
    contextMenu.classList.toggle("temporary-block",
 | 
						|
                                 element.classList.contains("temporary-block"));
 | 
						|
  },
 | 
						|
 | 
						|
  onDownloadDragStart(aEvent) {
 | 
						|
    let element = this.richListBox.selectedItem;
 | 
						|
    if (!element) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // We must check for existence synchronously because this is a DOM event.
 | 
						|
    let file = new FileUtils.File(DownloadsView.itemForElement(element)
 | 
						|
                                               .download.target.path);
 | 
						|
    if (!file.exists()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let dataTransfer = aEvent.dataTransfer;
 | 
						|
    dataTransfer.mozSetDataAt("application/x-moz-file", file, 0);
 | 
						|
    dataTransfer.effectAllowed = "copyMove";
 | 
						|
    let spec = NetUtil.newURI(file).spec;
 | 
						|
    dataTransfer.setData("text/uri-list", spec);
 | 
						|
    dataTransfer.setData("text/plain", spec);
 | 
						|
    dataTransfer.addElement(element);
 | 
						|
 | 
						|
    aEvent.stopPropagation();
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView);
 | 
						|
 | 
						|
// DownloadsViewItem
 | 
						|
 | 
						|
/**
 | 
						|
 * Builds and updates a single item in the downloads list widget, responding to
 | 
						|
 * changes in the download state and real-time data, and handles the user
 | 
						|
 * interaction events related to a single item in the downloads list widgets.
 | 
						|
 *
 | 
						|
 * @param download
 | 
						|
 *        Download object to be associated with the view item.
 | 
						|
 * @param aElement
 | 
						|
 *        XUL element corresponding to the single download item in the view.
 | 
						|
 */
 | 
						|
function DownloadsViewItem(download, aElement) {
 | 
						|
  this.download = download;
 | 
						|
  this.element = aElement;
 | 
						|
  this.element._shell = this;
 | 
						|
 | 
						|
  this.element.setAttribute("type", "download");
 | 
						|
  this.element.classList.add("download-state");
 | 
						|
 | 
						|
  this.isPanel = true;
 | 
						|
}
 | 
						|
 | 
						|
DownloadsViewItem.prototype = {
 | 
						|
  __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 | 
						|
 | 
						|
  /**
 | 
						|
   * The XUL element corresponding to the associated richlistbox item.
 | 
						|
   */
 | 
						|
  _element: null,
 | 
						|
 | 
						|
  onChanged() {
 | 
						|
    let newState = DownloadsCommon.stateOfDownload(this.download);
 | 
						|
    if (this.downloadState !== newState) {
 | 
						|
      this.downloadState = newState;
 | 
						|
      this._updateState();
 | 
						|
    } else {
 | 
						|
      this._updateStateInner();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  isCommandEnabled(aCommand) {
 | 
						|
    switch (aCommand) {
 | 
						|
      case "downloadsCmd_open": {
 | 
						|
        if (!this.download.succeeded) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
 | 
						|
        let file = new FileUtils.File(this.download.target.path);
 | 
						|
        return file.exists();
 | 
						|
      }
 | 
						|
      case "downloadsCmd_show": {
 | 
						|
        let file = new FileUtils.File(this.download.target.path);
 | 
						|
        if (file.exists()) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!this.download.target.partFilePath) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
 | 
						|
        let partFile = new FileUtils.File(this.download.target.partFilePath);
 | 
						|
        return partFile.exists();
 | 
						|
      }
 | 
						|
      case "cmd_delete":
 | 
						|
      case "downloadsCmd_copyLocation":
 | 
						|
      case "downloadsCmd_doDefault":
 | 
						|
        return true;
 | 
						|
      case "downloadsCmd_showBlockedInfo":
 | 
						|
        return this.download.hasBlockedData;
 | 
						|
    }
 | 
						|
    return DownloadsViewUI.DownloadElementShell.prototype
 | 
						|
                          .isCommandEnabled.call(this, aCommand);
 | 
						|
  },
 | 
						|
 | 
						|
  doCommand(aCommand) {
 | 
						|
    if (this.isCommandEnabled(aCommand)) {
 | 
						|
      this[aCommand]();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // Item commands
 | 
						|
 | 
						|
  downloadsCmd_unblock() {
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
    this.confirmUnblock(window, "unblock");
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_chooseUnblock() {
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
    this.confirmUnblock(window, "chooseUnblock");
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_unblockAndOpen() {
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
    this.unblockAndOpenDownload().catch(Cu.reportError);
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_open() {
 | 
						|
    this.download.launch().catch(Cu.reportError);
 | 
						|
 | 
						|
    // We explicitly close the panel here to give the user the feedback that
 | 
						|
    // their click has been received, and we're handling the action.
 | 
						|
    // Otherwise, we'd have to wait for the file-type handler to execute
 | 
						|
    // before the panel would close. This also helps to prevent the user from
 | 
						|
    // accidentally opening a file several times.
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_show() {
 | 
						|
    let file = new FileUtils.File(this.download.target.path);
 | 
						|
    DownloadsCommon.showDownloadedFile(file);
 | 
						|
 | 
						|
    // We explicitly close the panel here to give the user the feedback that
 | 
						|
    // their click has been received, and we're handling the action.
 | 
						|
    // Otherwise, we'd have to wait for the operating system file manager
 | 
						|
    // window to open before the panel closed. This also helps to prevent the
 | 
						|
    // user from opening the containing folder several times.
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_showBlockedInfo() {
 | 
						|
    DownloadsBlockedSubview.toggle(this.element,
 | 
						|
                                   ...this.rawBlockedTitleAndDetails);
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_openReferrer() {
 | 
						|
    openURL(this.download.source.referrer);
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_copyLocation() {
 | 
						|
    DownloadsCommon.copyDownloadLink(this.download);
 | 
						|
  },
 | 
						|
 | 
						|
  downloadsCmd_doDefault() {
 | 
						|
    let defaultCommand = this.currentDefaultCommandName;
 | 
						|
    if (defaultCommand && this.isCommandEnabled(defaultCommand)) {
 | 
						|
      this.doCommand(defaultCommand);
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
// DownloadsViewController
 | 
						|
 | 
						|
/**
 | 
						|
 * Handles part of the user interaction events raised by the downloads list
 | 
						|
 * widget, in particular the "commands" that apply to multiple items, and
 | 
						|
 * dispatches the commands that apply to individual items.
 | 
						|
 */
 | 
						|
var DownloadsViewController = {
 | 
						|
  // Initialization and termination
 | 
						|
 | 
						|
  initialize() {
 | 
						|
    window.controllers.insertControllerAt(0, this);
 | 
						|
  },
 | 
						|
 | 
						|
  terminate() {
 | 
						|
    window.controllers.removeController(this);
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIController
 | 
						|
 | 
						|
  supportsCommand(aCommand) {
 | 
						|
    if (aCommand === "downloadsCmd_clearList") {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    // Firstly, determine if this is a command that we can handle.
 | 
						|
    if (!DownloadsViewUI.isCommandName(aCommand)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    if (!(aCommand in this) &&
 | 
						|
        !(aCommand in DownloadsViewItem.prototype)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    // The currently supported commands depend on whether the blocked subview is
 | 
						|
    // showing.  If it is, then take the following path.
 | 
						|
    if (DownloadsView.subViewOpen) {
 | 
						|
      let blockedSubviewCmds = [
 | 
						|
        "downloadsCmd_unblockAndOpen",
 | 
						|
        "cmd_delete",
 | 
						|
      ];
 | 
						|
      return blockedSubviewCmds.includes(aCommand);
 | 
						|
    }
 | 
						|
    // If the blocked subview is not showing, then determine if focus is on a
 | 
						|
    // control in the downloads list.
 | 
						|
    let element = document.commandDispatcher.focusedElement;
 | 
						|
    while (element && element != DownloadsView.richListBox) {
 | 
						|
      element = element.parentNode;
 | 
						|
    }
 | 
						|
    // We should handle the command only if the downloads list is among the
 | 
						|
    // ancestors of the focused element.
 | 
						|
    return !!element;
 | 
						|
  },
 | 
						|
 | 
						|
  isCommandEnabled(aCommand) {
 | 
						|
    // Handle commands that are not selection-specific.
 | 
						|
    if (aCommand == "downloadsCmd_clearList") {
 | 
						|
      return DownloadsCommon.getData(window).canRemoveFinished;
 | 
						|
    }
 | 
						|
 | 
						|
    // Other commands are selection-specific.
 | 
						|
    let element = DownloadsView.richListBox.selectedItem;
 | 
						|
    return element && DownloadsView.itemForElement(element)
 | 
						|
                                   .isCommandEnabled(aCommand);
 | 
						|
  },
 | 
						|
 | 
						|
  doCommand(aCommand) {
 | 
						|
    // If this command is not selection-specific, execute it.
 | 
						|
    if (aCommand in this) {
 | 
						|
      this[aCommand]();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Other commands are selection-specific.
 | 
						|
    let element = DownloadsView.richListBox.selectedItem;
 | 
						|
    if (element) {
 | 
						|
      // The doCommand function also checks if the command is enabled.
 | 
						|
      DownloadsView.itemForElement(element).doCommand(aCommand);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onEvent() {},
 | 
						|
 | 
						|
  // Other functions
 | 
						|
 | 
						|
  updateCommands() {
 | 
						|
    function updateCommandsForObject(object) {
 | 
						|
      for (let name in object) {
 | 
						|
        if (DownloadsViewUI.isCommandName(name)) {
 | 
						|
          goUpdateCommand(name);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    updateCommandsForObject(this);
 | 
						|
    updateCommandsForObject(DownloadsViewItem.prototype);
 | 
						|
  },
 | 
						|
 | 
						|
  // Selection-independent commands
 | 
						|
 | 
						|
  downloadsCmd_clearList() {
 | 
						|
    DownloadsCommon.getData(window).removeFinished();
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController);
 | 
						|
 | 
						|
// DownloadsSummary
 | 
						|
 | 
						|
/**
 | 
						|
 * Manages the summary at the bottom of the downloads panel list if the number
 | 
						|
 * of items in the list exceeds the panels limit.
 | 
						|
 */
 | 
						|
var DownloadsSummary = {
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the active state of the summary. When active, the summary subscribes
 | 
						|
   * to the DownloadsCommon DownloadsSummaryData singleton.
 | 
						|
   *
 | 
						|
   * @param aActive
 | 
						|
   *        Set to true to activate the summary.
 | 
						|
   */
 | 
						|
  set active(aActive) {
 | 
						|
    if (aActive == this._active || !this._summaryNode) {
 | 
						|
      return this._active;
 | 
						|
    }
 | 
						|
    if (aActive) {
 | 
						|
      DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
 | 
						|
                     .refreshView(this);
 | 
						|
    } else {
 | 
						|
      DownloadsFooter.showingSummary = false;
 | 
						|
    }
 | 
						|
 | 
						|
    return this._active = aActive;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the active state of the downloads summary.
 | 
						|
   */
 | 
						|
  get active() {
 | 
						|
    return this._active;
 | 
						|
  },
 | 
						|
 | 
						|
  _active: false,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets whether or not we show the progress bar.
 | 
						|
   *
 | 
						|
   * @param aShowingProgress
 | 
						|
   *        True if we should show the progress bar.
 | 
						|
   */
 | 
						|
  set showingProgress(aShowingProgress) {
 | 
						|
    if (aShowingProgress) {
 | 
						|
      this._summaryNode.setAttribute("inprogress", "true");
 | 
						|
    } else {
 | 
						|
      this._summaryNode.removeAttribute("inprogress");
 | 
						|
    }
 | 
						|
    // If progress isn't being shown, then we simply do not show the summary.
 | 
						|
    return DownloadsFooter.showingSummary = aShowingProgress;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the amount of progress that is visible in the progress bar.
 | 
						|
   *
 | 
						|
   * @param aValue
 | 
						|
   *        A value between 0 and 100 to represent the progress of the
 | 
						|
   *        summarized downloads.
 | 
						|
   */
 | 
						|
  set percentComplete(aValue) {
 | 
						|
    if (this._progressNode) {
 | 
						|
      this._progressNode.setAttribute("value", aValue);
 | 
						|
    }
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the description for the download summary.
 | 
						|
   *
 | 
						|
   * @param aValue
 | 
						|
   *        A string representing the description of the summarized
 | 
						|
   *        downloads.
 | 
						|
   */
 | 
						|
  set description(aValue) {
 | 
						|
    if (this._descriptionNode) {
 | 
						|
      this._descriptionNode.setAttribute("value", aValue);
 | 
						|
      this._descriptionNode.setAttribute("tooltiptext", aValue);
 | 
						|
    }
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the details for the download summary, such as the time remaining,
 | 
						|
   * the amount of bytes transferred, etc.
 | 
						|
   *
 | 
						|
   * @param aValue
 | 
						|
   *        A string representing the details of the summarized
 | 
						|
   *        downloads.
 | 
						|
   */
 | 
						|
  set details(aValue) {
 | 
						|
    if (this._detailsNode) {
 | 
						|
      this._detailsNode.setAttribute("value", aValue);
 | 
						|
      this._detailsNode.setAttribute("tooltiptext", aValue);
 | 
						|
    }
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Focuses the root element of the summary.
 | 
						|
   */
 | 
						|
  focus() {
 | 
						|
    if (this._summaryNode) {
 | 
						|
      this._summaryNode.focus();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Respond to keydown events on the Downloads Summary node.
 | 
						|
   *
 | 
						|
   * @param aEvent
 | 
						|
   *        The keydown event being handled.
 | 
						|
   */
 | 
						|
  onKeyDown(aEvent) {
 | 
						|
    if (aEvent.charCode == " ".charCodeAt(0) ||
 | 
						|
        aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
 | 
						|
      DownloadsPanel.showDownloadsHistory();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Respond to click events on the Downloads Summary node.
 | 
						|
   *
 | 
						|
   * @param aEvent
 | 
						|
   *        The click event being handled.
 | 
						|
   */
 | 
						|
  onClick(aEvent) {
 | 
						|
    DownloadsPanel.showDownloadsHistory();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the root of the downloads summary.
 | 
						|
   */
 | 
						|
  get _summaryNode() {
 | 
						|
    let node = document.getElementById("downloadsSummary");
 | 
						|
    if (!node) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    delete this._summaryNode;
 | 
						|
    return this._summaryNode = node;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the progress bar in the downloads summary.
 | 
						|
   */
 | 
						|
  get _progressNode() {
 | 
						|
    let node = document.getElementById("downloadsSummaryProgress");
 | 
						|
    if (!node) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    delete this._progressNode;
 | 
						|
    return this._progressNode = node;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the main description of the downloads
 | 
						|
   * summary.
 | 
						|
   */
 | 
						|
  get _descriptionNode() {
 | 
						|
    let node = document.getElementById("downloadsSummaryDescription");
 | 
						|
    if (!node) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    delete this._descriptionNode;
 | 
						|
    return this._descriptionNode = node;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the secondary description of the downloads
 | 
						|
   * summary.
 | 
						|
   */
 | 
						|
  get _detailsNode() {
 | 
						|
    let node = document.getElementById("downloadsSummaryDetails");
 | 
						|
    if (!node) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    delete this._detailsNode;
 | 
						|
    return this._detailsNode = node;
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsSummary", DownloadsSummary);
 | 
						|
 | 
						|
// DownloadsFooter
 | 
						|
 | 
						|
/**
 | 
						|
 * Manages events sent to to the footer vbox, which contains both the
 | 
						|
 * DownloadsSummary as well as the "Show All Downloads" button.
 | 
						|
 */
 | 
						|
var DownloadsFooter = {
 | 
						|
 | 
						|
  /**
 | 
						|
   * Focuses the appropriate element within the footer. If the summary
 | 
						|
   * is visible, focus it. If not, focus the "Show All Downloads"
 | 
						|
   * button.
 | 
						|
   */
 | 
						|
  focus() {
 | 
						|
    if (this._showingSummary) {
 | 
						|
      DownloadsSummary.focus();
 | 
						|
    } else {
 | 
						|
      DownloadsView.downloadsHistory.focus();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _showingSummary: false,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets whether or not the Downloads Summary should be displayed in the
 | 
						|
   * footer. If not, the "Show All Downloads" button is shown instead.
 | 
						|
   */
 | 
						|
  set showingSummary(aValue) {
 | 
						|
    if (this._footerNode) {
 | 
						|
      if (aValue) {
 | 
						|
        this._footerNode.setAttribute("showingsummary", "true");
 | 
						|
      } else {
 | 
						|
        this._footerNode.removeAttribute("showingsummary");
 | 
						|
      }
 | 
						|
      this._showingSummary = aValue;
 | 
						|
    }
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Element corresponding to the footer of the downloads panel.
 | 
						|
   */
 | 
						|
  get _footerNode() {
 | 
						|
    let node = document.getElementById("downloadsFooter");
 | 
						|
    if (!node) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    delete this._footerNode;
 | 
						|
    return this._footerNode = node;
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter);
 | 
						|
 | 
						|
 | 
						|
// DownloadsBlockedSubview
 | 
						|
 | 
						|
/**
 | 
						|
 * Manages the blocked subview that slides in when you click a blocked download.
 | 
						|
 */
 | 
						|
var DownloadsBlockedSubview = {
 | 
						|
  /**
 | 
						|
   * Elements in the subview.
 | 
						|
   */
 | 
						|
  get elements() {
 | 
						|
    let idSuffixes = [
 | 
						|
      "title",
 | 
						|
      "details1",
 | 
						|
      "details2",
 | 
						|
      "openButton",
 | 
						|
      "deleteButton",
 | 
						|
    ];
 | 
						|
    let elements = idSuffixes.reduce((memo, s) => {
 | 
						|
      memo[s] = document.getElementById("downloadsPanel-blockedSubview-" + s);
 | 
						|
      return memo;
 | 
						|
    }, {});
 | 
						|
    delete this.elements;
 | 
						|
    return this.elements = elements;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * The blocked-download richlistitem element that was clicked to show the
 | 
						|
   * subview.  If the subview is not showing, this is undefined.
 | 
						|
   */
 | 
						|
  element: undefined,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Slides in the blocked subview.
 | 
						|
   *
 | 
						|
   * @param element
 | 
						|
   *        The blocked-download richlistitem element that was clicked.
 | 
						|
   * @param title
 | 
						|
   *        The title to show in the subview.
 | 
						|
   * @param details
 | 
						|
   *        An array of strings with information about the block.
 | 
						|
   */
 | 
						|
  toggle(element, title, details) {
 | 
						|
    DownloadsView.subViewOpen = true;
 | 
						|
    DownloadsViewController.updateCommands();
 | 
						|
 | 
						|
    let e = this.elements;
 | 
						|
    let s = DownloadsCommon.strings;
 | 
						|
    e.title.textContent = title;
 | 
						|
    e.details1.textContent = details[0];
 | 
						|
    e.details2.textContent = details[1];
 | 
						|
    e.openButton.label = s.unblockButtonOpen;
 | 
						|
    e.deleteButton.label = s.unblockButtonConfirmBlock;
 | 
						|
 | 
						|
    let verdict = element.getAttribute("verdict");
 | 
						|
    this.subview.setAttribute("verdict", verdict);
 | 
						|
 | 
						|
    this.mainView.addEventListener("ViewShown", this);
 | 
						|
    DownloadsPanel.panel.addEventListener("popuphidden", this);
 | 
						|
    this.panelMultiView.showSubView(this.subview);
 | 
						|
 | 
						|
    // Without this, the mainView is more narrow than the panel once all
 | 
						|
    // downloads are removed from the panel.
 | 
						|
    this.mainView.style.minWidth = window.getComputedStyle(this.subview).width;
 | 
						|
  },
 | 
						|
 | 
						|
  handleEvent(event) {
 | 
						|
    // This is called when the main view is shown or the panel is hidden.
 | 
						|
    DownloadsView.subViewOpen = false;
 | 
						|
    this.mainView.removeEventListener("ViewShown", this);
 | 
						|
    DownloadsPanel.panel.removeEventListener("popuphidden", this);
 | 
						|
    // Focus the proper element if we're going back to the main panel.
 | 
						|
    if (event.type == "ViewShown") {
 | 
						|
      DownloadsPanel.showPanel();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Deletes the download and hides the entire panel.
 | 
						|
   */
 | 
						|
  confirmBlock() {
 | 
						|
    goDoCommand("cmd_delete");
 | 
						|
    DownloadsPanel.hidePanel();
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "panelMultiView",
 | 
						|
  () => document.getElementById("downloadsPanel-multiView"));
 | 
						|
XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "mainView",
 | 
						|
  () => document.getElementById("downloadsPanel-mainView"));
 | 
						|
XPCOMUtils.defineLazyGetter(DownloadsBlockedSubview, "subview",
 | 
						|
  () => document.getElementById("downloadsPanel-blockedSubview"));
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview",
 | 
						|
                          DownloadsBlockedSubview);
 |