this._tabMinWidth = newValue,
            newValue => {
              const LIMIT = 50;
              return Math.max(newValue, LIMIT);
            },
          );
          this._tabMinWidth = this._tabMinWidthPref;
          XPCOMUtils.defineLazyPreferenceGetter(this, "_multiselectEnabledPref",
            "browser.tabs.multiselect", null,
            (pref, prevValue, newValue) => this._multiselectEnabled = newValue);
          this._multiselectEnabled = this._multiselectEnabledPref;
          this._setPositionalAttributes();
          CustomizableUI.addListener(this);
          this._updateNewTabVisibility();
          XPCOMUtils.defineLazyPreferenceGetter(this, "_closeTabByDblclick",
            "browser.tabs.closeTabByDblclick", false);
          if (gMultiProcessBrowser) {
            this.tabbox.tabpanels.setAttribute("async", "true");
          }
        ]]>
      
      
        
      
      
        document.getElementById("tabbrowser-tabbox");
      
      
        document.getElementById("tabContextMenu");
      
      
        document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
      
      null
      null
      null
      null
      null
      null
      
        
          this.style.setProperty("--tab-min-width", val + "px");
          return val;
        
      
      
        
          // Unlike boolean HTML attributes, the value of boolean ARIA attributes actually matters.
          this.setAttribute("aria-multiselectable", !!val);
          return val;
        
        
          return this.getAttribute("aria-multiselectable") == "true";
        
      
      
        
        
        
         2)) {
                containersEnabled = false;
              }
              const newTab = document.getElementById("new-tab-button");
              const newTab2 = document.getAnonymousElementByAttribute(this, "anonid", "tabs-newtab-button");
              for (let parent of [newTab, newTab2]) {
                if (!parent)
                  continue;
                gClickAndHoldListenersOnElement.remove(parent);
                parent.removeAttribute("type");
                if (parent.firstElementChild) {
                  parent.firstElementChild.remove();
                }
                if (containersEnabled) {
                  let popup = document.createElementNS(
                                "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                "menupopup");
                  if (parent.id) {
                    popup.id = "newtab-popup";
                  } else {
                    popup.setAttribute("anonid", "newtab-popup");
                  }
                  popup.className = "new-tab-popup";
                  popup.setAttribute("position", "after_end");
                  popup.addEventListener("popupshowing", event => {
                    createUserContextMenu(event, {
                      useAccessKeys: false,
                      showDefaultTab: Services.prefs.getIntPref("privacy.userContext.longPressBehavior") == 1,
                    });
                  });
                  parent.appendChild(popup);
                  // longPressBehavior == 2 means that the menu is shown after X
                  // millisecs. Otherwise, with 1, the menu is open immediatelly.
                  if (longPressBehavior == 2) {
                    gClickAndHoldListenersOnElement.add(parent);
                  }
                  parent.setAttribute("type", "menu");
                }
              }
              break;
          }
        ]]>
      
      
        
      
      
        
      
      
        
      
      false
      
        document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
      
      350
      0
      false
      
         {
            window.requestAnimationFrame(() => {
              this._closeButtonsUpdatePending = false;
              // The scrollbox may have started overflowing since we checked
              // overflow earlier, so check again.
              if (this.getAttribute("overflow") == "true") {
                this.setAttribute("closebuttons", "activetab");
                return;
              }
              // Check if tab widths are below the threshold where we want to
              // remove close buttons from background tabs so that people don't
              // accidentally close tabs by selecting them.
              let rect = ele => {
                return window.windowUtils.getBoundsWithoutFlushing(ele);
              };
              let tab = this._getVisibleTabs()[gBrowser._numPinnedTabs];
              if (tab && rect(tab).width <= this._tabClipWidth) {
                this.setAttribute("closebuttons", "activetab");
              } else {
                this.removeAttribute("closebuttons");
              }
            });
          });
        ]]>
      
      
        
      
      
        
        
      
      
        document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
      
      NaN
      false
      false
      
      
        
        
         tabs[tabs.length - 1]._tPos);
          if (!this._tabDefaultMaxWidth) {
            this._tabDefaultMaxWidth =
              parseFloat(window.getComputedStyle(aTab).maxWidth);
          }
          this._lastTabClosedByMouse = true;
          if (this.getAttribute("overflow") == "true") {
            // Don't need to do anything if we're in overflow mode and aren't scrolled
            // all the way to the right, or if we're closing the last tab.
            if (isEndTab || !this.arrowScrollbox._scrollButtonDown.disabled) {
              return;
            }
            // If the tab has an owner that will become the active tab, the owner will
            // be to the left of it, so we actually want the left tab to slide over.
            // This can't be done as easily in non-overflow mode, so we don't bother.
            if (aTab.owner) {
              return;
            }
            this._expandSpacerBy(aTabWidth);
          } else { // non-overflow mode
            // Locking is neither in effect nor needed, so let tabs expand normally.
            if (isEndTab && !this._hasTabTempMaxWidth) {
              return;
            }
            let numPinned = gBrowser._numPinnedTabs;
            // Force tabs to stay the same width, unless we're closing the last tab,
            // which case we need to let them expand just enough so that the overall
            // tabbar width is the same.
            if (isEndTab) {
              let numNormalTabs = tabs.length - numPinned;
              aTabWidth = aTabWidth * (numNormalTabs + 1) / numNormalTabs;
              if (aTabWidth > this._tabDefaultMaxWidth) {
                aTabWidth = this._tabDefaultMaxWidth;
              }
            }
            aTabWidth += "px";
            let tabsToReset = [];
            for (let i = numPinned; i < tabs.length; i++) {
              let tab = tabs[i];
              tab.style.setProperty("max-width", aTabWidth, "important");
              if (!isEndTab) { // keep tabs the same width
                tab.style.transition = "none";
                tabsToReset.push(tab);
              }
            }
            if (tabsToReset.length) {
              window.promiseDocumentFlushed(() => {}).then(() => {
                window.requestAnimationFrame(() => {
                  for (let tab of tabsToReset) {
                    tab.style.transition = "";
                  }
                });
              });
            }
            this._hasTabTempMaxWidth = true;
            gBrowser.addEventListener("mousemove", this);
            window.addEventListener("mouseout", this);
          }
        ]]>
      
      
        
        
      
      
        
      
      
        
      
      0
      null
      
         numPinned &&
                           numPinned > 0;
          if (doPosition) {
            this.setAttribute("positionpinnedtabs", "true");
            let layoutData = this._pinnedTabsLayoutCache;
            let uiDensity = document.documentElement.getAttribute("uidensity");
            if (!layoutData ||
                layoutData.uiDensity != uiDensity) {
              let arrowScrollbox = this.arrowScrollbox;
              layoutData = this._pinnedTabsLayoutCache = {
                uiDensity,
                pinnedTabWidth: this.children[0].getBoundingClientRect().width,
                scrollButtonWidth: arrowScrollbox._scrollButtonDown.getBoundingClientRect().width,
              };
            }
            let width = 0;
            for (let i = numPinned - 1; i >= 0; i--) {
              let tab = this.children[i];
              width += layoutData.pinnedTabWidth;
              tab.style.setProperty("margin-inline-start",
                -(width + layoutData.scrollButtonWidth) + "px", "important");
              tab._pinnedUnscrollable = true;
            }
            this.style.paddingInlineStart = width + "px";
          } else {
            this.removeAttribute("positionpinnedtabs");
            for (let i = 0; i < numPinned; i++) {
              let tab = this.children[i];
              tab.style.marginInlineStart = "";
              tab._pinnedUnscrollable = false;
            }
            this.style.paddingInlineStart = "";
          }
          if (this._lastNumPinned != numPinned) {
            this._lastNumPinned = numPinned;
            this._handleTabSelect(true);
          }
        ]]>
      
      
        
         draggedTab._dragData.animLastScreenX;
          draggedTab._dragData.animLastScreenX = screenX;
          let pinned = draggedTab.pinned;
          let numPinned = gBrowser._numPinnedTabs;
          let tabs = this._getVisibleTabs()
                         .slice(pinned ? 0 : numPinned,
                                pinned ? numPinned : undefined);
          if (RTL_UI) {
            tabs.reverse();
            // Copy moving tabs array to avoid infinite reversing.
            movingTabs = [...movingTabs].reverse();
          }
          let tabWidth = draggedTab.getBoundingClientRect().width;
          let shiftWidth = tabWidth * movingTabs.length;
          draggedTab._dragData.tabWidth = tabWidth;
          // Move the dragged tab based on the mouse position.
          let leftTab = tabs[0];
          let rightTab = tabs[tabs.length - 1];
          let rightMovingTabScreenX = movingTabs[movingTabs.length - 1].screenX;
          let leftMovingTabScreenX = movingTabs[0].screenX;
          let translateX = screenX - draggedTab._dragData.screenX;
          if (!pinned) {
            translateX += this.arrowScrollbox.scrollbox.scrollLeft - draggedTab._dragData.scrollX;
          }
          let leftBound = leftTab.screenX - leftMovingTabScreenX;
          let rightBound = (rightTab.screenX + rightTab.boxObject.width) -
                           (rightMovingTabScreenX + tabWidth);
          translateX = Math.min(Math.max(translateX, leftBound), rightBound);
          for (let tab of movingTabs) {
            tab.style.transform = "translateX(" + translateX + "px)";
          }
          draggedTab._dragData.translateX = translateX;
          // Determine what tab we're dragging over.
          // * Single tab dragging: Point of reference is the center of the dragged tab. If that
          //   point touches a background tab, the dragged tab would take that
          //   tab's position when dropped.
          // * Multiple tabs dragging: All dragged tabs are one "giant" tab with two
          //   points of reference (center of tabs on the extremities). When
          //   mouse is moving from left to right, the right reference gets activated,
          //   otherwise the left reference will be used. Everything else works the same
          //   as single tab dragging.
          // * We're doing a binary search in order to reduce the amount of
          //   tabs we need to check.
          tabs = tabs.filter(t => !movingTabs.includes(t) || t == draggedTab);
          let leftTabCenter = leftMovingTabScreenX + translateX + tabWidth / 2;
          let rightTabCenter = rightMovingTabScreenX + translateX + tabWidth / 2;
          let tabCenter = ltrMove ? rightTabCenter : leftTabCenter;
          let newIndex = -1;
          let oldIndex = "animDropIndex" in draggedTab._dragData ?
                         draggedTab._dragData.animDropIndex : movingTabs[0]._tPos;
          let low = 0;
          let high = tabs.length - 1;
          while (low <= high) {
            let mid = Math.floor((low + high) / 2);
            if (tabs[mid] == draggedTab && ++mid > high)
              break;
            screenX = tabs[mid].screenX + getTabShift(tabs[mid], oldIndex);
            if (screenX > tabCenter) {
              high = mid - 1;
            } else if (screenX + tabs[mid].getBoundingClientRect().width < tabCenter) {
              low = mid + 1;
            } else {
              newIndex = tabs[mid]._tPos;
              break;
            }
          }
          if (newIndex >= oldIndex)
            newIndex++;
          if (newIndex < 0 || newIndex == oldIndex)
            return;
          draggedTab._dragData.animDropIndex = newIndex;
          // Shift background tabs to leave a gap where the dragged tab
          // would currently be dropped.
          for (let tab of tabs) {
            if (tab != draggedTab) {
              let shift = getTabShift(tab, newIndex);
              tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
            }
          }
          function getTabShift(tab, dropIndex) {
            if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
              return (RTL_UI ? -shiftWidth : shiftWidth);
            if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
              return (RTL_UI ? shiftWidth : -shiftWidth);
            return 0;
          }
        ]]>
      
      
        
      
      
      
        
         -1; i--) {
            let movingTab = selectedTabs[i];
            insertAtPos = newIndex(movingTab, insertAtPos);
            if (animate) {
              movingTab.groupingTabsData = {};
              addAnimationData(movingTab, insertAtPos, "left");
            } else {
              gBrowser.moveTabTo(movingTab, insertAtPos);
            }
            insertAtPos--;
          }
          // Animate right selected tabs
          insertAtPos = draggedTabPos + 1;
          for (let i = selectedTabs.indexOf(tab) + 1; i < selectedTabs.length; i++) {
            let movingTab = selectedTabs[i];
            insertAtPos = newIndex(movingTab, insertAtPos);
            if (animate) {
              movingTab.groupingTabsData = {};
              addAnimationData(movingTab, insertAtPos, "right");
            } else {
              gBrowser.moveTabTo(movingTab, insertAtPos);
            }
            insertAtPos++;
          }
          // Slide the relevant tabs to their new position.
          for (let t of this._getVisibleTabs()) {
            if (t.groupingTabsData && t.groupingTabsData.translateX) {
              let translateX = (RTL_UI ? -1 : 1) * t.groupingTabsData.translateX;
              t.style.transform = "translateX(" + translateX + "px)";
            }
          }
          function newIndex(aTab, index) {
            // Don't allow mixing pinned and unpinned tabs.
            if (aTab.pinned) {
              return Math.min(index, gBrowser._numPinnedTabs - 1);
            }
            return Math.max(index, gBrowser._numPinnedTabs);
          }
          function addAnimationData(movingTab, movingTabNewIndex, side) {
            let movingTabOldIndex = movingTab._tPos;
            if (movingTabOldIndex == movingTabNewIndex) {
              // movingTab is already at the right position
              // and thus don't need to be animated.
              return;
            }
            let movingTabWidth = movingTab.boxObject.width;
            let shift = (movingTabNewIndex - movingTabOldIndex) * movingTabWidth;
            movingTab.groupingTabsData.animate = true;
            movingTab.setAttribute("tab-grouping", "true");
            movingTab.groupingTabsData.translateX = shift;
            let onTransitionEnd = transitionendEvent => {
              if (transitionendEvent.propertyName != "transform" ||
                  transitionendEvent.originalTarget != movingTab) {
                return;
              }
              movingTab.removeEventListener("transitionend", onTransitionEnd);
              movingTab.groupingTabsData.newIndex = movingTabNewIndex;
              movingTab.groupingTabsData.animate = false;
            };
            movingTab.addEventListener("transitionend", onTransitionEnd);
            // Add animation data for tabs between movingTab (selected
            // tab moving towards the dragged tab) and draggedTab.
            // Those tabs in the middle should move in
            // the opposite direction of movingTab.
            let lowerIndex = Math.min(movingTabOldIndex, draggedTabPos);
            let higherIndex = Math.max(movingTabOldIndex, draggedTabPos);
            for (let i = lowerIndex + 1; i < higherIndex; i++) {
              let middleTab = gBrowser.visibleTabs[i];
              if (middleTab.pinned != movingTab.pinned) {
                // Don't mix pinned and unpinned tabs
                break;
              }
              if (middleTab.multiselected) {
                // Skip because this selected tab should
                // be shifted towards the dragged Tab.
                continue;
              }
              if (!middleTab.groupingTabsData || !middleTab.groupingTabsData.translateX) {
                middleTab.groupingTabsData = { translateX: 0};
              }
              if (side == "left") {
                middleTab.groupingTabsData.translateX -= movingTabWidth;
              } else {
                middleTab.groupingTabsData.translateX += movingTabWidth;
              }
              middleTab.setAttribute("tab-grouping", "true");
            }
          }
        ]]>
      
      
        
         -1; i--) {
            let movingTab = selectedTabs[i];
            if (movingTab.groupingTabsData.newIndex) {
              gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
            }
          }
          // Moving right tabs
          for (let i = tabIndex + 1; i < selectedTabs.length; i++) {
            let movingTab = selectedTabs[i];
            if (movingTab.groupingTabsData.newIndex) {
              gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
            }
          }
          for (let t of this._getVisibleTabs()) {
            t.style.transform = "";
            t.removeAttribute("tab-grouping");
            delete t.groupingTabsData;
          }
        ]]>
      
      
        
      
      
        
        
      
      
        this.arrowScrollbox._scrollButtonDown;
      
      
        
         {
              let lastTabRect = this._lastTabToScrollIntoView.getBoundingClientRect();
              let selectedTab = this.selectedItem;
              if (selectedTab.pinned) {
                selectedTab = null;
              } else {
                selectedTab = selectedTab.getBoundingClientRect();
                selectedTab = {left: selectedTab.left, right: selectedTab.right};
              }
              return [
                this._lastTabToScrollIntoView,
                this.arrowScrollbox.scrollClientRect,
                {left: lastTabRect.left, right: lastTabRect.right},
                selectedTab,
              ];
            }).then(([tabUsed, scrollRect, tabRect, selectedRect]) => {
              // First off, remove the promise so we can re-enter if necessary.
              delete this._backgroundTabScrollPromise;
              // Then, if the layout info isn't for the last-scrolled-to-tab, re-run
              // the code above to get layout info for *that* tab, and don't do
              // anything here, as we really just want to run this for the last-opened tab.
              if (this._lastTabToScrollIntoView != tabUsed) {
                this._notifyBackgroundTab(this._lastTabToScrollIntoView);
                return;
              }
              delete this._lastTabToScrollIntoView;
              // Is the new tab already completely visible?
              if (scrollRect.left <= tabRect.left && tabRect.right <= scrollRect.right)
                return;
              if (this.arrowScrollbox.smoothScroll) {
                // Can we make both the new tab and the selected tab completely visible?
                if (!selectedRect ||
                    Math.max(tabRect.right - selectedRect.left, selectedRect.right - tabRect.left) <=
                      scrollRect.width) {
                  this.arrowScrollbox.ensureElementIsVisible(aTab);
                  return;
                }
                this.arrowScrollbox.scrollByPixels(RTL_UI ?
                                                     selectedRect.right - scrollRect.right :
                                                     selectedRect.left - scrollRect.left);
              }
              if (!this._animateElement.hasAttribute("highlight")) {
                this._animateElement.setAttribute("highlight", "true");
                setTimeout(function(ele) {
                  ele.removeAttribute("highlight");
                }, 150, this._animateElement);
              }
            });
          }
        ]]>
      
      
        
        
         tab.screenX + boxObject.width * .75)
              return null;
          }
          return tab;
        ]]>
      
      
        
        
         tabs[i].screenX + tabs[i].boxObject.width / 2)
                return i;
          }
          return tabs.length;
        ]]>
      
      
        
         0;
          for (let i = 0; i < dt.mozItemCount; i++) {
            // tabs are always added as the first type
            let types = dt.mozTypesAt(0);
            if (types[0] != TAB_DROP_TYPE) {
              isMovingTabs = false;
              break;
            }
          }
          if (isMovingTabs) {
            let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
            if (sourceNode instanceof XULElement &&
                sourceNode.localName == "tab" &&
                sourceNode.ownerGlobal.isChromeWindow &&
                sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
                sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.parentNode) {
              // Do not allow transfering a private tab to a non-private window
              // and vice versa.
              if (PrivateBrowsingUtils.isWindowPrivate(window) !=
                  PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerGlobal))
                return "none";
              if (window.gMultiProcessBrowser !=
                  sourceNode.ownerGlobal.gMultiProcessBrowser)
                return "none";
              return dt.dropEffect == "copy" ? "copy" : "move";
            }
          }
          if (browserDragAndDrop.canDropLink(event)) {
            return "link";
          }
          return "none";
        ]]>
      
      
        
        
      
      
        
        
        
        
      
      
        
        
        
        
      
      
         n.parentNode.localName == "toolbarpaletteitem" ? n.parentNode : n;
          let unwrap = n => n && n.localName == "toolbarpaletteitem" ? n.firstElementChild : n;
          // Starting from the tabs element, find the next sibling that:
          // - isn't hidden; and
          // - isn't the all-tabs button.
          // If it's the new tab button, consider the new tab button adjacent to the tabs.
          // If the new tab button is marked as adjacent and the tabstrip doesn't
          // overflow, we'll display the 'new tab' button inline in the tabstrip.
          // In all other cases, the separate new tab button is displayed in its
          // customized location.
          let sib = this;
          do {
            sib = unwrap(wrap(sib).nextElementSibling);
          } while (sib && (sib.hidden ||
                           sib.id == "alltabs-button"));
          const kAttr = "hasadjacentnewtabbutton";
          if (sib && sib.id == "new-tab-button") {
            this.setAttribute(kAttr, "true");
          } else {
            this.removeAttribute(kAttr);
          }
        ]]>
      
      
        
        
        
        
      
      
        
        
        
      
      
        
        
        
      
      
        
        
        
      
    
    
      
      
      
      
      
      
      
       1 && !target._ignoredCloseButtonClicks) {
            target._ignoredCloseButtonClicks = true;
            event.stopPropagation();
            return;
          } else {
            // Reset the "ignored click" flag
            target._ignoredCloseButtonClicks = false;
          }
        }
        /* Protects from close-tab-button errant doubleclick:
         * Since we're removing the event target, if the user
         * double-clicks the button, the dblclick event will be dispatched
         * with the tabbar as its event target (and explicit/originalTarget),
         * which treats that as a mouse gesture for opening a new tab.
         * In this context, we're manually blocking the dblclick event.
         */
        if (this._blockDblClick) {
          if (!("_clickedTabBarOnce" in this)) {
            this._clickedTabBarOnce = true;
            return;
          }
          delete this._clickedTabBarOnce;
          this._blockDblClick = false;
        }
      ]]>
       endOfTab) ||
              (RTL_UI && event.clientX < endOfTab)) {
            BrowserOpenTab();
          }
        } else {
          return;
        }
        event.stopPropagation();
      ]]>
      = visibleTabs.length) {
            focusedTabIndex = 0;
          } else if (focusedTabIndex < 0) {
            focusedTabIndex = visibleTabs.length - 1;
          }
        } else {
          focusedTabIndex = Math.min(visibleTabs.length - 1, Math.max(0, focusedTabIndex));
        }
        if (keyComboForFocus &&
            focusedTabIndex != lastFocusedTabIndex) {
          this.ariaFocusedItem = visibleTabs[focusedTabIndex];
        }
        event.preventDefault();
      ]]>
       selectedTab != tab);
        let dataTransferOrderedTabs = [tab].concat(otherSelectedTabs);
        let dt = event.dataTransfer;
        for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
          let dtTab = dataTransferOrderedTabs[i];
          dt.mozSetDataAt(TAB_DROP_TYPE, dtTab, i);
          let dtBrowser = dtTab.linkedBrowser;
          // We must not set text/x-moz-url or text/plain data here,
          // otherwise trying to detach the tab by dropping it on the desktop
          // may result in an "internet shortcut"
          dt.mozSetDataAt("text/x-moz-text-internal", dtBrowser.currentURI.spec, i);
        }
        // Set the cursor to an arrow during tab drags.
        dt.mozCursor = "default";
        // Set the tab as the source of the drag, which ensures we have a stable
        // node to deliver the `dragend` event.  See bug 1345473.
        dt.addElement(tab);
        if (tab.multiselected) {
          this._groupSelectedTabs(tab);
        }
        // Create a canvas to which we capture the current tab.
        // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
        // canvas size (in CSS pixels) to the window's backing resolution in order
        // to get a full-resolution drag image for use on HiDPI displays.
        let windowUtils = window.windowUtils;
        let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
        let canvas = this._dndCanvas;
        if (!canvas) {
          this._dndCanvas = canvas =
            document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
          canvas.style.width = "100%";
          canvas.style.height = "100%";
          canvas.mozOpaque = true;
        }
        canvas.width = 160 * scale;
        canvas.height = 90 * scale;
        let toDrag = canvas;
        let dragImageOffset = -16;
        let browser = tab.linkedBrowser;
        if (gMultiProcessBrowser) {
          var context = canvas.getContext("2d");
          context.fillStyle = "white";
          context.fillRect(0, 0, canvas.width, canvas.height);
          let captureListener;
          let platform = AppConstants.platform;
          // On Windows and Mac we can update the drag image during a drag
          // using updateDragImage. On Linux, we can use a panel.
          if (platform == "win" || platform == "macosx") {
            captureListener = function() {
              dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
            };
          } else {
            // Create a panel to use it in setDragImage
            // which will tell xul to render a panel that follows
            // the pointer while a dnd session is on.
            if (!this._dndPanel) {
              this._dndCanvas = canvas;
              this._dndPanel = document.createXULElement("panel");
              this._dndPanel.className = "dragfeedback-tab";
              this._dndPanel.setAttribute("type", "drag");
              let wrapper = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
              wrapper.style.width = "160px";
              wrapper.style.height = "90px";
              wrapper.appendChild(canvas);
              this._dndPanel.appendChild(wrapper);
              document.documentElement.appendChild(this._dndPanel);
            }
            toDrag = this._dndPanel;
          }
          // PageThumb is async with e10s but that's fine
          // since we can update the image during the dnd.
          PageThumbs.captureToCanvas(browser, canvas, captureListener);
        } else {
          // For the non e10s case we can just use PageThumbs
          // sync, so let's use the canvas for setDragImage.
          PageThumbs.captureToCanvas(browser, canvas);
          dragImageOffset = dragImageOffset * scale;
        }
        dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
        // _dragData.offsetX/Y give the coordinates that the mouse should be
        // positioned relative to the corner of the new window created upon
        // dragend such that the mouse appears to have the same position
        // relative to the corner of the dragged tab.
        function clientX(ele) {
          return ele.getBoundingClientRect().left;
        }
        let tabOffsetX = clientX(tab) - clientX(this);
        tab._dragData = {
          offsetX: event.screenX - window.screenX - tabOffsetX,
          offsetY: event.screenY - window.screenY,
          scrollX: this.arrowScrollbox.scrollbox.scrollLeft,
          screenX: event.screenX,
          movingTabs: (tab.multiselected ? gBrowser.selectedTabs : [tab])
                      .filter(t => t.pinned == tab.pinned),
        };
        event.stopPropagation();
      ]]>
      = this._dragTime + this._dragOverDelay)
              this.selectedItem = tab;
            ind.collapsed = true;
            return;
          }
        }
        var rect = arrowScrollbox.getBoundingClientRect();
        var newMargin;
        if (pixelsToScroll) {
          // if we are scrolling, put the drop indicator at the edge
          // so that it doesn't jump while scrolling
          let scrollRect = arrowScrollbox.scrollClientRect;
          let minMargin = scrollRect.left - rect.left;
          let maxMargin = Math.min(minMargin + scrollRect.width,
                                   scrollRect.right);
          if (RTL_UI) {
            [minMargin, maxMargin] = [this.clientWidth - maxMargin,
                                      this.clientWidth - minMargin];
          }
          newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
        } else {
          let newIndex = this._getDropIndex(event, effects == "link");
          if (newIndex == this.children.length) {
            let tabRect = this.children[newIndex - 1].getBoundingClientRect();
            if (RTL_UI) {
              newMargin = rect.right - tabRect.left;
            } else {
              newMargin = tabRect.right - rect.left;
            }
          } else {
            let tabRect = this.children[newIndex].getBoundingClientRect();
            if (RTL_UI) {
              newMargin = rect.right - tabRect.right;
            } else {
              newMargin = tabRect.left - rect.left;
            }
          }
        }
        ind.collapsed = false;
        newMargin += ind.clientWidth / 2;
        if (RTL_UI) {
          newMargin *= -1;
        }
        ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
        ind.style.marginInlineStart = (-ind.clientWidth) + "px";
      ]]>
       0 && translateOffset > tabWidth / 2) {
            newTranslateX += tabWidth;
          } else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) {
            newTranslateX -= tabWidth;
          }
          let dropIndex = "animDropIndex" in draggedTab._dragData &&
                          draggedTab._dragData.animDropIndex;
          let incrementDropIndex = true;
          if (dropIndex && dropIndex > movingTabs[0]._tPos) {
            dropIndex--;
            incrementDropIndex = false;
          }
          let animate = gBrowser.animationsEnabled;
          if (oldTranslateX && oldTranslateX != newTranslateX && animate) {
            for (let tab of movingTabs) {
              tab.setAttribute("tabdrop-samewindow", "true");
              tab.style.transform = "translateX(" + newTranslateX + "px)";
              let onTransitionEnd = transitionendEvent => {
                if (transitionendEvent.propertyName != "transform" ||
                    transitionendEvent.originalTarget != tab) {
                  return;
                }
                tab.removeEventListener("transitionend", onTransitionEnd);
                tab.removeAttribute("tabdrop-samewindow");
                this._finishAnimateTabMove();
                if (dropIndex !== false) {
                  gBrowser.moveTabTo(tab, dropIndex);
                  if (incrementDropIndex)
                    dropIndex++;
                }
                gBrowser.syncThrobberAnimations(tab);
              };
              tab.addEventListener("transitionend", onTransitionEnd);
            }
          } else {
            this._finishAnimateTabMove();
            if (dropIndex !== false) {
              for (let tab of movingTabs) {
                gBrowser.moveTabTo(tab, dropIndex);
                if (incrementDropIndex)
                  dropIndex++;
              }
            }
          }
        } else if (draggedTab) {
          let newIndex = this._getDropIndex(event, false);
          let newTabs = [];
          for (let tab of movingTabs) {
            let newTab = gBrowser.adoptTab(tab, newIndex++, tab == draggedTab);
            newTabs.push(newTab);
          }
          // Restore tab selection
          gBrowser.addRangeToMultiSelectedTabs(newTabs[0], newTabs[newTabs.length - 1]);
        } else {
          // Pass true to disallow dropping javascript: or data: urls
          let links;
          try {
            links = browserDragAndDrop.dropLinks(event, true);
          } catch (ex) {}
          if (!links || links.length === 0)
            return;
          let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
          if (event.shiftKey)
            inBackground = !inBackground;
          let targetTab = this._getDragTargetTab(event, true);
          let userContextId = this.selectedItem.getAttribute("usercontextid");
          let replace = !!targetTab;
          let newIndex = this._getDropIndex(event, true);
          let urls = links.map(link => link.url);
          let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(event);
          (async () => {
            if (urls.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
              // Sync dialog cannot be used inside drop event handler.
              let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(urls.length,
                                                                          window);
              if (!answer) {
                return;
              }
            }
            gBrowser.loadTabs(urls, {
              inBackground,
              replace,
              allowThirdPartyFixup: true,
              targetTab,
              newIndex,
              userContextId,
              triggeringPrincipal,
            });
          })();
        }
        if (draggedTab) {
          delete draggedTab._dragData;
        }
      ]]>
       wX && eX < (wX + window.outerWidth)) {
          // also avoid detaching if the the tab was dropped too close to
          // the tabbar (half a tab)
          let rect = window.windowUtils.getBoundsWithoutFlushing(this.arrowScrollbox);
          let detachTabThresholdY = window.screenY + rect.top + 1.5 * rect.height;
          if (eY < detachTabThresholdY && eY > window.screenY)
            return;
        }
        // screen.availLeft et. al. only check the screen that this window is on,
        // but we want to look at the screen the tab is being dropped onto.
        var screen = Cc["@mozilla.org/gfx/screenmanager;1"]
                       .getService(Ci.nsIScreenManager)
                       .screenForRect(eX, eY, 1, 1);
        var fullX = {}, fullY = {}, fullWidth = {}, fullHeight = {};
        var availX = {}, availY = {}, availWidth = {}, availHeight = {};
        // get full screen rect and available rect, both in desktop pix
        screen.GetRectDisplayPix(fullX, fullY, fullWidth, fullHeight);
        screen.GetAvailRectDisplayPix(availX, availY, availWidth, availHeight);
        // scale factor to convert desktop pixels to CSS px
        var scaleFactor =
          screen.contentsScaleFactor / screen.defaultCSSScaleFactor;
        // synchronize CSS-px top-left coordinates with the screen's desktop-px
        // coordinates, to ensure uniqueness across multiple screens
        // (compare the equivalent adjustments in nsGlobalWindow::GetScreenXY()
        // and related methods)
        availX.value = (availX.value - fullX.value) * scaleFactor + fullX.value;
        availY.value = (availY.value - fullY.value) * scaleFactor + fullY.value;
        availWidth.value *= scaleFactor;
        availHeight.value *= scaleFactor;
        // ensure new window entirely within screen
        var winWidth = Math.min(window.outerWidth, availWidth.value);
        var winHeight = Math.min(window.outerHeight, availHeight.value);
        var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, availX.value),
                            availX.value + availWidth.value - winWidth);
        var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, availY.value),
                           availY.value + availHeight.value - winHeight);
        delete draggedTab._dragData;
        if (gBrowser.tabs.length == 1) {
          // resize _before_ move to ensure the window fits the new screen.  if
          // the window is too large for its screen, the window manager may do
          // automatic repositioning.
          window.resizeTo(winWidth, winHeight);
          window.moveTo(left, top);
          window.focus();
        } else {
          let props = { screenX: left, screenY: top, suppressanimation: 1 };
          if (AppConstants.platform != "win") {
            props.outerWidth = winWidth;
            props.outerHeight = winHeight;
          }
          gBrowser.replaceTabsWithWindow(draggedTab, props);
        }
        event.stopPropagation();
      ]]>
      
    
  
  
    
      
        
          
          
          
        
        
        
          
          
          
          
          
          
            
          
          
          
        
      
    
    
      
      
        
          
        
      
      
        
          
        
      
      false
      
        
          return this.getAttribute("pinned") == "true";
        
      
      
        
          return this.getAttribute("hidden") == "true";
        
      
      
        
          return this.getAttribute("muted") == "true";
        
      
      
        
          return this.getAttribute("multiselected") == "true";
        
      
      
        
          return this.getAttribute("before-multiselected") == "true";
        
      
      
      undefined
      
        
          return this.hasAttribute("usercontextid")
                   ? parseInt(this.getAttribute("usercontextid"))
                   : 0;
        
      
      
        
          return this.getAttribute("soundplaying") == "true";
        
      
      
        
          return this.getAttribute("activemedia-blocked") == "true";
        
      
      
        
          // 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 (!checkEmptyPageOrigin(browser))
            return false;
          if (browser.canGoForward || browser.canGoBack)
            return false;
          return true;
        
      
      
        
          return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
        
      
      
        
        
      
      false
      
        
      
      null
      
      
        
      
      
        
      
      
        
      
      
         {
            if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
              TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
            }
          }, 100);
        ]]>
      
      
        
      
      
        
        
        
        
      
      
        
        
        
        
      
    
    
      
      
      
        this.style.MozUserFocus = "";
      
      
      
      
      
      
        // Make sure that clear-selection is released.
        // Otherwise selection using Shift key may be broken.
        gBrowser.unlockClearMultiSelection();
        this.style.MozUserFocus = "";
      
       0 &&
            !event.originalTarget.classList.contains("tab-close-button") &&
            !event.originalTarget.classList.contains("tab-icon-sound") &&
            !event.originalTarget.classList.contains("tab-icon-overlay")) {
          // Tabs were previously multi-selected and user clicks on a tab
          // without holding Ctrl/Cmd Key
          // Force positional attributes to update when the
          // target (of the click) is the "active" tab.
          let updatePositionalAttr = gBrowser.selectedTab == this;
          gBrowser.clearMultiSelectedTabs(updatePositionalAttr);
        }
        if (event.originalTarget.classList.contains("tab-icon-sound") ||
            (event.originalTarget.classList.contains("tab-icon-overlay") &&
             (event.originalTarget.hasAttribute("soundplaying") ||
              event.originalTarget.hasAttribute("muted")))) {
          if (this.multiselected) {
            gBrowser.toggleMuteAudioOnMultiSelectedTabs(this);
          } else {
            this.toggleMuteAudio();
          }
          return;
        }
        if (event.originalTarget.getAttribute("anonid") == "close-button") {
          if (this.multiselected) {
            gBrowser.removeMultiSelectedTabs();
          } else {
            gBrowser.removeTab(this, {
              animate: true,
              byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
            });
          }
          // This enables double-click protection for the tab container
          // (see tabbrowser-tabs 'click' handler).
          gBrowser.tabContainer._blockDblClick = true;
        }
      ]]>