mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			670 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			670 lines
		
	
	
	
		
			21 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/. */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
// This is loaded into chrome windows with the subscript loader. Wrap in
 | 
						|
// a block to prevent accidentally leaking globals onto `window`.
 | 
						|
{
 | 
						|
  class MozTabbrowserTab extends MozElements.MozTab {
 | 
						|
    static markup = `
 | 
						|
      <stack class="tab-stack" flex="1">
 | 
						|
        <vbox class="tab-background">
 | 
						|
          <hbox class="tab-context-line"/>
 | 
						|
          <hbox class="tab-loading-burst" flex="1"/>
 | 
						|
        </vbox>
 | 
						|
        <hbox class="tab-content" align="center">
 | 
						|
          <stack class="tab-icon-stack">
 | 
						|
            <hbox class="tab-throbber"/>
 | 
						|
            <hbox class="tab-icon-pending"/>
 | 
						|
            <html:img class="tab-icon-image" role="presentation" decoding="sync" />
 | 
						|
            <image class="tab-sharing-icon-overlay" role="presentation"/>
 | 
						|
            <image class="tab-icon-overlay" role="presentation"/>
 | 
						|
          </stack>
 | 
						|
          <vbox class="tab-label-container"
 | 
						|
                onoverflow="this.setAttribute('textoverflow', 'true');"
 | 
						|
                onunderflow="this.removeAttribute('textoverflow');"
 | 
						|
                align="start"
 | 
						|
                pack="center"
 | 
						|
                flex="1">
 | 
						|
            <label class="tab-text tab-label" role="presentation"/>
 | 
						|
            <hbox class="tab-secondary-label">
 | 
						|
              <label class="tab-icon-sound-label tab-icon-sound-playing-label" data-l10n-id="browser-tab-audio-playing2" role="presentation"/>
 | 
						|
              <label class="tab-icon-sound-label tab-icon-sound-muted-label" data-l10n-id="browser-tab-audio-muted2" role="presentation"/>
 | 
						|
              <label class="tab-icon-sound-label tab-icon-sound-blocked-label" data-l10n-id="browser-tab-audio-blocked" role="presentation"/>
 | 
						|
              <label class="tab-icon-sound-label tab-icon-sound-pip-label" data-l10n-id="browser-tab-audio-pip" role="presentation"/>
 | 
						|
              <label class="tab-icon-sound-label tab-icon-sound-tooltip-label" role="presentation"/>
 | 
						|
            </hbox>
 | 
						|
          </vbox>
 | 
						|
          <image class="tab-close-button close-icon" role="presentation"/>
 | 
						|
        </hbox>
 | 
						|
      </stack>
 | 
						|
      `;
 | 
						|
 | 
						|
    constructor() {
 | 
						|
      super();
 | 
						|
 | 
						|
      this.addEventListener("mouseover", this);
 | 
						|
      this.addEventListener("mouseout", this);
 | 
						|
      this.addEventListener("dragstart", this, true);
 | 
						|
      this.addEventListener("dragstart", this);
 | 
						|
      this.addEventListener("mousedown", this);
 | 
						|
      this.addEventListener("mouseup", this);
 | 
						|
      this.addEventListener("click", this);
 | 
						|
      this.addEventListener("dblclick", this, true);
 | 
						|
      this.addEventListener("animationend", this);
 | 
						|
      this.addEventListener("focus", this);
 | 
						|
      this.addEventListener("AriaFocus", this);
 | 
						|
 | 
						|
      this._hover = false;
 | 
						|
      this._selectedOnFirstMouseDown = false;
 | 
						|
 | 
						|
      /**
 | 
						|
       * Describes how the tab ended up in this mute state. May be any of:
 | 
						|
       *
 | 
						|
       * - undefined: The tabs mute state has never changed.
 | 
						|
       * - null: The mute state was last changed through the UI.
 | 
						|
       * - Any string: The ID was changed through an extension API. The string
 | 
						|
       * must be the ID of the extension which changed it.
 | 
						|
       */
 | 
						|
      this.muteReason = undefined;
 | 
						|
 | 
						|
      this.mOverCloseButton = false;
 | 
						|
 | 
						|
      this.mCorrespondingMenuitem = null;
 | 
						|
 | 
						|
      this.closing = false;
 | 
						|
    }
 | 
						|
 | 
						|
    static get inheritedAttributes() {
 | 
						|
      return {
 | 
						|
        ".tab-background": "selected=visuallyselected,fadein,multiselected",
 | 
						|
        ".tab-line": "selected=visuallyselected,multiselected",
 | 
						|
        ".tab-loading-burst": "pinned,bursting,notselectedsinceload",
 | 
						|
        ".tab-content":
 | 
						|
          "pinned,selected=visuallyselected,titlechanged,attention",
 | 
						|
        ".tab-icon-stack":
 | 
						|
          "sharing,pictureinpicture,crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,indicator-replaces-favicon",
 | 
						|
        ".tab-throbber":
 | 
						|
          "fadein,pinned,busy,progress,selected=visuallyselected",
 | 
						|
        ".tab-icon-pending":
 | 
						|
          "fadein,pinned,busy,progress,selected=visuallyselected,pendingicon",
 | 
						|
        ".tab-icon-image":
 | 
						|
          "src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing,pictureinpicture",
 | 
						|
        ".tab-sharing-icon-overlay": "sharing,selected=visuallyselected,pinned",
 | 
						|
        ".tab-icon-overlay":
 | 
						|
          "sharing,pictureinpicture,crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,indicator-replaces-favicon",
 | 
						|
        ".tab-label-container":
 | 
						|
          "pinned,selected=visuallyselected,labeldirection",
 | 
						|
        ".tab-label":
 | 
						|
          "text=label,accesskey,fadein,pinned,selected=visuallyselected,attention",
 | 
						|
        ".tab-label-container .tab-secondary-label":
 | 
						|
          "soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,pictureinpicture",
 | 
						|
        ".tab-close-button": "fadein,pinned,selected=visuallyselected",
 | 
						|
      };
 | 
						|
    }
 | 
						|
 | 
						|
    connectedCallback() {
 | 
						|
      this.initialize();
 | 
						|
    }
 | 
						|
 | 
						|
    initialize() {
 | 
						|
      if (this._initialized) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      this.textContent = "";
 | 
						|
      this.appendChild(this.constructor.fragment);
 | 
						|
      this.initializeAttributeInheritance();
 | 
						|
      this.setAttribute("context", "tabContextMenu");
 | 
						|
      this._initialized = true;
 | 
						|
 | 
						|
      if (!("_lastAccessed" in this)) {
 | 
						|
        this.updateLastAccessed();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    get container() {
 | 
						|
      return gBrowser.tabContainer;
 | 
						|
    }
 | 
						|
 | 
						|
    set attention(val) {
 | 
						|
      if (val == this.hasAttribute("attention")) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      this.toggleAttribute("attention", val);
 | 
						|
      gBrowser._tabAttrModified(this, ["attention"]);
 | 
						|
    }
 | 
						|
 | 
						|
    set _visuallySelected(val) {
 | 
						|
      if (val == (this.getAttribute("visuallyselected") == "true")) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (val) {
 | 
						|
        this.setAttribute("visuallyselected", "true");
 | 
						|
      } else {
 | 
						|
        this.removeAttribute("visuallyselected");
 | 
						|
      }
 | 
						|
      gBrowser._tabAttrModified(this, ["visuallyselected"]);
 | 
						|
    }
 | 
						|
 | 
						|
    set _selected(val) {
 | 
						|
      // in e10s we want to only pseudo-select a tab before its rendering is done, so that
 | 
						|
      // the rest of the system knows that the tab is selected, but we don't want to update its
 | 
						|
      // visual status to selected until after we receive confirmation that its content has painted.
 | 
						|
      if (val) {
 | 
						|
        this.setAttribute("selected", "true");
 | 
						|
      } else {
 | 
						|
        this.removeAttribute("selected");
 | 
						|
      }
 | 
						|
 | 
						|
      // If we're non-e10s we should update the visual selection as well at the same time,
 | 
						|
      // *or* if we're e10s and the visually selected tab isn't changing, in which case the
 | 
						|
      // tab switcher code won't run and update anything else (like the before- and after-
 | 
						|
      // selected attributes).
 | 
						|
      if (
 | 
						|
        !gMultiProcessBrowser ||
 | 
						|
        (val && this.hasAttribute("visuallyselected"))
 | 
						|
      ) {
 | 
						|
        this._visuallySelected = val;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    get pinned() {
 | 
						|
      return this.getAttribute("pinned") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get hidden() {
 | 
						|
      // This getter makes `hidden` read-only
 | 
						|
      return super.hidden;
 | 
						|
    }
 | 
						|
 | 
						|
    get muted() {
 | 
						|
      return this.getAttribute("muted") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get multiselected() {
 | 
						|
      return this.getAttribute("multiselected") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get userContextId() {
 | 
						|
      return this.hasAttribute("usercontextid")
 | 
						|
        ? parseInt(this.getAttribute("usercontextid"))
 | 
						|
        : 0;
 | 
						|
    }
 | 
						|
 | 
						|
    get soundPlaying() {
 | 
						|
      return this.getAttribute("soundplaying") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get pictureinpicture() {
 | 
						|
      return this.getAttribute("pictureinpicture") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get activeMediaBlocked() {
 | 
						|
      return this.getAttribute("activemedia-blocked") == "true";
 | 
						|
    }
 | 
						|
 | 
						|
    get isEmpty() {
 | 
						|
      // Determines if a tab is "empty", usually used in the context of determining
 | 
						|
      // if it's ok to close the tab.
 | 
						|
      if (this.hasAttribute("busy")) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.hasAttribute("customizemode")) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      let browser = this.linkedBrowser;
 | 
						|
      if (!isBlankPageURL(browser.currentURI.spec)) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!BrowserUIUtils.checkEmptyPageOrigin(browser)) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (browser.canGoForward || browser.canGoBack) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    get lastAccessed() {
 | 
						|
      return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
 | 
						|
    }
 | 
						|
 | 
						|
    get _overPlayingIcon() {
 | 
						|
      return this.overlayIcon?.matches(":hover");
 | 
						|
    }
 | 
						|
 | 
						|
    get overlayIcon() {
 | 
						|
      return this.querySelector(".tab-icon-overlay");
 | 
						|
    }
 | 
						|
 | 
						|
    get throbber() {
 | 
						|
      return this.querySelector(".tab-throbber");
 | 
						|
    }
 | 
						|
 | 
						|
    get iconImage() {
 | 
						|
      return this.querySelector(".tab-icon-image");
 | 
						|
    }
 | 
						|
 | 
						|
    get sharingIcon() {
 | 
						|
      return this.querySelector(".tab-sharing-icon-overlay");
 | 
						|
    }
 | 
						|
 | 
						|
    get textLabel() {
 | 
						|
      return this.querySelector(".tab-label");
 | 
						|
    }
 | 
						|
 | 
						|
    get closeButton() {
 | 
						|
      return this.querySelector(".tab-close-button");
 | 
						|
    }
 | 
						|
 | 
						|
    updateLastAccessed(aDate) {
 | 
						|
      this._lastAccessed = this.selected ? Infinity : aDate || Date.now();
 | 
						|
    }
 | 
						|
 | 
						|
    updateLastUnloadedByTabUnloader() {
 | 
						|
      this._lastUnloaded = Date.now();
 | 
						|
      Services.telemetry.scalarAdd("browser.engagement.tab_unload_count", 1);
 | 
						|
    }
 | 
						|
 | 
						|
    recordTimeFromUnloadToReload() {
 | 
						|
      if (!this._lastUnloaded) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      const diff_in_msec = Date.now() - this._lastUnloaded;
 | 
						|
      Services.telemetry
 | 
						|
        .getHistogramById("TAB_UNLOAD_TO_RELOAD")
 | 
						|
        .add(diff_in_msec / 1000);
 | 
						|
      Services.telemetry.scalarAdd("browser.engagement.tab_reload_count", 1);
 | 
						|
      delete this._lastUnloaded;
 | 
						|
    }
 | 
						|
 | 
						|
    on_mouseover(event) {
 | 
						|
      if (event.target.classList.contains("tab-close-button")) {
 | 
						|
        this.mOverCloseButton = true;
 | 
						|
      }
 | 
						|
      if (this._overPlayingIcon) {
 | 
						|
        const selectedTabs = gBrowser.selectedTabs;
 | 
						|
        const contextTabInSelection = selectedTabs.includes(this);
 | 
						|
        const affectedTabsLength = contextTabInSelection
 | 
						|
          ? selectedTabs.length
 | 
						|
          : 1;
 | 
						|
        let stringID;
 | 
						|
        if (this.hasAttribute("activemedia-blocked")) {
 | 
						|
          stringID = "browser-tab-unblock";
 | 
						|
        } else {
 | 
						|
          stringID = this.linkedBrowser.audioMuted
 | 
						|
            ? "browser-tab-unmute"
 | 
						|
            : "browser-tab-mute";
 | 
						|
        }
 | 
						|
        this.setSecondaryTabTooltipLabel(stringID, {
 | 
						|
          count: affectedTabsLength,
 | 
						|
        });
 | 
						|
      }
 | 
						|
      this._mouseenter();
 | 
						|
    }
 | 
						|
 | 
						|
    on_mouseout(event) {
 | 
						|
      if (event.target.classList.contains("tab-close-button")) {
 | 
						|
        this.mOverCloseButton = false;
 | 
						|
      }
 | 
						|
      if (event.target == this.overlayIcon) {
 | 
						|
        this.setSecondaryTabTooltipLabel(null);
 | 
						|
      }
 | 
						|
      this._mouseleave();
 | 
						|
    }
 | 
						|
 | 
						|
    on_dragstart(event) {
 | 
						|
      // We use "failed" drag end events that weren't cancelled by the user
 | 
						|
      // to detach tabs. Ensure that we do not show the drag image returning
 | 
						|
      // to its point of origin when this happens, as it makes the drag
 | 
						|
      // finishing feel very slow.
 | 
						|
      event.dataTransfer.mozShowFailAnimation = false;
 | 
						|
      if (event.eventPhase == Event.CAPTURING_PHASE) {
 | 
						|
        this.style.MozUserFocus = "";
 | 
						|
      } else if (
 | 
						|
        this.mOverCloseButton ||
 | 
						|
        gSharedTabWarning.willShowSharedTabWarning(this)
 | 
						|
      ) {
 | 
						|
        event.stopPropagation();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_mousedown(event) {
 | 
						|
      let eventMaySelectTab = true;
 | 
						|
      let tabContainer = this.container;
 | 
						|
 | 
						|
      if (
 | 
						|
        tabContainer._closeTabByDblclick &&
 | 
						|
        event.button == 0 &&
 | 
						|
        event.detail == 1
 | 
						|
      ) {
 | 
						|
        this._selectedOnFirstMouseDown = this.selected;
 | 
						|
      }
 | 
						|
 | 
						|
      if (this.selected) {
 | 
						|
        this.style.MozUserFocus = "ignore";
 | 
						|
      } else if (
 | 
						|
        event.target.classList.contains("tab-close-button") ||
 | 
						|
        event.target.classList.contains("tab-icon-overlay")
 | 
						|
      ) {
 | 
						|
        eventMaySelectTab = false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (event.button == 1) {
 | 
						|
        gBrowser.warmupTab(gBrowser._findTabToBlurTo(this));
 | 
						|
      }
 | 
						|
 | 
						|
      if (event.button == 0) {
 | 
						|
        let shiftKey = event.shiftKey;
 | 
						|
        let accelKey = event.getModifierState("Accel");
 | 
						|
        if (shiftKey) {
 | 
						|
          eventMaySelectTab = false;
 | 
						|
          const lastSelectedTab = gBrowser.lastMultiSelectedTab;
 | 
						|
          if (!accelKey) {
 | 
						|
            gBrowser.selectedTab = lastSelectedTab;
 | 
						|
 | 
						|
            // Make sure selection is cleared when tab-switch doesn't happen.
 | 
						|
            gBrowser.clearMultiSelectedTabs();
 | 
						|
          }
 | 
						|
          gBrowser.addRangeToMultiSelectedTabs(lastSelectedTab, this);
 | 
						|
        } else if (accelKey) {
 | 
						|
          // Ctrl (Cmd for mac) key is pressed
 | 
						|
          eventMaySelectTab = false;
 | 
						|
          if (this.multiselected) {
 | 
						|
            gBrowser.removeFromMultiSelectedTabs(this);
 | 
						|
          } else if (this != gBrowser.selectedTab) {
 | 
						|
            gBrowser.addToMultiSelectedTabs(this);
 | 
						|
            gBrowser.lastMultiSelectedTab = this;
 | 
						|
          }
 | 
						|
        } else if (!this.selected && this.multiselected) {
 | 
						|
          gBrowser.lockClearMultiSelectionOnce();
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (gSharedTabWarning.willShowSharedTabWarning(this)) {
 | 
						|
        eventMaySelectTab = false;
 | 
						|
      }
 | 
						|
 | 
						|
      if (eventMaySelectTab) {
 | 
						|
        super.on_mousedown(event);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_mouseup(event) {
 | 
						|
      // Make sure that clear-selection is released.
 | 
						|
      // Otherwise selection using Shift key may be broken.
 | 
						|
      gBrowser.unlockClearMultiSelection();
 | 
						|
 | 
						|
      this.style.MozUserFocus = "";
 | 
						|
    }
 | 
						|
 | 
						|
    on_click(event) {
 | 
						|
      if (event.button != 0) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (event.getModifierState("Accel") || event.shiftKey) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (
 | 
						|
        gBrowser.multiSelectedTabsCount > 0 &&
 | 
						|
        !event.target.classList.contains("tab-close-button") &&
 | 
						|
        !event.target.classList.contains("tab-icon-overlay")
 | 
						|
      ) {
 | 
						|
        // Tabs were previously multi-selected and user clicks on a tab
 | 
						|
        // without holding Ctrl/Cmd Key
 | 
						|
        gBrowser.clearMultiSelectedTabs();
 | 
						|
      }
 | 
						|
 | 
						|
      if (event.target.classList.contains("tab-icon-overlay")) {
 | 
						|
        if (this.activeMediaBlocked) {
 | 
						|
          if (this.multiselected) {
 | 
						|
            gBrowser.resumeDelayedMediaOnMultiSelectedTabs(this);
 | 
						|
          } else {
 | 
						|
            this.resumeDelayedMedia();
 | 
						|
          }
 | 
						|
        } else if (this.soundPlaying || this.muted) {
 | 
						|
          if (this.multiselected) {
 | 
						|
            gBrowser.toggleMuteAudioOnMultiSelectedTabs(this);
 | 
						|
          } else {
 | 
						|
            this.toggleMuteAudio();
 | 
						|
          }
 | 
						|
        }
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (event.target.classList.contains("tab-close-button")) {
 | 
						|
        if (this.multiselected) {
 | 
						|
          gBrowser.removeMultiSelectedTabs();
 | 
						|
        } else {
 | 
						|
          gBrowser.removeTab(this, {
 | 
						|
            animate: true,
 | 
						|
            triggeringEvent: event,
 | 
						|
          });
 | 
						|
        }
 | 
						|
        // This enables double-click protection for the tab container
 | 
						|
        // (see tabbrowser-tabs 'click' handler).
 | 
						|
        gBrowser.tabContainer._blockDblClick = true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_dblclick(event) {
 | 
						|
      if (event.button != 0) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // for the one-close-button case
 | 
						|
      if (event.target.classList.contains("tab-close-button")) {
 | 
						|
        event.stopPropagation();
 | 
						|
      }
 | 
						|
 | 
						|
      let tabContainer = this.container;
 | 
						|
      if (
 | 
						|
        tabContainer._closeTabByDblclick &&
 | 
						|
        this._selectedOnFirstMouseDown &&
 | 
						|
        this.selected &&
 | 
						|
        !event.target.classList.contains("tab-icon-overlay")
 | 
						|
      ) {
 | 
						|
        gBrowser.removeTab(this, {
 | 
						|
          animate: true,
 | 
						|
          triggeringEvent: event,
 | 
						|
        });
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    on_animationend(event) {
 | 
						|
      if (event.target.classList.contains("tab-loading-burst")) {
 | 
						|
        this.removeAttribute("bursting");
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    _mouseenter() {
 | 
						|
      if (this.hidden || this.closing) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      this._hover = true;
 | 
						|
 | 
						|
      if (this.selected) {
 | 
						|
        this.container._handleTabSelect();
 | 
						|
      } else if (this.linkedPanel) {
 | 
						|
        this.linkedBrowser.unselectedTabHover(true);
 | 
						|
        this.startUnselectedTabHoverTimer();
 | 
						|
      }
 | 
						|
 | 
						|
      // Prepare connection to host beforehand.
 | 
						|
      SessionStore.speculativeConnectOnTabHover(this);
 | 
						|
 | 
						|
      let tabToWarm = this;
 | 
						|
      if (this.mOverCloseButton) {
 | 
						|
        tabToWarm = gBrowser._findTabToBlurTo(this);
 | 
						|
      }
 | 
						|
      gBrowser.warmupTab(tabToWarm);
 | 
						|
    }
 | 
						|
 | 
						|
    _mouseleave() {
 | 
						|
      if (!this._hover) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      this._hover = false;
 | 
						|
      if (this.linkedPanel && !this.selected) {
 | 
						|
        this.linkedBrowser.unselectedTabHover(false);
 | 
						|
        this.cancelUnselectedTabHoverTimer();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    setSecondaryTabTooltipLabel(l10nID, l10nArgs) {
 | 
						|
      this.querySelector(".tab-secondary-label").toggleAttribute(
 | 
						|
        "showtooltip",
 | 
						|
        l10nID
 | 
						|
      );
 | 
						|
 | 
						|
      const tooltipEl = this.querySelector(".tab-icon-sound-tooltip-label");
 | 
						|
 | 
						|
      if (l10nArgs) {
 | 
						|
        tooltipEl.setAttribute("data-l10n-args", JSON.stringify(l10nArgs));
 | 
						|
      } else {
 | 
						|
        tooltipEl.removeAttribute("data-l10n-args");
 | 
						|
      }
 | 
						|
      if (l10nID) {
 | 
						|
        tooltipEl.setAttribute("data-l10n-id", l10nID);
 | 
						|
      } else {
 | 
						|
        tooltipEl.removeAttribute("data-l10n-id");
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    startUnselectedTabHoverTimer() {
 | 
						|
      // Only record data when we need to.
 | 
						|
      if (!this.linkedBrowser.shouldHandleUnselectedTabHover) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (
 | 
						|
        !TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)
 | 
						|
      ) {
 | 
						|
        TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
 | 
						|
      }
 | 
						|
 | 
						|
      if (this._hoverTabTimer) {
 | 
						|
        clearTimeout(this._hoverTabTimer);
 | 
						|
        this._hoverTabTimer = null;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    cancelUnselectedTabHoverTimer() {
 | 
						|
      // Since we're listening "mouseout" event, instead of "mouseleave".
 | 
						|
      // Every time the cursor is moving from the tab to its child node (icon),
 | 
						|
      // it would dispatch "mouseout"(for tab) first and then dispatch
 | 
						|
      // "mouseover" (for icon, eg: close button, speaker icon) soon.
 | 
						|
      // It causes we would cancel present TelemetryStopwatch immediately
 | 
						|
      // when cursor is moving on the icon, and then start a new one.
 | 
						|
      // In order to avoid this situation, we could delay cancellation and
 | 
						|
      // remove it if we get "mouseover" within very short period.
 | 
						|
      this._hoverTabTimer = setTimeout(() => {
 | 
						|
        if (
 | 
						|
          TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)
 | 
						|
        ) {
 | 
						|
          TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
 | 
						|
        }
 | 
						|
      }, 100);
 | 
						|
    }
 | 
						|
 | 
						|
    finishUnselectedTabHoverTimer() {
 | 
						|
      // Stop timer when the tab is opened.
 | 
						|
      if (
 | 
						|
        TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)
 | 
						|
      ) {
 | 
						|
        TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    resumeDelayedMedia() {
 | 
						|
      if (this.activeMediaBlocked) {
 | 
						|
        Services.telemetry
 | 
						|
          .getHistogramById("TAB_AUDIO_INDICATOR_USED")
 | 
						|
          .add(3 /* unblockByClickingIcon */);
 | 
						|
        this.removeAttribute("activemedia-blocked");
 | 
						|
        this.linkedBrowser.resumeMedia();
 | 
						|
        gBrowser._tabAttrModified(this, ["activemedia-blocked"]);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    toggleMuteAudio(aMuteReason) {
 | 
						|
      let browser = this.linkedBrowser;
 | 
						|
      let hist = Services.telemetry.getHistogramById(
 | 
						|
        "TAB_AUDIO_INDICATOR_USED"
 | 
						|
      );
 | 
						|
 | 
						|
      if (browser.audioMuted) {
 | 
						|
        if (this.linkedPanel) {
 | 
						|
          // "Lazy Browser" should not invoke its unmute method
 | 
						|
          browser.unmute();
 | 
						|
        }
 | 
						|
        this.removeAttribute("muted");
 | 
						|
        hist.add(1 /* unmute */);
 | 
						|
      } else {
 | 
						|
        if (this.linkedPanel) {
 | 
						|
          // "Lazy Browser" should not invoke its mute method
 | 
						|
          browser.mute();
 | 
						|
        }
 | 
						|
        this.setAttribute("muted", "true");
 | 
						|
        hist.add(0 /* mute */);
 | 
						|
      }
 | 
						|
      this.muteReason = aMuteReason || null;
 | 
						|
 | 
						|
      gBrowser._tabAttrModified(this, ["muted"]);
 | 
						|
    }
 | 
						|
 | 
						|
    setUserContextId(aUserContextId) {
 | 
						|
      if (aUserContextId) {
 | 
						|
        if (this.linkedBrowser) {
 | 
						|
          this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
 | 
						|
        }
 | 
						|
        this.setAttribute("usercontextid", aUserContextId);
 | 
						|
      } else {
 | 
						|
        if (this.linkedBrowser) {
 | 
						|
          this.linkedBrowser.removeAttribute("usercontextid");
 | 
						|
        }
 | 
						|
        this.removeAttribute("usercontextid");
 | 
						|
      }
 | 
						|
 | 
						|
      ContextualIdentityService.setTabStyle(this);
 | 
						|
    }
 | 
						|
 | 
						|
    updateA11yDescription() {
 | 
						|
      let prevDescTab = gBrowser.tabContainer.querySelector(
 | 
						|
        "tab[aria-describedby]"
 | 
						|
      );
 | 
						|
      if (prevDescTab) {
 | 
						|
        // We can only have a description for the focused tab.
 | 
						|
        prevDescTab.removeAttribute("aria-describedby");
 | 
						|
      }
 | 
						|
      let desc = document.getElementById("tabbrowser-tab-a11y-desc");
 | 
						|
      desc.textContent = gBrowser.getTabTooltip(this, false);
 | 
						|
      this.setAttribute("aria-describedby", "tabbrowser-tab-a11y-desc");
 | 
						|
    }
 | 
						|
 | 
						|
    on_focus(event) {
 | 
						|
      this.updateA11yDescription();
 | 
						|
    }
 | 
						|
 | 
						|
    on_AriaFocus(event) {
 | 
						|
      this.updateA11yDescription();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  customElements.define("tabbrowser-tab", MozTabbrowserTab, {
 | 
						|
    extends: "tab",
 | 
						|
  });
 | 
						|
}
 |