michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Fired when the tabtray is displayed michael@0: const kContextUITabsShowEvent = "MozContextUITabsShow"; michael@0: // add more as needed... michael@0: michael@0: /* michael@0: * Manages context UI (navbar, tabbar, appbar) and track visibility. Also michael@0: * tracks events that summon and hide the context UI. michael@0: */ michael@0: var ContextUI = { michael@0: _expandable: true, michael@0: _hidingId: 0, michael@0: michael@0: /******************************************* michael@0: * init michael@0: */ michael@0: michael@0: init: function init() { michael@0: Elements.browsers.addEventListener('URLChanged', this, true); michael@0: Elements.browsers.addEventListener("AlertActive", this, true); michael@0: Elements.browsers.addEventListener("AlertClose", this, true); michael@0: Elements.tabList.addEventListener('TabSelect', this, true); michael@0: Elements.panelUI.addEventListener('ToolPanelShown', this, false); michael@0: Elements.panelUI.addEventListener('ToolPanelHidden', this, false); michael@0: michael@0: Elements.tray.addEventListener("mousemove", this, false); michael@0: Elements.tray.addEventListener("mouseleave", this, false); michael@0: michael@0: window.addEventListener("touchstart", this, true); michael@0: window.addEventListener("mousedown", this, true); michael@0: window.addEventListener("MozEdgeUIStarted", this, true); michael@0: window.addEventListener("MozEdgeUICanceled", this, true); michael@0: window.addEventListener("MozEdgeUICompleted", this, true); michael@0: window.addEventListener("keypress", this, true); michael@0: window.addEventListener("KeyboardChanged", this, false); michael@0: window.addEventListener("MozFlyoutPanelShowing", this, false); michael@0: michael@0: Elements.tray.addEventListener("transitionend", this, true); michael@0: michael@0: Appbar.init(); michael@0: }, michael@0: michael@0: /******************************************* michael@0: * Context UI state getters & setters michael@0: */ michael@0: michael@0: // any visiblilty michael@0: get isVisible() { michael@0: return this.navbarVisible || this.tabbarVisible || this.contextAppbarVisible; michael@0: }, michael@0: michael@0: // navbar visiblilty michael@0: get navbarVisible() { michael@0: return (Elements.navbar.hasAttribute("visible") || michael@0: Elements.navbar.hasAttribute("startpage")); michael@0: }, michael@0: michael@0: // tabbar visiblilty michael@0: get tabbarVisible() { michael@0: return Elements.tray.hasAttribute("expanded"); michael@0: }, michael@0: michael@0: // appbar visiblilty michael@0: get contextAppbarVisible() { michael@0: return Elements.contextappbar.isShowing; michael@0: }, michael@0: michael@0: // currently not in use, for the always show tabs feature michael@0: get isExpandable() { return this._expandable; }, michael@0: set isExpandable(aFlag) { michael@0: this._expandable = aFlag; michael@0: if (!this._expandable) michael@0: this.dismiss(); michael@0: }, michael@0: michael@0: /******************************************* michael@0: * Public api michael@0: */ michael@0: michael@0: /* michael@0: * Toggle the current nav UI state. Fires context ui events. michael@0: */ michael@0: toggleNavUI: function () { michael@0: // The navbar is forced open when the start page is visible. appbar.js michael@0: // controls the "visible" property, and browser-ui controls the "startpage" michael@0: // property. Hence we rely on the tabbar for current toggle state. michael@0: if (this.tabbarVisible) { michael@0: this.dismiss(); michael@0: } else { michael@0: this.displayNavUI(); michael@0: } michael@0: }, michael@0: michael@0: /* michael@0: * Show the nav and tabs bar. Returns true if any non-visible UI michael@0: * was shown. Fires context ui events. michael@0: */ michael@0: displayNavUI: function () { michael@0: let shown = false; michael@0: michael@0: if (!this.navbarVisible) { michael@0: BrowserUI.updateURI(); michael@0: this.displayNavbar(); michael@0: shown = true; michael@0: } michael@0: michael@0: if (!this.tabbarVisible) { michael@0: this.displayTabs(); michael@0: shown = true; michael@0: } michael@0: michael@0: if (shown) { michael@0: ContentAreaObserver.updateContentArea(); michael@0: } michael@0: michael@0: return shown; michael@0: }, michael@0: michael@0: /* michael@0: * Dismiss any context UI currently visible. Returns true if any michael@0: * visible UI was dismissed. Fires context ui events. michael@0: */ michael@0: dismiss: function () { michael@0: let dismissed = false; michael@0: michael@0: this._clearDelayedTimeout(); michael@0: michael@0: // No assurances this will hide the nav bar. It may have the michael@0: // 'startpage' property set. This removes the 'visible' property. michael@0: if (this.navbarVisible) { michael@0: BrowserUI.blurNavBar(); michael@0: this.dismissNavbar(); michael@0: dismissed = true; michael@0: } michael@0: if (this.tabbarVisible) { michael@0: this.dismissTabs(); michael@0: dismissed = true; michael@0: } michael@0: if (Elements.contextappbar.isShowing) { michael@0: this.dismissContextAppbar(); michael@0: dismissed = true; michael@0: } michael@0: michael@0: if (dismissed) { michael@0: ContentAreaObserver.updateContentArea(); michael@0: } michael@0: michael@0: return dismissed; michael@0: }, michael@0: michael@0: /* michael@0: * Briefly show the tab bar and then hide it. Fires context ui events. michael@0: */ michael@0: peekTabs: function peekTabs(aDelay) { michael@0: if (!this.tabbarVisible) michael@0: this.displayTabs(); michael@0: michael@0: ContextUI.dismissTabsWithDelay(aDelay); michael@0: }, michael@0: michael@0: /* michael@0: * Dismiss tab bar after a delay. Fires context ui events. michael@0: */ michael@0: dismissTabsWithDelay: function (aDelay) { michael@0: aDelay = aDelay || kForegroundTabAnimationDelay; michael@0: this._clearDelayedTimeout(); michael@0: this._lastTimeoutDelay = aDelay; michael@0: this._hidingId = setTimeout(function () { michael@0: ContextUI.dismissTabs(); michael@0: }, aDelay); michael@0: }, michael@0: michael@0: /* michael@0: * Display the nav bar. michael@0: * michael@0: * @return false if we were already visible, and didn't do anything. michael@0: */ michael@0: displayNavbar: function () { michael@0: if (Elements.chromeState.getAttribute("navbar") == "visible") { michael@0: return false; michael@0: } michael@0: michael@0: Elements.navbar.show(); michael@0: Elements.chromeState.setAttribute("navbar", "visible"); michael@0: ContentAreaObserver.updateContentArea(); michael@0: return true; michael@0: }, michael@0: michael@0: // Display the tab tray michael@0: displayTabs: function () { michael@0: this._clearDelayedTimeout(); michael@0: this._setIsExpanded(true); michael@0: }, michael@0: michael@0: // Dismiss the navbar if visible. michael@0: dismissNavbar: function dismissNavbar() { michael@0: if (!BrowserUI.isStartTabVisible) { michael@0: Elements.autocomplete.closePopup(); michael@0: Elements.navbar.dismiss(); michael@0: Elements.chromeState.removeAttribute("navbar"); michael@0: ContentAreaObserver.updateContentArea(); michael@0: } michael@0: }, michael@0: michael@0: // Dismiss the tabstray if visible. michael@0: dismissTabs: function dimissTabs() { michael@0: this._clearDelayedTimeout(); michael@0: this._setIsExpanded(false); michael@0: }, michael@0: michael@0: // Dismiss the appbar if visible. michael@0: dismissContextAppbar: function dismissContextAppbar() { michael@0: Elements.contextappbar.dismiss(); michael@0: }, michael@0: michael@0: /******************************************* michael@0: * Internal utils michael@0: */ michael@0: michael@0: // tabtray state michael@0: _setIsExpanded: function _setIsExpanded(aFlag, setSilently) { michael@0: // if the tray can't be expanded, don't expand it. michael@0: if (!this.isExpandable || this.tabbarVisible == aFlag) michael@0: return; michael@0: michael@0: if (aFlag) michael@0: Elements.tray.setAttribute("expanded", "true"); michael@0: else michael@0: Elements.tray.removeAttribute("expanded"); michael@0: michael@0: if (!setSilently) michael@0: this._fire(kContextUITabsShowEvent); michael@0: }, michael@0: michael@0: _clearDelayedTimeout: function _clearDelayedTimeout() { michael@0: if (this._hidingId) { michael@0: clearTimeout(this._hidingId); michael@0: this._hidingId = 0; michael@0: this._delayedHide = false; michael@0: } michael@0: }, michael@0: michael@0: _resetDelayedTimeout: function () { michael@0: this._hidingId = setTimeout(function () { michael@0: ContextUI.dismissTabs(); michael@0: }, this._lastTimeoutDelay); michael@0: }, michael@0: michael@0: /******************************************* michael@0: * Events michael@0: */ michael@0: michael@0: _onEdgeUIStarted: function(aEvent) { michael@0: this._hasEdgeSwipeStarted = true; michael@0: this._clearDelayedTimeout(); michael@0: this.toggleNavUI(); michael@0: }, michael@0: michael@0: _onEdgeUICanceled: function(aEvent) { michael@0: this._hasEdgeSwipeStarted = false; michael@0: this.dismiss(); michael@0: }, michael@0: michael@0: _onEdgeUICompleted: function(aEvent) { michael@0: if (this._hasEdgeSwipeStarted) { michael@0: this._hasEdgeSwipeStarted = false; michael@0: return; michael@0: } michael@0: michael@0: this._clearDelayedTimeout(); michael@0: this.toggleNavUI(); michael@0: }, michael@0: michael@0: onDownInput: function onDownInput(aEvent) { michael@0: if (!this.isVisible) { michael@0: return; michael@0: } michael@0: michael@0: // Various ui element containers we do not update context ui for. michael@0: let whitelist = [ michael@0: // Clicks on tab bar elements should not close the tab bar. the tabbar michael@0: // handles this. michael@0: Elements.tabs, michael@0: // Don't let a click on an infobar button dismiss the appbar or navbar. michael@0: // Note the notification box should always hover above these other two michael@0: // bars. michael@0: Browser.getNotificationBox() michael@0: ]; michael@0: michael@0: if (whitelist.some(elem => elem.contains(aEvent.target))) { michael@0: return; michael@0: } michael@0: michael@0: // If a start tab is visible only dismiss the tab bar. michael@0: if (BrowserUI.isStartTabVisible) { michael@0: ContextUI.dismissTabs(); michael@0: return; michael@0: } michael@0: michael@0: // content, dismiss anything visible michael@0: if (aEvent.target.ownerDocument.defaultView.top == getBrowser().contentWindow) { michael@0: this.dismiss(); michael@0: return; michael@0: } michael@0: michael@0: // dismiss tabs and context app bar if visible michael@0: this.dismissTabs(); michael@0: this.dismissContextAppbar(); michael@0: }, michael@0: michael@0: onMouseMove: function (aEvent) { michael@0: if (this._hidingId) { michael@0: this._clearDelayedTimeout(); michael@0: this._delayedHide = true; michael@0: } michael@0: }, michael@0: michael@0: onMouseLeave: function (aEvent) { michael@0: if (this._delayedHide) { michael@0: this._delayedHide = false; michael@0: this._resetDelayedTimeout(); michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function handleEvent(aEvent) { michael@0: switch (aEvent.type) { michael@0: case "URLChanged": michael@0: // "aEvent.detail" is a boolean value that indicates whether actual URL michael@0: // has changed ignoring URL fragment changes. michael@0: if (aEvent.target == Browser.selectedBrowser && aEvent.detail) { michael@0: this.displayNavbar(); michael@0: } michael@0: break; michael@0: case "MozEdgeUIStarted": michael@0: this._onEdgeUIStarted(aEvent); michael@0: break; michael@0: case "MozEdgeUICanceled": michael@0: this._onEdgeUICanceled(aEvent); michael@0: break; michael@0: case "MozEdgeUICompleted": michael@0: this._onEdgeUICompleted(aEvent); michael@0: break; michael@0: case "keypress": michael@0: if (String.fromCharCode(aEvent.which) == "z" && michael@0: aEvent.getModifierState("Win")) michael@0: this.toggleNavUI(); michael@0: break; michael@0: case "transitionend": michael@0: setTimeout(function () { michael@0: ContentAreaObserver.updateContentArea(); michael@0: }, 0); michael@0: break; michael@0: case "KeyboardChanged": michael@0: this.dismissTabs(); michael@0: break; michael@0: case "mousedown": michael@0: if (aEvent.button != 0) { michael@0: break; michael@0: } michael@0: this.onDownInput(aEvent); michael@0: break; michael@0: case "mousemove": michael@0: this.onMouseMove(aEvent); michael@0: break; michael@0: case "mouseleave": michael@0: this.onMouseLeave(aEvent); michael@0: break; michael@0: case "touchstart": michael@0: this.onDownInput(aEvent); michael@0: break; michael@0: case "ToolPanelShown": michael@0: case "ToolPanelHidden": michael@0: this.dismiss(); michael@0: break; michael@0: case "AlertActive": michael@0: case "AlertClose": michael@0: case "TabSelect": michael@0: ContentAreaObserver.updateContentArea(); michael@0: break; michael@0: case "MozFlyoutPanelShowing": michael@0: if (BrowserUI.isStartTabVisible) { michael@0: this.dismissTabs(); michael@0: this.dismissContextAppbar(); michael@0: } else { michael@0: this.dismiss(); michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _fire: function (name) { michael@0: let event = document.createEvent("Events"); michael@0: event.initEvent(name, true, true); michael@0: window.dispatchEvent(event); michael@0: } michael@0: };