2015-12-01 22:05:45 +00:00
|
|
|
/* -*- 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 = {
|
2016-12-29 23:34:54 +00:00
|
|
|
init() {
|
2015-12-01 22:05:45 +00:00
|
|
|
this._readPref();
|
2017-04-14 19:51:38 +00:00
|
|
|
Services.prefs.addObserver(this._prefName, this);
|
2015-12-01 22:05:45 +00:00
|
|
|
|
|
|
|
// 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)
|
2018-03-26 15:24:17 +00:00
|
|
|
this.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
};
|
|
|
|
this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
|
|
|
|
if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
|
2018-03-26 15:24:17 +00:00
|
|
|
this.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
};
|
|
|
|
CustomizableUI.addListener(this);
|
|
|
|
|
2018-03-26 15:24:17 +00:00
|
|
|
window.addEventListener("resolutionchange", this);
|
|
|
|
window.addEventListener("resize", this);
|
2016-03-17 12:53:31 +00:00
|
|
|
|
2018-03-22 17:52:29 +00:00
|
|
|
gDragSpaceObserver.init();
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
this._initialized = true;
|
|
|
|
this.update();
|
|
|
|
},
|
|
|
|
|
|
|
|
whenWindowLayoutReady() {
|
|
|
|
this._windowLayoutReady = true;
|
|
|
|
this.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
allowedBy(condition, allow) {
|
2015-12-01 22:05:45 +00:00
|
|
|
if (allow) {
|
|
|
|
if (condition in this._disallowed) {
|
|
|
|
delete this._disallowed[condition];
|
2018-03-26 15:24:17 +00:00
|
|
|
this.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
2016-08-12 15:50:35 +00:00
|
|
|
} else if (!(condition in this._disallowed)) {
|
|
|
|
this._disallowed[condition] = null;
|
2018-03-26 15:24:17 +00:00
|
|
|
this.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-03-30 15:36:01 +00:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2015-12-01 22:05:45 +00:00
|
|
|
get enabled() {
|
|
|
|
return document.documentElement.getAttribute("tabsintitlebar") == "true";
|
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
observe(subject, topic, data) {
|
2015-12-01 22:05:45 +00:00
|
|
|
if (topic == "nsPref:changed")
|
|
|
|
this._readPref();
|
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
handleEvent(aEvent) {
|
2018-03-26 15:24:17 +00:00
|
|
|
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;
|
2016-03-17 12:53:31 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
_onMenuMutate(aMutations) {
|
2015-12-01 22:05:45 +00:00
|
|
|
for (let mutation of aMutations) {
|
|
|
|
if (mutation.attributeName == "inactive" ||
|
|
|
|
mutation.attributeName == "autohide") {
|
2018-03-26 15:24:17 +00:00
|
|
|
TabsInTitlebar.update();
|
2015-12-01 22:05:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
_initialized: false,
|
|
|
|
_windowLayoutReady: false,
|
2015-12-01 22:05:45 +00:00
|
|
|
_disallowed: {},
|
|
|
|
_prefName: "browser.tabs.drawInTitlebar",
|
|
|
|
_lastSizeMode: null,
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
_readPref() {
|
2015-12-01 22:05:45 +00:00
|
|
|
this.allowedBy("pref",
|
|
|
|
Services.prefs.getBoolPref(this._prefName));
|
|
|
|
},
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
update() {
|
|
|
|
if (!this._initialized || window.fullScreen) {
|
2018-03-28 18:34:21 +00:00
|
|
|
return;
|
2018-03-28 18:36:21 +00:00
|
|
|
}
|
2018-03-28 18:24:26 +00:00
|
|
|
|
2018-03-30 15:36:01 +00:00
|
|
|
let allowed = this.systemSupported &&
|
|
|
|
(Object.keys(this._disallowed)).length == 0;
|
2018-03-28 18:36:21 +00:00
|
|
|
if (allowed) {
|
|
|
|
document.documentElement.setAttribute("tabsintitlebar", "true");
|
2018-03-28 18:47:21 +00:00
|
|
|
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");
|
|
|
|
}
|
2018-03-28 18:36:21 +00:00
|
|
|
} else {
|
|
|
|
document.documentElement.removeAttribute("tabsintitlebar");
|
2018-03-28 18:47:21 +00:00
|
|
|
document.documentElement.removeAttribute("chromemargin");
|
|
|
|
if (AppConstants.platform == "macosx") {
|
|
|
|
document.documentElement.setAttribute("drawtitle", "true");
|
|
|
|
}
|
2018-03-28 18:36:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this._layOutTitlebar(allowed);
|
|
|
|
|
|
|
|
ToolbarIconColor.inferFromText("tabsintitlebar", allowed);
|
|
|
|
},
|
|
|
|
|
|
|
|
_layOutTitlebar(drawInTitlebar) {
|
|
|
|
if (!this._windowLayoutReady) {
|
2015-12-01 22:05:45 +00:00
|
|
|
return;
|
2016-05-13 17:52:53 +00:00
|
|
|
}
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
let $ = id => document.getElementById(id);
|
|
|
|
let rect = ele => ele.getBoundingClientRect();
|
|
|
|
let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
|
2015-12-01 22:05:45 +00:00
|
|
|
|
|
|
|
let titlebar = $("titlebar");
|
|
|
|
let menubar = $("toolbar-menubar");
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
if (!drawInTitlebar) {
|
|
|
|
if (AppConstants.platform == "macosx") {
|
|
|
|
let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
|
|
|
|
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
2017-08-09 13:36:01 +00:00
|
|
|
}
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Reset styles that might have been modified:
|
|
|
|
titlebar.style.marginBottom = "";
|
|
|
|
menubar.style.paddingBottom = "";
|
|
|
|
return;
|
|
|
|
}
|
2017-10-06 14:41:43 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
let titlebarContent = $("titlebar-content");
|
|
|
|
let titlebarButtons = $("titlebar-buttonbox");
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// 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");
|
|
|
|
}
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Try to avoid reflows in this code by calculating dimensions first and
|
|
|
|
// then later set the properties affecting layout together in a batch.
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Get the height of the tabs toolbar:
|
|
|
|
let fullTabsHeight = rect($("TabsToolbar")).height;
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Buttons first:
|
|
|
|
let captionButtonsBoxWidth = rect(titlebarButtons).width;
|
2017-06-10 16:20:53 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
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;
|
|
|
|
}
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// And get the height of what's in the titlebar:
|
|
|
|
let titlebarContentHeight = rect(titlebarContent).height;
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Begin setting CSS properties which will cause a reflow
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// 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";
|
|
|
|
}
|
2018-03-28 18:34:21 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// 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;
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
2018-03-28 18:36:21 +00:00
|
|
|
}
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// 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;
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
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";
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
titlebarContentHeight += extraMargin;
|
2015-12-01 22:05:45 +00:00
|
|
|
} else {
|
2018-03-28 18:36:21 +00:00
|
|
|
titlebarContent.style.removeProperty("margin-bottom");
|
|
|
|
}
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// 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";
|
2015-12-01 22:05:45 +00:00
|
|
|
|
2018-03-28 18:36:21 +00:00
|
|
|
// Finally, size the placeholders:
|
|
|
|
if (AppConstants.platform == "macosx") {
|
|
|
|
this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
2018-03-28 18:36:21 +00:00
|
|
|
this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
|
2015-12-01 22:05:45 +00:00
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
_sizePlaceholder(type, width) {
|
2016-11-10 22:48:04 +00:00
|
|
|
Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='" + type + "']"),
|
2018-01-04 18:25:30 +00:00
|
|
|
function(node) { node.style.width = width + "px"; });
|
2015-12-01 22:05:45 +00:00
|
|
|
},
|
|
|
|
|
2016-12-29 23:34:54 +00:00
|
|
|
uninit() {
|
2015-12-01 22:05:45 +00:00
|
|
|
Services.prefs.removeObserver(this._prefName, this);
|
|
|
|
this._menuObserver.disconnect();
|
|
|
|
CustomizableUI.removeListener(this);
|
2018-03-22 17:52:29 +00:00
|
|
|
gDragSpaceObserver.uninit();
|
2015-12-01 22:05:45 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function onTitlebarMaxClick() {
|
|
|
|
if (window.windowState == window.STATE_MAXIMIZED)
|
|
|
|
window.restore();
|
|
|
|
else
|
|
|
|
window.maximize();
|
|
|
|
}
|
2018-03-22 17:52:29 +00:00
|
|
|
|
|
|
|
// 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");
|
|
|
|
}
|
2018-03-26 15:24:17 +00:00
|
|
|
TabsInTitlebar.update();
|
2018-03-22 17:52:29 +00:00
|
|
|
},
|
|
|
|
};
|