forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1076 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1076 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(this, {
 | 
						|
  AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
 | 
						|
  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
 | 
						|
  PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
 | 
						|
});
 | 
						|
ChromeUtils.defineModuleGetter(
 | 
						|
  this,
 | 
						|
  "ToolbarPanelHub",
 | 
						|
  "resource://activity-stream/lib/ToolbarPanelHub.jsm"
 | 
						|
);
 | 
						|
 | 
						|
/**
 | 
						|
 * Maintains the state and dispatches events for the main menu panel.
 | 
						|
 */
 | 
						|
 | 
						|
const PanelUI = {
 | 
						|
  /** Panel events that we listen for. **/
 | 
						|
  get kEvents() {
 | 
						|
    return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
 | 
						|
  },
 | 
						|
  /**
 | 
						|
   * Used for lazily getting and memoizing elements from the document. Lazy
 | 
						|
   * getters are set in init, and memoizing happens after the first retrieval.
 | 
						|
   */
 | 
						|
  get kElements() {
 | 
						|
    return {
 | 
						|
      multiView: "appMenu-multiView",
 | 
						|
      menuButton: "PanelUI-menu-button",
 | 
						|
      panel: "appMenu-popup",
 | 
						|
      overflowFixedList: "widget-overflow-fixed-list",
 | 
						|
      overflowPanel: "widget-overflow",
 | 
						|
      navbar: "nav-bar",
 | 
						|
    };
 | 
						|
  },
 | 
						|
 | 
						|
  _initialized: false,
 | 
						|
  _notifications: null,
 | 
						|
  _notificationPanel: null,
 | 
						|
 | 
						|
  init(shouldSuppress) {
 | 
						|
    this._shouldSuppress = shouldSuppress;
 | 
						|
    this._initElements();
 | 
						|
 | 
						|
    this.menuButton.addEventListener("mousedown", this);
 | 
						|
    this.menuButton.addEventListener("keypress", this);
 | 
						|
 | 
						|
    Services.obs.addObserver(this, "fullscreen-nav-toolbox");
 | 
						|
    Services.obs.addObserver(this, "appMenu-notifications");
 | 
						|
    Services.obs.addObserver(this, "show-update-progress");
 | 
						|
 | 
						|
    XPCOMUtils.defineLazyPreferenceGetter(
 | 
						|
      this,
 | 
						|
      "autoHideToolbarInFullScreen",
 | 
						|
      "browser.fullscreen.autohide",
 | 
						|
      false,
 | 
						|
      (pref, previousValue, newValue) => {
 | 
						|
        // On OSX, or with autohide preffed off, MozDOMFullscreen is the only
 | 
						|
        // event we care about, since fullscreen should behave just like non
 | 
						|
        // fullscreen. Otherwise, we don't want to listen to these because
 | 
						|
        // we'd just be spamming ourselves with both of them whenever a user
 | 
						|
        // opened a video.
 | 
						|
        if (newValue) {
 | 
						|
          window.removeEventListener("MozDOMFullscreen:Entered", this);
 | 
						|
          window.removeEventListener("MozDOMFullscreen:Exited", this);
 | 
						|
          window.addEventListener("fullscreen", this);
 | 
						|
        } else {
 | 
						|
          window.addEventListener("MozDOMFullscreen:Entered", this);
 | 
						|
          window.addEventListener("MozDOMFullscreen:Exited", this);
 | 
						|
          window.removeEventListener("fullscreen", this);
 | 
						|
        }
 | 
						|
 | 
						|
        this.updateNotifications(false);
 | 
						|
      },
 | 
						|
      autoHidePref => autoHidePref && Services.appinfo.OS !== "Darwin"
 | 
						|
    );
 | 
						|
 | 
						|
    if (this.autoHideToolbarInFullScreen) {
 | 
						|
      window.addEventListener("fullscreen", this);
 | 
						|
    } else {
 | 
						|
      window.addEventListener("MozDOMFullscreen:Entered", this);
 | 
						|
      window.addEventListener("MozDOMFullscreen:Exited", this);
 | 
						|
    }
 | 
						|
 | 
						|
    window.addEventListener("activate", this);
 | 
						|
    CustomizableUI.addListener(this);
 | 
						|
 | 
						|
    // We do this sync on init because in order to have the overflow button show up
 | 
						|
    // we need to know whether anything is in the permanent panel area.
 | 
						|
    this.overflowFixedList.hidden = false;
 | 
						|
    // Also unhide the separator. We use CSS to hide/show it based on the panel's content.
 | 
						|
    this.overflowFixedList.previousElementSibling.hidden = false;
 | 
						|
    CustomizableUI.registerPanelNode(
 | 
						|
      this.overflowFixedList,
 | 
						|
      CustomizableUI.AREA_FIXED_OVERFLOW_PANEL
 | 
						|
    );
 | 
						|
    this.updateOverflowStatus();
 | 
						|
 | 
						|
    Services.obs.notifyObservers(
 | 
						|
      null,
 | 
						|
      "appMenu-notifications-request",
 | 
						|
      "refresh"
 | 
						|
    );
 | 
						|
 | 
						|
    this._initialized = true;
 | 
						|
  },
 | 
						|
 | 
						|
  _initElements() {
 | 
						|
    for (let [k, v] of Object.entries(this.kElements)) {
 | 
						|
      // Need to do fresh let-bindings per iteration
 | 
						|
      let getKey = k;
 | 
						|
      let id = v;
 | 
						|
      this.__defineGetter__(getKey, function () {
 | 
						|
        delete this[getKey];
 | 
						|
        return (this[getKey] = document.getElementById(id));
 | 
						|
      });
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _eventListenersAdded: false,
 | 
						|
  _ensureEventListenersAdded() {
 | 
						|
    if (this._eventListenersAdded) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this._addEventListeners();
 | 
						|
  },
 | 
						|
 | 
						|
  _addEventListeners() {
 | 
						|
    for (let event of this.kEvents) {
 | 
						|
      this.panel.addEventListener(event, this);
 | 
						|
    }
 | 
						|
 | 
						|
    PanelMultiView.getViewNode(document, "PanelUI-helpView").addEventListener(
 | 
						|
      "ViewShowing",
 | 
						|
      this._onHelpViewShow
 | 
						|
    );
 | 
						|
    this._eventListenersAdded = true;
 | 
						|
  },
 | 
						|
 | 
						|
  _removeEventListeners() {
 | 
						|
    for (let event of this.kEvents) {
 | 
						|
      this.panel.removeEventListener(event, this);
 | 
						|
    }
 | 
						|
    PanelMultiView.getViewNode(
 | 
						|
      document,
 | 
						|
      "PanelUI-helpView"
 | 
						|
    ).removeEventListener("ViewShowing", this._onHelpViewShow);
 | 
						|
    this._eventListenersAdded = false;
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    this._removeEventListeners();
 | 
						|
 | 
						|
    if (this._notificationPanel) {
 | 
						|
      for (let event of this.kEvents) {
 | 
						|
        this.notificationPanel.removeEventListener(event, this);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
 | 
						|
    Services.obs.removeObserver(this, "appMenu-notifications");
 | 
						|
    Services.obs.removeObserver(this, "show-update-progress");
 | 
						|
 | 
						|
    window.removeEventListener("MozDOMFullscreen:Entered", this);
 | 
						|
    window.removeEventListener("MozDOMFullscreen:Exited", this);
 | 
						|
    window.removeEventListener("fullscreen", this);
 | 
						|
    window.removeEventListener("activate", this);
 | 
						|
    this.menuButton.removeEventListener("mousedown", this);
 | 
						|
    this.menuButton.removeEventListener("keypress", this);
 | 
						|
    CustomizableUI.removeListener(this);
 | 
						|
    if (this.whatsNewPanel) {
 | 
						|
      this.whatsNewPanel.removeEventListener("ViewShowing", this);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Opens the menu panel if it's closed, or closes it if it's
 | 
						|
   * open.
 | 
						|
   *
 | 
						|
   * @param aEvent the event that triggers the toggle.
 | 
						|
   */
 | 
						|
  toggle(aEvent) {
 | 
						|
    // Don't show the panel if the window is in customization mode,
 | 
						|
    // since this button doubles as an exit path for the user in this case.
 | 
						|
    if (document.documentElement.hasAttribute("customizing")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this._ensureEventListenersAdded();
 | 
						|
    if (this.panel.state == "open") {
 | 
						|
      this.hide();
 | 
						|
    } else if (this.panel.state == "closed") {
 | 
						|
      this.show(aEvent);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Opens the menu panel. If the event target has a child with the
 | 
						|
   * toolbarbutton-icon attribute, the panel will be anchored on that child.
 | 
						|
   * Otherwise, the panel is anchored on the event target itself.
 | 
						|
   *
 | 
						|
   * @param aEvent the event (if any) that triggers showing the menu.
 | 
						|
   */
 | 
						|
  show(aEvent) {
 | 
						|
    this._ensureShortcutsShown();
 | 
						|
    (async () => {
 | 
						|
      await this.ensureReady();
 | 
						|
 | 
						|
      if (
 | 
						|
        this.panel.state == "open" ||
 | 
						|
        document.documentElement.hasAttribute("customizing")
 | 
						|
      ) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let domEvent = null;
 | 
						|
      if (aEvent && aEvent.type != "command") {
 | 
						|
        domEvent = aEvent;
 | 
						|
      }
 | 
						|
 | 
						|
      let anchor = this._getPanelAnchor(this.menuButton);
 | 
						|
      await PanelMultiView.openPopup(this.panel, anchor, {
 | 
						|
        triggerEvent: domEvent,
 | 
						|
      });
 | 
						|
    })().catch(console.error);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * If the menu panel is being shown, hide it.
 | 
						|
   */
 | 
						|
  hide() {
 | 
						|
    if (document.documentElement.hasAttribute("customizing")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    PanelMultiView.hidePopup(this.panel);
 | 
						|
  },
 | 
						|
 | 
						|
  observe(subject, topic, status) {
 | 
						|
    switch (topic) {
 | 
						|
      case "fullscreen-nav-toolbox":
 | 
						|
        if (this._notifications) {
 | 
						|
          this.updateNotifications(false);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case "appMenu-notifications":
 | 
						|
        // Don't initialize twice.
 | 
						|
        if (status == "init" && this._notifications) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        this._notifications = AppMenuNotifications.notifications;
 | 
						|
        this.updateNotifications(true);
 | 
						|
        break;
 | 
						|
      case "show-update-progress":
 | 
						|
        openAboutDialog();
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleEvent(aEvent) {
 | 
						|
    // Ignore context menus and menu button menus showing and hiding:
 | 
						|
    if (aEvent.type.startsWith("popup") && aEvent.target != this.panel) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    switch (aEvent.type) {
 | 
						|
      case "popupshowing":
 | 
						|
        updateEditUIVisibility();
 | 
						|
      // Fall through
 | 
						|
      case "popupshown":
 | 
						|
        if (aEvent.type == "popupshown") {
 | 
						|
          CustomizableUI.addPanelCloseListeners(this.panel);
 | 
						|
        }
 | 
						|
      // Fall through
 | 
						|
      case "popuphiding":
 | 
						|
        if (aEvent.type == "popuphiding") {
 | 
						|
          updateEditUIVisibility();
 | 
						|
        }
 | 
						|
      // Fall through
 | 
						|
      case "popuphidden":
 | 
						|
        this.updateNotifications();
 | 
						|
        this._updatePanelButton(aEvent.target);
 | 
						|
        if (aEvent.type == "popuphidden") {
 | 
						|
          CustomizableUI.removePanelCloseListeners(this.panel);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case "mousedown":
 | 
						|
        // On Mac, ctrl-click will send a context menu event from the widget, so
 | 
						|
        // we don't want to bring up the panel when ctrl key is pressed.
 | 
						|
        if (
 | 
						|
          aEvent.button == 0 &&
 | 
						|
          (AppConstants.platform != "macosx" || !aEvent.ctrlKey)
 | 
						|
        ) {
 | 
						|
          this.toggle(aEvent);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case "keypress":
 | 
						|
        if (aEvent.key == " " || aEvent.key == "Enter") {
 | 
						|
          this.toggle(aEvent);
 | 
						|
          aEvent.stopPropagation();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case "MozDOMFullscreen:Entered":
 | 
						|
      case "MozDOMFullscreen:Exited":
 | 
						|
      case "fullscreen":
 | 
						|
      case "activate":
 | 
						|
        this.updateNotifications();
 | 
						|
        break;
 | 
						|
      case "ViewShowing":
 | 
						|
        if (aEvent.target == this.whatsNewPanel) {
 | 
						|
          this.onWhatsNewPanelShowing();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  get isReady() {
 | 
						|
    return !!this._isReady;
 | 
						|
  },
 | 
						|
 | 
						|
  get isNotificationPanelOpen() {
 | 
						|
    let panelState = this.notificationPanel.state;
 | 
						|
 | 
						|
    return panelState == "showing" || panelState == "open";
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Registering the menu panel is done lazily for performance reasons. This
 | 
						|
   * method is exposed so that CustomizationMode can force panel-readyness in the
 | 
						|
   * event that customization mode is started before the panel has been opened
 | 
						|
   * by the user.
 | 
						|
   *
 | 
						|
   * @param aCustomizing (optional) set to true if this was called while entering
 | 
						|
   *        customization mode. If that's the case, we trust that customization
 | 
						|
   *        mode will handle calling beginBatchUpdate and endBatchUpdate.
 | 
						|
   *
 | 
						|
   * @return a Promise that resolves once the panel is ready to roll.
 | 
						|
   */
 | 
						|
  async ensureReady() {
 | 
						|
    if (this._isReady) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    await window.delayedStartupPromise;
 | 
						|
    this._ensureEventListenersAdded();
 | 
						|
    this.panel.hidden = false;
 | 
						|
    this._isReady = true;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Switch the panel to the help view if it's not already
 | 
						|
   * in that view.
 | 
						|
   */
 | 
						|
  showHelpView(aAnchor) {
 | 
						|
    this._ensureEventListenersAdded();
 | 
						|
    this.multiView.showSubView("PanelUI-helpView", aAnchor);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Switch the panel to the "More Tools" view.
 | 
						|
   *
 | 
						|
   * @param moreTools The panel showing the "More Tools" view.
 | 
						|
   */
 | 
						|
  showMoreToolsPanel(moreTools) {
 | 
						|
    this.showSubView("appmenu-moreTools", moreTools);
 | 
						|
 | 
						|
    // Notify DevTools the panel view is showing and need it to populate the
 | 
						|
    // "Browser Tools" section of the panel. We notify the observer setup by
 | 
						|
    // DevTools because we want to ensure the same menuitem list is shared
 | 
						|
    // between both the AppMenu and toolbar button views.
 | 
						|
    let view = document.getElementById("appmenu-developer-tools-view");
 | 
						|
    Services.obs.notifyObservers(view, "web-developer-tools-view-showing");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Shows a subview in the panel with a given ID.
 | 
						|
   *
 | 
						|
   * @param aViewId the ID of the subview to show.
 | 
						|
   * @param aAnchor the element that spawned the subview.
 | 
						|
   * @param aEvent the event triggering the view showing.
 | 
						|
   */
 | 
						|
  async showSubView(aViewId, aAnchor, aEvent) {
 | 
						|
    if (aEvent) {
 | 
						|
      // On Mac, ctrl-click will send a context menu event from the widget, so
 | 
						|
      // we don't want to bring up the panel when ctrl key is pressed.
 | 
						|
      if (
 | 
						|
        aEvent.type == "mousedown" &&
 | 
						|
        (aEvent.button != 0 ||
 | 
						|
          (AppConstants.platform == "macosx" && aEvent.ctrlKey))
 | 
						|
      ) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      if (
 | 
						|
        aEvent.type == "keypress" &&
 | 
						|
        aEvent.key != " " &&
 | 
						|
        aEvent.key != "Enter"
 | 
						|
      ) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this._ensureEventListenersAdded();
 | 
						|
 | 
						|
    let viewNode = PanelMultiView.getViewNode(document, aViewId);
 | 
						|
    if (!viewNode) {
 | 
						|
      console.error("Could not show panel subview with id: ", aViewId);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!aAnchor) {
 | 
						|
      console.error(
 | 
						|
        "Expected an anchor when opening subview with id: ",
 | 
						|
        aViewId
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this.ensureWhatsNewInitialized(viewNode);
 | 
						|
    this.ensurePanicViewInitialized(viewNode);
 | 
						|
 | 
						|
    let container = aAnchor.closest("panelmultiview");
 | 
						|
    if (container && !viewNode.hasAttribute("disallowSubView")) {
 | 
						|
      container.showSubView(aViewId, aAnchor);
 | 
						|
    } else if (!aAnchor.open) {
 | 
						|
      aAnchor.open = true;
 | 
						|
 | 
						|
      let tempPanel = document.createXULElement("panel");
 | 
						|
      tempPanel.setAttribute("type", "arrow");
 | 
						|
      tempPanel.setAttribute("id", "customizationui-widget-panel");
 | 
						|
      if (viewNode.hasAttribute("neverhidden")) {
 | 
						|
        tempPanel.setAttribute("neverhidden", "true");
 | 
						|
      }
 | 
						|
 | 
						|
      tempPanel.setAttribute("class", "cui-widget-panel panel-no-padding");
 | 
						|
      tempPanel.setAttribute("viewId", aViewId);
 | 
						|
      if (aAnchor.getAttribute("tabspecific")) {
 | 
						|
        tempPanel.setAttribute("tabspecific", true);
 | 
						|
      }
 | 
						|
      if (aAnchor.getAttribute("locationspecific")) {
 | 
						|
        tempPanel.setAttribute("locationspecific", true);
 | 
						|
      }
 | 
						|
      if (this._disableAnimations) {
 | 
						|
        tempPanel.setAttribute("animate", "false");
 | 
						|
      }
 | 
						|
      tempPanel.setAttribute("context", "");
 | 
						|
      document
 | 
						|
        .getElementById(CustomizableUI.AREA_NAVBAR)
 | 
						|
        .appendChild(tempPanel);
 | 
						|
 | 
						|
      let multiView = document.createXULElement("panelmultiview");
 | 
						|
      multiView.setAttribute("id", "customizationui-widget-multiview");
 | 
						|
      multiView.setAttribute("viewCacheId", "appMenu-viewCache");
 | 
						|
      multiView.setAttribute("mainViewId", viewNode.id);
 | 
						|
      multiView.appendChild(viewNode);
 | 
						|
      tempPanel.appendChild(multiView);
 | 
						|
      viewNode.classList.add("cui-widget-panelview", "PanelUI-subView");
 | 
						|
 | 
						|
      let viewShown = false;
 | 
						|
      let panelRemover = event => {
 | 
						|
        // Avoid bubbled events triggering the panel closing.
 | 
						|
        if (event && event.target != tempPanel) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        viewNode.classList.remove("cui-widget-panelview");
 | 
						|
        if (viewShown) {
 | 
						|
          CustomizableUI.removePanelCloseListeners(tempPanel);
 | 
						|
          tempPanel.removeEventListener("popuphidden", panelRemover);
 | 
						|
        }
 | 
						|
        aAnchor.open = false;
 | 
						|
 | 
						|
        PanelMultiView.removePopup(tempPanel);
 | 
						|
      };
 | 
						|
 | 
						|
      if (aAnchor.parentNode.id == "PersonalToolbar") {
 | 
						|
        tempPanel.classList.add("bookmarks-toolbar");
 | 
						|
      }
 | 
						|
 | 
						|
      let anchor = this._getPanelAnchor(aAnchor);
 | 
						|
 | 
						|
      if (aAnchor != anchor && aAnchor.id) {
 | 
						|
        anchor.setAttribute("consumeanchor", aAnchor.id);
 | 
						|
      }
 | 
						|
 | 
						|
      try {
 | 
						|
        viewShown = await PanelMultiView.openPopup(tempPanel, anchor, {
 | 
						|
          position: "bottomright topright",
 | 
						|
          triggerEvent: aEvent,
 | 
						|
        });
 | 
						|
      } catch (ex) {
 | 
						|
        console.error(ex);
 | 
						|
      }
 | 
						|
 | 
						|
      if (viewShown) {
 | 
						|
        CustomizableUI.addPanelCloseListeners(tempPanel);
 | 
						|
        tempPanel.addEventListener("popuphidden", panelRemover);
 | 
						|
      } else {
 | 
						|
        panelRemover();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets up the event listener for when the What's New panel is shown.
 | 
						|
   *
 | 
						|
   * @param {panelview} panelView The What's New panelview.
 | 
						|
   */
 | 
						|
  ensureWhatsNewInitialized(panelView) {
 | 
						|
    if (panelView.id != "PanelUI-whatsNew" || panelView._initialized) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this.whatsNewPanel) {
 | 
						|
      this.whatsNewPanel = panelView;
 | 
						|
    }
 | 
						|
 | 
						|
    panelView._initialized = true;
 | 
						|
    panelView.addEventListener("ViewShowing", this);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Adds FTL before appending the panic view markup to the main DOM.
 | 
						|
   *
 | 
						|
   * @param {panelview} panelView The Panic View panelview.
 | 
						|
   */
 | 
						|
  ensurePanicViewInitialized(panelView) {
 | 
						|
    if (panelView.id != "PanelUI-panicView" || panelView._initialized) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this.panic) {
 | 
						|
      this.panic = panelView;
 | 
						|
    }
 | 
						|
 | 
						|
    MozXULElement.insertFTLIfNeeded("browser/panicButton.ftl");
 | 
						|
    panelView._initialized = true;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * When the What's New panel is showing, we fetch the messages to show.
 | 
						|
   */
 | 
						|
  onWhatsNewPanelShowing() {
 | 
						|
    ToolbarPanelHub.renderMessages(
 | 
						|
      window,
 | 
						|
      document,
 | 
						|
      "PanelUI-whatsNew-message-container"
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * NB: The enable- and disableSingleSubviewPanelAnimations methods only
 | 
						|
   * affect the hiding/showing animations of single-subview panels (tempPanel
 | 
						|
   * in the showSubView method).
 | 
						|
   */
 | 
						|
  disableSingleSubviewPanelAnimations() {
 | 
						|
    this._disableAnimations = true;
 | 
						|
  },
 | 
						|
 | 
						|
  enableSingleSubviewPanelAnimations() {
 | 
						|
    this._disableAnimations = false;
 | 
						|
  },
 | 
						|
 | 
						|
  updateOverflowStatus() {
 | 
						|
    let hasKids = this.overflowFixedList.hasChildNodes();
 | 
						|
    if (hasKids && !this.navbar.hasAttribute("nonemptyoverflow")) {
 | 
						|
      this.navbar.setAttribute("nonemptyoverflow", "true");
 | 
						|
      this.overflowPanel.setAttribute("hasfixeditems", "true");
 | 
						|
    } else if (!hasKids && this.navbar.hasAttribute("nonemptyoverflow")) {
 | 
						|
      PanelMultiView.hidePopup(this.overflowPanel);
 | 
						|
      this.overflowPanel.removeAttribute("hasfixeditems");
 | 
						|
      this.navbar.removeAttribute("nonemptyoverflow");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval) {
 | 
						|
    if (aContainer == this.overflowFixedList) {
 | 
						|
      this.updateOverflowStatus();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onAreaReset(aArea, aContainer) {
 | 
						|
    if (aContainer == this.overflowFixedList) {
 | 
						|
      this.updateOverflowStatus();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the anchor node into the open or closed state, depending
 | 
						|
   * on the state of the panel.
 | 
						|
   */
 | 
						|
  _updatePanelButton() {
 | 
						|
    let { state } = this.panel;
 | 
						|
    if (state == "open" || state == "showing") {
 | 
						|
      this.menuButton.open = true;
 | 
						|
      document.l10n.setAttributes(
 | 
						|
        this.menuButton,
 | 
						|
        "appmenu-menu-button-opened2"
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      this.menuButton.open = false;
 | 
						|
      document.l10n.setAttributes(
 | 
						|
        this.menuButton,
 | 
						|
        "appmenu-menu-button-closed2"
 | 
						|
      );
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _onHelpViewShow(aEvent) {
 | 
						|
    // Call global menu setup function
 | 
						|
    buildHelpMenu();
 | 
						|
 | 
						|
    let helpMenu = document.getElementById("menu_HelpPopup");
 | 
						|
    let items = this.getElementsByTagName("vbox")[0];
 | 
						|
    let attrs = [
 | 
						|
      "command",
 | 
						|
      "oncommand",
 | 
						|
      "onclick",
 | 
						|
      "key",
 | 
						|
      "disabled",
 | 
						|
      "accesskey",
 | 
						|
      "label",
 | 
						|
    ];
 | 
						|
 | 
						|
    // Remove all buttons from the view
 | 
						|
    while (items.firstChild) {
 | 
						|
      items.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    // Add the current set of menuitems of the Help menu to this view
 | 
						|
    let menuItems = Array.prototype.slice.call(
 | 
						|
      helpMenu.getElementsByTagName("menuitem")
 | 
						|
    );
 | 
						|
    let fragment = document.createDocumentFragment();
 | 
						|
    for (let node of menuItems) {
 | 
						|
      if (node.hidden) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      let button = document.createXULElement("toolbarbutton");
 | 
						|
      // Copy specific attributes from a menuitem of the Help menu
 | 
						|
      for (let attrName of attrs) {
 | 
						|
        if (!node.hasAttribute(attrName)) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        button.setAttribute(attrName, node.getAttribute(attrName));
 | 
						|
      }
 | 
						|
 | 
						|
      // We have AppMenu-specific strings for the Help menu. By convention,
 | 
						|
      // their localization IDs are set on "appmenu-data-l10n-id" attributes.
 | 
						|
      let l10nId = node.getAttribute("appmenu-data-l10n-id");
 | 
						|
      if (l10nId) {
 | 
						|
        document.l10n.setAttributes(button, l10nId);
 | 
						|
      }
 | 
						|
 | 
						|
      if (node.id) {
 | 
						|
        button.id = "appMenu_" + node.id;
 | 
						|
      }
 | 
						|
 | 
						|
      button.classList.add("subviewbutton");
 | 
						|
      fragment.appendChild(button);
 | 
						|
    }
 | 
						|
 | 
						|
    // The Enterprise Support menu item has a different location than its
 | 
						|
    // placement in the menubar, so we need to specify it here.
 | 
						|
    let helpPolicySupport = fragment.querySelector(
 | 
						|
      "#appMenu_helpPolicySupport"
 | 
						|
    );
 | 
						|
    if (helpPolicySupport) {
 | 
						|
      fragment.insertBefore(
 | 
						|
        helpPolicySupport,
 | 
						|
        fragment.querySelector("#appMenu_menu_HelpPopup_reportPhishingtoolmenu")
 | 
						|
          .nextSibling
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    items.appendChild(fragment);
 | 
						|
  },
 | 
						|
 | 
						|
  _hidePopup() {
 | 
						|
    if (!this._notificationPanel) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.isNotificationPanelOpen) {
 | 
						|
      this.notificationPanel.hidePopup();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Selects and marks an item by id from the main view. The ids are an array,
 | 
						|
   * the first in the main view and the later ids in subsequent subviews that
 | 
						|
   * become marked when the user opens the subview. The subview marking is
 | 
						|
   * cancelled if a different subview is opened.
 | 
						|
   */
 | 
						|
  async selectAndMarkItem(itemIds) {
 | 
						|
    // This shouldn't really occur, but return early just in case.
 | 
						|
    if (document.documentElement.hasAttribute("customizing")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // This function was triggered from a button while the menu was
 | 
						|
    // already open, so the panel should be in the process of hiding.
 | 
						|
    // Wait for the panel to hide first, then reopen it.
 | 
						|
    if (this.panel.state == "hiding") {
 | 
						|
      await new Promise(resolve => {
 | 
						|
        this.panel.addEventListener("popuphidden", resolve, { once: true });
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.panel.state != "open") {
 | 
						|
      await new Promise(resolve => {
 | 
						|
        this.panel.addEventListener("ViewShown", resolve, { once: true });
 | 
						|
        this.show();
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    let currentView;
 | 
						|
 | 
						|
    let viewShownCB = event => {
 | 
						|
      viewHidingCB();
 | 
						|
 | 
						|
      if (itemIds.length) {
 | 
						|
        let subItem = window.document.getElementById(itemIds[0]);
 | 
						|
        if (event.target.id == subItem?.closest("panelview")?.id) {
 | 
						|
          Services.tm.dispatchToMainThread(() => {
 | 
						|
            markItem(event.target);
 | 
						|
          });
 | 
						|
        } else {
 | 
						|
          itemIds = [];
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    let viewHidingCB = () => {
 | 
						|
      if (currentView) {
 | 
						|
        currentView.ignoreMouseMove = false;
 | 
						|
      }
 | 
						|
      currentView = null;
 | 
						|
    };
 | 
						|
 | 
						|
    let popupHiddenCB = () => {
 | 
						|
      viewHidingCB();
 | 
						|
      this.panel.removeEventListener("ViewShown", viewShownCB);
 | 
						|
    };
 | 
						|
 | 
						|
    let markItem = viewNode => {
 | 
						|
      let id = itemIds.shift();
 | 
						|
      let item = window.document.getElementById(id);
 | 
						|
      item.setAttribute("tabindex", "-1");
 | 
						|
 | 
						|
      currentView = PanelView.forNode(viewNode);
 | 
						|
      currentView.selectedElement = item;
 | 
						|
      currentView.focusSelectedElement(true);
 | 
						|
 | 
						|
      // Prevent the mouse from changing the highlight temporarily.
 | 
						|
      // This flag gets removed when the view is hidden or a key
 | 
						|
      // is pressed.
 | 
						|
      currentView.ignoreMouseMove = true;
 | 
						|
 | 
						|
      if (itemIds.length) {
 | 
						|
        this.panel.addEventListener("ViewShown", viewShownCB, { once: true });
 | 
						|
      }
 | 
						|
      this.panel.addEventListener("ViewHiding", viewHidingCB, { once: true });
 | 
						|
    };
 | 
						|
 | 
						|
    this.panel.addEventListener("popuphidden", popupHiddenCB, { once: true });
 | 
						|
    markItem(this.mainView);
 | 
						|
  },
 | 
						|
 | 
						|
  updateNotifications(notificationsChanged) {
 | 
						|
    let notifications = this._notifications;
 | 
						|
    if (!notifications || !notifications.length) {
 | 
						|
      if (notificationsChanged) {
 | 
						|
        this._clearAllNotifications();
 | 
						|
        this._hidePopup();
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (
 | 
						|
      (window.fullScreen && FullScreen.navToolboxHidden) ||
 | 
						|
      document.fullscreenElement ||
 | 
						|
      this._shouldSuppress()
 | 
						|
    ) {
 | 
						|
      this._hidePopup();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let doorhangers = notifications.filter(
 | 
						|
      n => !n.dismissed && !n.options.badgeOnly
 | 
						|
    );
 | 
						|
 | 
						|
    if (this.panel.state == "showing" || this.panel.state == "open") {
 | 
						|
      // If the menu is already showing, then we need to dismiss all
 | 
						|
      // notifications since we don't want their doorhangers competing for
 | 
						|
      // attention. Don't hide the badge though; it isn't really in competition
 | 
						|
      // with anything.
 | 
						|
      doorhangers.forEach(n => {
 | 
						|
        n.dismissed = true;
 | 
						|
        if (n.options.onDismissed) {
 | 
						|
          n.options.onDismissed(window);
 | 
						|
        }
 | 
						|
      });
 | 
						|
      this._hidePopup();
 | 
						|
      if (!notifications[0].options.badgeOnly) {
 | 
						|
        this._showBannerItem(notifications[0]);
 | 
						|
      }
 | 
						|
    } else if (doorhangers.length) {
 | 
						|
      // Only show the doorhanger if the window is focused and not fullscreen
 | 
						|
      if (
 | 
						|
        (window.fullScreen && this.autoHideToolbarInFullScreen) ||
 | 
						|
        Services.focus.activeWindow !== window
 | 
						|
      ) {
 | 
						|
        this._hidePopup();
 | 
						|
        this._showBadge(doorhangers[0]);
 | 
						|
        this._showBannerItem(doorhangers[0]);
 | 
						|
      } else {
 | 
						|
        this._clearBadge();
 | 
						|
        this._showNotificationPanel(doorhangers[0]);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this._hidePopup();
 | 
						|
      this._showBadge(notifications[0]);
 | 
						|
      this._showBannerItem(notifications[0]);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _showNotificationPanel(notification) {
 | 
						|
    this._refreshNotificationPanel(notification);
 | 
						|
 | 
						|
    if (this.isNotificationPanelOpen) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (notification.options.beforeShowDoorhanger) {
 | 
						|
      notification.options.beforeShowDoorhanger(document);
 | 
						|
    }
 | 
						|
 | 
						|
    let anchor = this._getPanelAnchor(this.menuButton);
 | 
						|
 | 
						|
    // Insert Fluent files when needed before notification is opened
 | 
						|
    MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
 | 
						|
    MozXULElement.insertFTLIfNeeded("browser/appMenuNotifications.ftl");
 | 
						|
 | 
						|
    // After Fluent files are loaded into document replace data-lazy-l10n-ids with actual ones
 | 
						|
    document
 | 
						|
      .getElementById("appMenu-notification-popup")
 | 
						|
      .querySelectorAll("[data-lazy-l10n-id]")
 | 
						|
      .forEach(el => {
 | 
						|
        el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
 | 
						|
        el.removeAttribute("data-lazy-l10n-id");
 | 
						|
      });
 | 
						|
 | 
						|
    this.notificationPanel.openPopup(anchor, "bottomright topright");
 | 
						|
  },
 | 
						|
 | 
						|
  _clearNotificationPanel() {
 | 
						|
    for (let popupnotification of this.notificationPanel.children) {
 | 
						|
      popupnotification.hidden = true;
 | 
						|
      popupnotification.notification = null;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _clearAllNotifications() {
 | 
						|
    this._clearNotificationPanel();
 | 
						|
    this._clearBadge();
 | 
						|
    this._clearBannerItem();
 | 
						|
  },
 | 
						|
 | 
						|
  get notificationPanel() {
 | 
						|
    // Lazy load the panic-button-success-notification panel the first time we need to display it.
 | 
						|
    if (!this._notificationPanel) {
 | 
						|
      let template = document.getElementById("appMenuNotificationTemplate");
 | 
						|
      template.replaceWith(template.content);
 | 
						|
      this._notificationPanel = document.getElementById(
 | 
						|
        "appMenu-notification-popup"
 | 
						|
      );
 | 
						|
      for (let event of this.kEvents) {
 | 
						|
        this._notificationPanel.addEventListener(event, this);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return this._notificationPanel;
 | 
						|
  },
 | 
						|
 | 
						|
  get mainView() {
 | 
						|
    if (!this._mainView) {
 | 
						|
      this._mainView = PanelMultiView.getViewNode(
 | 
						|
        document,
 | 
						|
        "appMenu-protonMainView"
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return this._mainView;
 | 
						|
  },
 | 
						|
 | 
						|
  get addonNotificationContainer() {
 | 
						|
    if (!this._addonNotificationContainer) {
 | 
						|
      this._addonNotificationContainer = PanelMultiView.getViewNode(
 | 
						|
        document,
 | 
						|
        "appMenu-proton-addon-banners"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    return this._addonNotificationContainer;
 | 
						|
  },
 | 
						|
 | 
						|
  _formatDescriptionMessage(n) {
 | 
						|
    let text = {};
 | 
						|
    let array = n.options.message.split("<>");
 | 
						|
    text.start = array[0] || "";
 | 
						|
    text.name = n.options.name || "";
 | 
						|
    text.end = array[1] || "";
 | 
						|
    return text;
 | 
						|
  },
 | 
						|
 | 
						|
  _refreshNotificationPanel(notification) {
 | 
						|
    this._clearNotificationPanel();
 | 
						|
 | 
						|
    let popupnotificationID = this._getPopupId(notification);
 | 
						|
    let popupnotification = document.getElementById(popupnotificationID);
 | 
						|
 | 
						|
    popupnotification.setAttribute("id", popupnotificationID);
 | 
						|
    popupnotification.setAttribute(
 | 
						|
      "buttoncommand",
 | 
						|
      "PanelUI._onNotificationButtonEvent(event, 'buttoncommand');"
 | 
						|
    );
 | 
						|
    popupnotification.setAttribute(
 | 
						|
      "secondarybuttoncommand",
 | 
						|
      "PanelUI._onNotificationButtonEvent(event, 'secondarybuttoncommand');"
 | 
						|
    );
 | 
						|
 | 
						|
    if (notification.options.message) {
 | 
						|
      let desc = this._formatDescriptionMessage(notification);
 | 
						|
      popupnotification.setAttribute("label", desc.start);
 | 
						|
      popupnotification.setAttribute("name", desc.name);
 | 
						|
      popupnotification.setAttribute("endlabel", desc.end);
 | 
						|
    }
 | 
						|
    if (notification.options.onRefresh) {
 | 
						|
      notification.options.onRefresh(window);
 | 
						|
    }
 | 
						|
    if (notification.options.popupIconURL) {
 | 
						|
      popupnotification.setAttribute("icon", notification.options.popupIconURL);
 | 
						|
      popupnotification.setAttribute("hasicon", true);
 | 
						|
    }
 | 
						|
    if (notification.options.learnMoreURL) {
 | 
						|
      popupnotification.setAttribute(
 | 
						|
        "learnmoreurl",
 | 
						|
        notification.options.learnMoreURL
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    popupnotification.notification = notification;
 | 
						|
    popupnotification.show();
 | 
						|
  },
 | 
						|
 | 
						|
  _showBadge(notification) {
 | 
						|
    let badgeStatus = this._getBadgeStatus(notification);
 | 
						|
    this.menuButton.setAttribute("badge-status", badgeStatus);
 | 
						|
  },
 | 
						|
 | 
						|
  // "Banner item" here refers to an item in the hamburger panel menu. They will
 | 
						|
  // typically show up as a colored row in the panel.
 | 
						|
  _showBannerItem(notification) {
 | 
						|
    const supportedIds = [
 | 
						|
      "update-downloading",
 | 
						|
      "update-available",
 | 
						|
      "update-manual",
 | 
						|
      "update-unsupported",
 | 
						|
      "update-restart",
 | 
						|
    ];
 | 
						|
    if (!supportedIds.includes(notification.id)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this._panelBannerItem) {
 | 
						|
      this._panelBannerItem = this.mainView.querySelector(".panel-banner-item");
 | 
						|
    }
 | 
						|
 | 
						|
    let l10nId = "appmenuitem-banner-" + notification.id;
 | 
						|
    document.l10n.setAttributes(this._panelBannerItem, l10nId);
 | 
						|
 | 
						|
    this._panelBannerItem.setAttribute("notificationid", notification.id);
 | 
						|
    this._panelBannerItem.hidden = false;
 | 
						|
    this._panelBannerItem.notification = notification;
 | 
						|
  },
 | 
						|
 | 
						|
  _clearBadge() {
 | 
						|
    this.menuButton.removeAttribute("badge-status");
 | 
						|
  },
 | 
						|
 | 
						|
  _clearBannerItem() {
 | 
						|
    if (this._panelBannerItem) {
 | 
						|
      this._panelBannerItem.notification = null;
 | 
						|
      this._panelBannerItem.hidden = true;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _onNotificationButtonEvent(event, type) {
 | 
						|
    let notificationEl = getNotificationFromElement(event.originalTarget);
 | 
						|
 | 
						|
    if (!notificationEl) {
 | 
						|
      throw new Error(
 | 
						|
        "PanelUI._onNotificationButtonEvent: couldn't find notification element"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    if (!notificationEl.notification) {
 | 
						|
      throw new Error(
 | 
						|
        "PanelUI._onNotificationButtonEvent: couldn't find notification"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    let notification = notificationEl.notification;
 | 
						|
 | 
						|
    if (type == "secondarybuttoncommand") {
 | 
						|
      AppMenuNotifications.callSecondaryAction(window, notification);
 | 
						|
    } else {
 | 
						|
      AppMenuNotifications.callMainAction(window, notification, true);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _onBannerItemSelected(event) {
 | 
						|
    let target = event.originalTarget;
 | 
						|
    if (!target.notification) {
 | 
						|
      throw new Error(
 | 
						|
        "menucommand target has no associated action/notification"
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    event.stopPropagation();
 | 
						|
    AppMenuNotifications.callMainAction(window, target.notification, false);
 | 
						|
  },
 | 
						|
 | 
						|
  _getPopupId(notification) {
 | 
						|
    return "appMenu-" + notification.id + "-notification";
 | 
						|
  },
 | 
						|
 | 
						|
  _getBadgeStatus(notification) {
 | 
						|
    return notification.id;
 | 
						|
  },
 | 
						|
 | 
						|
  _getPanelAnchor(candidate) {
 | 
						|
    let iconAnchor = candidate.badgeStack || candidate.icon;
 | 
						|
    return iconAnchor || candidate;
 | 
						|
  },
 | 
						|
 | 
						|
  _ensureShortcutsShown(view = this.mainView) {
 | 
						|
    if (view.hasAttribute("added-shortcuts")) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    view.setAttribute("added-shortcuts", "true");
 | 
						|
    for (let button of view.querySelectorAll("toolbarbutton[key]")) {
 | 
						|
      let keyId = button.getAttribute("key");
 | 
						|
      let key = document.getElementById(keyId);
 | 
						|
      if (!key) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      button.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(key));
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
 | 
						|
 | 
						|
/**
 | 
						|
 * Gets the currently selected locale for display.
 | 
						|
 * @return  the selected locale
 | 
						|
 */
 | 
						|
function getLocale() {
 | 
						|
  return Services.locale.appLocaleAsBCP47;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a DOM node inside a <popupnotification>, return the parent <popupnotification>.
 | 
						|
 */
 | 
						|
function getNotificationFromElement(aElement) {
 | 
						|
  return aElement.closest("popupnotification");
 | 
						|
}
 |