diff options
Diffstat (limited to 'application/basilisk/base/content/browser-tabsintitlebar.js')
-rw-r--r-- | application/basilisk/base/content/browser-tabsintitlebar.js | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/application/basilisk/base/content/browser-tabsintitlebar.js b/application/basilisk/base/content/browser-tabsintitlebar.js new file mode 100644 index 000000000..7f2e0a358 --- /dev/null +++ b/application/basilisk/base/content/browser-tabsintitlebar.js @@ -0,0 +1,310 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +// Note: the file browser-tabsintitlebar-stub.js is used instead of +// this one on platforms which don't have CAN_DRAW_IN_TITLEBAR defined. + +var TabsInTitlebar = { + init() { + if (this._initialized) { + return; + } + this._readPref(); + Services.prefs.addObserver(this._prefName, this, false); + + // We need to update the appearance of the titlebar when the menu changes + // from the active to the inactive state. We can't, however, rely on + // DOMMenuBarInactive, because the menu fires this event and then removes + // the inactive attribute after an event-loop spin. + // + // Because updating the appearance involves sampling the heights and margins + // of various elements, it's important that the layout be more or less + // settled before updating the titlebar. So instead of listening to + // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to + // watch the "invalid" attribute directly. + let menu = document.getElementById("toolbar-menubar"); + this._menuObserver = new MutationObserver(this._onMenuMutate); + this._menuObserver.observe(menu, {attributes: true}); + + this.onAreaReset = function(aArea) { + if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) + this._update(true); + }; + this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) { + if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) + this._update(true); + }; + CustomizableUI.addListener(this); + + addEventListener("resolutionchange", this, false); + + this._initialized = true; + if (this._updateOnInit) { + // We don't need to call this with 'true', even if original calls + // (before init()) did, because this will be the first call and so + // we will update anyway. + this._update(); + } + }, + + allowedBy(condition, allow) { + if (allow) { + if (condition in this._disallowed) { + delete this._disallowed[condition]; + this._update(true); + } + } else if (!(condition in this._disallowed)) { + this._disallowed[condition] = null; + this._update(true); + } + }, + + updateAppearance: function updateAppearance(aForce) { + this._update(aForce); + }, + + get enabled() { + return document.documentElement.getAttribute("tabsintitlebar") == "true"; + }, + + observe(subject, topic, data) { + if (topic == "nsPref:changed") + this._readPref(); + }, + + handleEvent(aEvent) { + if (aEvent.type == "resolutionchange" && aEvent.target == window) { + this._update(true); + } + }, + + _onMenuMutate(aMutations) { + for (let mutation of aMutations) { + if (mutation.attributeName == "inactive" || + mutation.attributeName == "autohide") { + TabsInTitlebar._update(true); + return; + } + } + }, + + _initialized: false, + _updateOnInit: false, + _disallowed: {}, + _prefName: "browser.tabs.drawInTitlebar", + _lastSizeMode: null, + + _readPref() { + this.allowedBy("pref", + Services.prefs.getBoolPref(this._prefName)); + }, + + _update(aForce = false) { + let $ = id => document.getElementById(id); + let rect = ele => ele.getBoundingClientRect(); + let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop); + + if (window.fullScreen) + return; + + // In some edgecases it is possible for this to fire before we've initialized. + // Don't run now, but don't forget to run it when we do initialize. + if (!this._initialized) { + this._updateOnInit = true; + return; + } + + if (!aForce) { + // _update is called on resize events, because the window is not ready + // after sizemode events. However, we only care about the event when the + // sizemode is different from the last time we updated the appearance of + // the tabs in the titlebar. + let sizemode = document.documentElement.getAttribute("sizemode"); + if (this._lastSizeMode == sizemode) { + return; + } + let oldSizeMode = this._lastSizeMode; + this._lastSizeMode = sizemode; + // Don't update right now if we are leaving fullscreen, since the UI is + // still changing in the consequent "fullscreen" event. Code there will + // call this function again when everything is ready. + // See browser-fullScreen.js: FullScreen.toggle and bug 1173768. + if (oldSizeMode == "fullscreen") { + return; + } + } + + let allowed = (Object.keys(this._disallowed)).length == 0; + + let titlebar = $("titlebar"); + let titlebarContent = $("titlebar-content"); + let menubar = $("toolbar-menubar"); + + if (allowed) { + // We set the tabsintitlebar attribute first so that our CSS for + // tabsintitlebar manifests before we do our measurements. + document.documentElement.setAttribute("tabsintitlebar", "true"); + updateTitlebarDisplay(); + + // Try to avoid reflows in this code by calculating dimensions first and + // then later set the properties affecting layout together in a batch. + + // Get the full height of the tabs toolbar: + let tabsToolbar = $("TabsToolbar"); + let tabsStyles = window.getComputedStyle(tabsToolbar); + let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles); + // Buttons first: + let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width; + + let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles; +#ifdef XP_MACOSX + secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; + // No need to look up the menubar stuff on OS X: + menuHeight = 0; + fullMenuHeight = 0; +#else + // Otherwise, get the height and margins separately for the menubar + menuHeight = rect(menubar).height; + menuStyles = window.getComputedStyle(menubar); + fullMenuHeight = verticalMargins(menuStyles) + menuHeight; +#endif + + // And get the height of what's in the titlebar: + let titlebarContentHeight = rect(titlebarContent).height; + + // Begin setting CSS properties which will cause a reflow + + // If the menubar is around (menuHeight is non-zero), try to adjust + // its full height (i.e. including margins) to match the titlebar, + // by changing the menubar's bottom padding + if (menuHeight) { + // Calculate the difference between the titlebar's height and that of the menubar + let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight; + let paddingBottom; + // The titlebar is bigger: + if (menuTitlebarDelta > 0) { + fullMenuHeight += menuTitlebarDelta; + // If there is already padding on the menubar, we need to add that + // to the difference so the total padding is correct: + if ((paddingBottom = menuStyles.paddingBottom)) { + menuTitlebarDelta += parseFloat(paddingBottom); + } + menubar.style.paddingBottom = menuTitlebarDelta + "px"; + // The menubar is bigger, but has bottom padding we can remove: + } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) { + let existingPadding = parseFloat(paddingBottom); + // menuTitlebarDelta is negative; work out what's left, but don't set negative padding: + let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta); + menubar.style.paddingBottom = desiredPadding + "px"; + // We've changed the menu height now: + fullMenuHeight += desiredPadding - existingPadding; + } + } + + // Next, we calculate how much we need to stretch the titlebar down to + // go all the way to the bottom of the tab strip, if necessary. + let tabAndMenuHeight = fullTabsHeight + fullMenuHeight; + + if (tabAndMenuHeight > titlebarContentHeight) { + // We need to increase the titlebar content's outer height (ie including margins) + // to match the tab and menu height: + let extraMargin = tabAndMenuHeight - titlebarContentHeight; +#ifndef XP_MACOSX + titlebarContent.style.marginBottom = extraMargin + "px"; +#endif + + titlebarContentHeight += extraMargin; + } else { + titlebarContent.style.removeProperty("margin-bottom"); + } + + // Then add a negative margin to the titlebar, so that the following elements + // will overlap it by the lesser of the titlebar height or the tabstrip+menu. + let minTitlebarOrTabsHeight = Math.min(titlebarContentHeight, tabAndMenuHeight); + titlebar.style.marginBottom = "-" + minTitlebarOrTabsHeight + "px"; + + // Finally, size the placeholders: +#ifdef XP_MACOSX + this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); +#endif + this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth); + + } else { + document.documentElement.removeAttribute("tabsintitlebar"); + updateTitlebarDisplay(); + +#ifdef XP_MACOSX + let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; + this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); +#endif + + // Reset the margins and padding that might have been modified: + titlebarContent.style.marginTop = ""; + titlebarContent.style.marginBottom = ""; + titlebar.style.marginBottom = ""; + menubar.style.paddingBottom = ""; + } + + ToolbarIconColor.inferFromText(); + if (CustomizationHandler.isCustomizing()) { + gCustomizeMode.updateLWTStyling(); + } + }, + + _sizePlaceholder(type, width) { + Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"), + function(node) { node.width = width; }); + }, + + uninit() { + this._initialized = false; + removeEventListener("resolutionchange", this); + Services.prefs.removeObserver(this._prefName, this); + this._menuObserver.disconnect(); + CustomizableUI.removeListener(this); + } +}; + +function updateTitlebarDisplay() { +#ifdef XP_MACOSX + // OS X and the other platforms differ enough to necessitate this kind of + // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR, + // we draw in the OS X titlebar when putting the tabs up there. However, OS X + // also draws in the titlebar when a lightweight theme is applied, regardless + // of whether or not the tabs are drawn in the titlebar. + if (TabsInTitlebar.enabled) { + document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1"); + document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1"); + document.documentElement.removeAttribute("drawtitle"); + } + else { + // We set chromemargin-nonlwtheme to "" instead of removing it as a way of + // making sure that LightweightThemeConsumer doesn't take it upon itself to + // detect this value again if and when we do a lwtheme state change. + document.documentElement.setAttribute("chromemargin-nonlwtheme", ""); + let isCustomizing = document.documentElement.hasAttribute("customizing"); + let hasLWTheme = document.documentElement.hasAttribute("lwtheme"); + let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window); + if ((!hasLWTheme || isCustomizing) && !isPrivate) { + document.documentElement.removeAttribute("chromemargin"); + } + document.documentElement.setAttribute("drawtitle", "true"); + } +#else + if (TabsInTitlebar.enabled) { + // not OS X + document.documentElement.setAttribute("chromemargin", "0,2,2,2"); + } else { + document.documentElement.removeAttribute("chromemargin"); + } +#endif +} + +function onTitlebarMaxClick() { + if (window.windowState == window.STATE_MAXIMIZED) + window.restore(); + else + window.maximize(); +} |