/* -*- 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/. */ var TabsInTitlebar = { init() { this._readPref(); Services.prefs.addObserver(this._prefName, this); // 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(); }; this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) { if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR) this.update(); }; CustomizableUI.addListener(this); window.addEventListener("resolutionchange", this); window.addEventListener("resize", this); gDragSpaceObserver.init(); this._initialized = true; this.update(); }, whenWindowLayoutReady() { this._windowLayoutReady = true; this.update(); }, allowedBy(condition, allow) { if (allow) { if (condition in this._disallowed) { delete this._disallowed[condition]; this.update(); } } else if (!(condition in this._disallowed)) { this._disallowed[condition] = null; this.update(); } }, get systemSupported() { let isSupported = false; switch (AppConstants.MOZ_WIDGET_TOOLKIT) { case "windows": case "cocoa": isSupported = true; break; case "gtk3": isSupported = window.matchMedia("(-moz-gtk-csd-available)"); break; } delete this.systemSupported; return this.systemSupported = isSupported; }, get enabled() { return document.documentElement.getAttribute("tabsintitlebar") == "true"; }, observe(subject, topic, data) { if (topic == "nsPref:changed") this._readPref(); }, handleEvent(aEvent) { switch (aEvent.type) { case "resolutionchange": if (aEvent.target == window) { this.update(); } break; case "resize": if (window.fullScreen || aEvent.target != window) { break; } // We use resize events because the window is not ready after // sizemodechange 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) { break; } 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") { break; } this.update(); break; } }, _onMenuMutate(aMutations) { for (let mutation of aMutations) { if (mutation.attributeName == "inactive" || mutation.attributeName == "autohide") { TabsInTitlebar.update(); return; } } }, _initialized: false, _windowLayoutReady: false, _disallowed: {}, _prefName: "browser.tabs.drawInTitlebar", _lastSizeMode: null, _readPref() { this.allowedBy("pref", Services.prefs.getBoolPref(this._prefName)); }, update() { if (!this._initialized || window.fullScreen) { return; } let allowed = this.systemSupported && (Object.keys(this._disallowed)).length == 0; if (allowed) { document.documentElement.setAttribute("tabsintitlebar", "true"); if (AppConstants.platform == "macosx") { document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1"); document.documentElement.removeAttribute("drawtitle"); } else { document.documentElement.setAttribute("chromemargin", "0,2,2,2"); } } else { document.documentElement.removeAttribute("tabsintitlebar"); document.documentElement.removeAttribute("chromemargin"); if (AppConstants.platform == "macosx") { document.documentElement.setAttribute("drawtitle", "true"); } } this._layOutTitlebar(allowed); ToolbarIconColor.inferFromText("tabsintitlebar", allowed); }, _layOutTitlebar(drawInTitlebar) { if (!this._windowLayoutReady) { return; } let $ = id => document.getElementById(id); let rect = ele => ele.getBoundingClientRect(); let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop); let titlebar = $("titlebar"); let menubar = $("toolbar-menubar"); if (!drawInTitlebar) { if (AppConstants.platform == "macosx") { let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width; this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); } // Reset styles that might have been modified: titlebar.style.marginBottom = ""; menubar.style.paddingBottom = ""; return; } let titlebarContent = $("titlebar-content"); let titlebarButtons = $("titlebar-buttonbox"); // Reset the custom titlebar height if the menubar is shown, // because we will want to calculate its original height. let buttonsShouldMatchTabHeight = AppConstants.isPlatformAndVersionAtLeast("win", "10.0") || AppConstants.platform == "linux"; if (buttonsShouldMatchTabHeight && (menubar.getAttribute("inactive") != "true" || menubar.getAttribute("autohide") != "true")) { titlebarButtons.style.removeProperty("height"); } // 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 height of the tabs toolbar: let fullTabsHeight = rect($("TabsToolbar")).height; // Buttons first: let captionButtonsBoxWidth = rect(titlebarButtons).width; let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles; if (AppConstants.platform == "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; } // And get the height of what's in the titlebar: let titlebarContentHeight = rect(titlebarContent).height; // Begin setting CSS properties which will cause a reflow // Adjust the window controls to span the entire // tab strip height if we're not showing a menu bar. if (buttonsShouldMatchTabHeight && !menuHeight) { titlebarContentHeight = fullTabsHeight; titlebarButtons.style.height = titlebarContentHeight + "px"; } // 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; if (AppConstants.platform != "macosx") { titlebarContent.style.marginBottom = extraMargin + "px"; } 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 greater of the titlebar height or the tabstrip+menu. let maxTitlebarOrTabsHeight = Math.max(titlebarContentHeight, tabAndMenuHeight); titlebar.style.marginBottom = "-" + maxTitlebarOrTabsHeight + "px"; // Finally, size the placeholders: if (AppConstants.platform == "macosx") { this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth); } this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth); }, _sizePlaceholder(type, width) { Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"), function(node) { node.style.width = width + "px"; }); }, uninit() { Services.prefs.removeObserver(this._prefName, this); this._menuObserver.disconnect(); CustomizableUI.removeListener(this); gDragSpaceObserver.uninit(); } }; function onTitlebarMaxClick() { if (window.windowState == window.STATE_MAXIMIZED) window.restore(); else window.maximize(); } // Adds additional drag space to the window by listening to // the corresponding preference. var gDragSpaceObserver = { pref: "browser.tabs.extraDragSpace", init() { this.update(); Services.prefs.addObserver(this.pref, this); }, uninit() { Services.prefs.removeObserver(this.pref, this); }, observe(aSubject, aTopic, aPrefName) { if (aTopic != "nsPref:changed" || aPrefName != this.pref) { return; } this.update(); }, update() { if (Services.prefs.getBoolPref(this.pref)) { document.documentElement.setAttribute("extradragspace", "true"); } else { document.documentElement.removeAttribute("extradragspace"); } TabsInTitlebar.update(); }, };