Commit 98fd22d8 authored by Richard Pospesel's avatar Richard Pospesel
Browse files

Bug 32220: Improve the letterboxing experience

CSS and JS changes to alter the UX surrounding letterboxing. The
browser element containing page content is now anchored to the bottom
of the toolbar, and the remaining letterbox margin is the same color
as the firefox chrome. The letterbox margin and border are tied to
the currently selected theme.

Also adds a 'needsLetterbox' property to tabbrowser.xml to fix a race
condition present when using the 'isEmpty' property. Using 'isEmpty'
as a proxy for 'needsLetterbox' resulted in over-zealous/unnecessary
letterboxing of about:blank tabs.

Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1594455
parent b3e5ac96
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -98,6 +98,13 @@ body {
  -moz-window-dragging: drag;
}

.browserStack > browser.letterboxing {
  border-color: var(--chrome-content-separator-color);
  border-style: solid;
  border-width : 1px;
  border-top: none;
}

#toolbar-menubar[autohide="true"] {
  overflow: hidden;
}
+9 −0
Original line number Diff line number Diff line
@@ -239,6 +239,15 @@
      return true;
    }

    get needsLetterbox() {
      let browser = this.linkedBrowser;
      if (isBlankPageURL(browser.currentURI.spec)) {
        return false;
      }

      return true;
    }

    get lastAccessed() {
      return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
    }
+6 −0
Original line number Diff line number Diff line
@@ -56,6 +56,12 @@
  background-color: var(--tabpanel-background-color);
}

/* extend down the toolbar's colors when letterboxing is enabled*/
#tabbrowser-tabpanels.letterboxing {
  background-color: var(--toolbar-bgcolor);
  background-image: var(--toolbar-bgimage);
}

#tabbrowser-tabs,
#tabbrowser-arrowscrollbox,
#tabbrowser-tabs[positionpinnedtabs] > #tabbrowser-arrowscrollbox > .tabbrowser-tab[pinned] {
+85 −12
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ class _RFPHelper {
  // ============================================================================
  constructor() {
    this._initialized = false;
    this._borderDimensions = null;
  }

  init() {
@@ -352,6 +353,24 @@ class _RFPHelper {
    });
  }

  getBorderDimensions(aBrowser) {
    if (this._borderDimensions) {
      return this._borderDimensions;
    }

    const win = aBrowser.ownerGlobal;
    const browserStyle = win.getComputedStyle(aBrowser);

    this._borderDimensions = {
      top: parseInt(browserStyle.borderTopWidth),
      right: parseInt(browserStyle.borderRightWidth),
      bottom: parseInt(browserStyle.borderBottomWidth),
      left: parseInt(browserStyle.borderLeftWidth),
    };

    return this._borderDimensions;
  }

  _addOrClearContentMargin(aBrowser) {
    let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser);

@@ -360,9 +379,13 @@ class _RFPHelper {
      return;
    }

    // we add the letterboxing class even if the content does not need letterboxing
    // in which case margins are set such that the borders are hidden
    aBrowser.classList.add("letterboxing");

    // We should apply no margin around an empty tab or a tab with system
    // principal.
    if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) {
    if (!tab.needsLetterbox || aBrowser.contentPrincipal.isSystemPrincipal) {
      this._clearContentViewMargin(aBrowser);
    } else {
      this._roundContentView(aBrowser);
@@ -530,10 +553,32 @@ class _RFPHelper {
    // Calculating the margins around the browser element in order to round the
    // content viewport. We will use a 200x100 stepping if the dimension set
    // is not given.
    let margins = calcMargins(containerWidth, containerHeight);

    const borderDimensions = this.getBorderDimensions(aBrowser);
    const marginDims = calcMargins(
      containerWidth,
      containerHeight - borderDimensions.top
    );

    let margins = {
      top: 0,
      right: 0,
      bottom: 0,
      left: 0,
    };

    // snap browser element to top
    margins.top = 0;
    // and leave 'double' margin at the bottom
    margins.bottom = 2 * marginDims.height - borderDimensions.bottom;
    // identical margins left and right
    margins.right = marginDims.width - borderDimensions.right;
    margins.left = marginDims.width - borderDimensions.left;

    const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;

    // If the size of the content is already quantized, we do nothing.
    if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) {
    if (aBrowser.style.margin === marginStyleString) {
      log("_roundContentView[" + logId + "] is_rounded == true");
      if (this._isLetterboxingTesting) {
        log(
@@ -554,19 +599,35 @@ class _RFPHelper {
        "_roundContentView[" +
          logId +
          "] setting margins to " +
          margins.width +
          " x " +
          margins.height
          marginStyleString
      );
      // One cannot (easily) control the color of a margin unfortunately.
      // An initial attempt to use a border instead of a margin resulted
      // in offset event dispatching; so for now we use a colorless margin.
      aBrowser.style.margin = `${margins.height}px ${margins.width}px`;

      // The margin background color is determined by the background color of the
      // window's tabpanels#tabbrowser-tabpanels element
      aBrowser.style.margin = marginStyleString;
    });
  }

  _clearContentViewMargin(aBrowser) {
    const borderDimensions = this.getBorderDimensions(aBrowser);
    // set the margins such that the browser elements border is visible up top, but
    // are rendered off-screen on the remaining sides
    let margins = {
      top: 0,
      right: -borderDimensions.right,
      bottom: -borderDimensions.bottom,
      left: -borderDimensions.left,
    };
    const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;

    aBrowser.ownerGlobal.requestAnimationFrame(() => {
      aBrowser.style.margin = marginStyleString;
    });
  }

  _removeLetterboxing(aBrowser) {
    aBrowser.ownerGlobal.requestAnimationFrame(() => {
      aBrowser.classList.remove("letterboxing");
      aBrowser.style.margin = "";
    });
  }
@@ -584,6 +645,11 @@ class _RFPHelper {
    aWindow.gBrowser.addTabsProgressListener(this);
    aWindow.addEventListener("TabOpen", this);

    const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels");
    if (tabPanel) {
      tabPanel.classList.add("letterboxing");
    }

    // Rounding the content viewport.
    this._updateMarginsForTabsInWindow(aWindow);
  }
@@ -607,10 +673,17 @@ class _RFPHelper {
    tabBrowser.removeTabsProgressListener(this);
    aWindow.removeEventListener("TabOpen", this);

    // Clear all margins and tooltip for all browsers.
    // revert tabpanel's background colors to default
    const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels");
    if (tabPanel) {
      tabPanel.classList.remove("letterboxing");
    }

    // and revert each browser element to default,
    // restore default margins and remove letterboxing class
    for (let tab of tabBrowser.tabs) {
      let browser = tab.linkedBrowser;
      this._clearContentViewMargin(browser);
      this._removeLetterboxing(browser);
    }
  }