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: "use strict"; michael@0: michael@0: var Appbar = { michael@0: get starButton() { return document.getElementById('star-button'); }, michael@0: get pinButton() { return document.getElementById('pin-button'); }, michael@0: get menuButton() { return document.getElementById('menu-button'); }, michael@0: michael@0: // track selected/active richgrid/tilegroup - the context for contextual action buttons michael@0: activeTileset: null, michael@0: michael@0: init: function Appbar_init() { michael@0: // fired from appbar bindings michael@0: Elements.contextappbar.addEventListener('MozAppbarShowing', this, false); michael@0: Elements.contextappbar.addEventListener('MozAppbarDismissing', this, false); michael@0: michael@0: // fired when a context sensitive item (bookmarks) changes state michael@0: window.addEventListener('MozContextActionsChange', this, false); michael@0: michael@0: // browser events we need to update button state on michael@0: Elements.browsers.addEventListener('URLChanged', this, true); michael@0: Elements.tabList.addEventListener('TabSelect', this, true); michael@0: michael@0: // tilegroup selection events for all modules get bubbled up michael@0: window.addEventListener("selectionchange", this, false); michael@0: Services.obs.addObserver(this, "metro_on_async_tile_created", false); michael@0: michael@0: // gather appbar telemetry data michael@0: try { michael@0: UITelemetry.addSimpleMeasureFunction("metro-appbar", michael@0: this.getAppbarMeasures.bind(this)); michael@0: } catch (ex) { michael@0: // swallow exception that occurs if metro-appbar measure is already set up michael@0: } michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "metro_on_async_tile_created": michael@0: this._updatePinButton(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function Appbar_handleEvent(aEvent) { michael@0: switch (aEvent.type) { michael@0: case 'URLChanged': michael@0: case 'TabSelect': michael@0: this.update(); michael@0: this.flushActiveTileset(aEvent.lastTab); michael@0: break; michael@0: michael@0: case 'MozAppbarShowing': michael@0: this.update(); michael@0: break; michael@0: michael@0: case 'MozAppbarDismissing': michael@0: if (this.activeTileset && ('isBound' in this.activeTileset)) { michael@0: this.activeTileset.clearSelection(); michael@0: } michael@0: this._clearContextualActions(); michael@0: this.activeTileset = null; michael@0: break; michael@0: michael@0: case 'MozContextActionsChange': michael@0: let actions = aEvent.actions; michael@0: let setName = aEvent.target.contextSetName; michael@0: // could transition in old, new buttons? michael@0: this.showContextualActions(actions, setName); michael@0: break; michael@0: michael@0: case "selectionchange": michael@0: let nodeName = aEvent.target.nodeName; michael@0: if ('richgrid' === nodeName) { michael@0: this._onTileSelectionChanged(aEvent); michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: flushActiveTileset: function flushActiveTileset(aTab) { michael@0: try { michael@0: let tab = aTab || Browser.selectedTab; michael@0: // Switching away from or loading a site into a startui tab that has actions michael@0: // pending, we consider this confirmation that the user wants to flush changes. michael@0: if (this.activeTileset && tab && tab.browser && tab.browser.currentURI.spec == kStartURI) { michael@0: ContextUI.dismiss(); michael@0: } michael@0: } catch (ex) {} michael@0: }, michael@0: michael@0: shutdown: function shutdown() { michael@0: this.flushActiveTileset(); michael@0: }, michael@0: michael@0: /* michael@0: * Called from various places when the visible content michael@0: * has changed such that button states may need to be michael@0: * updated. michael@0: */ michael@0: update: function update() { michael@0: this._updatePinButton(); michael@0: this._updateStarButton(); michael@0: }, michael@0: michael@0: onPinButton: function() { michael@0: if (this.pinButton.checked) { michael@0: Browser.pinSite(); michael@0: } else { michael@0: Browser.unpinSite(); michael@0: } michael@0: }, michael@0: michael@0: onStarButton: function(aValue) { michael@0: if (aValue === undefined) { michael@0: aValue = this.starButton.checked; michael@0: } michael@0: michael@0: if (aValue) { michael@0: Browser.starSite(function () { michael@0: Appbar._updateStarButton(); michael@0: }); michael@0: } else { michael@0: Browser.unstarSite(function () { michael@0: Appbar._updateStarButton(); michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: onMenuButton: function(aEvent) { michael@0: let typesArray = []; michael@0: michael@0: if (BrowserUI.isPrivateBrowsingEnabled) { michael@0: typesArray.push("private-browsing"); michael@0: } michael@0: if (!BrowserUI.isStartTabVisible) { michael@0: typesArray.push("find-in-page"); michael@0: if (ContextCommands.getPageSource()) michael@0: typesArray.push("view-page-source"); michael@0: } michael@0: if (ContextCommands.getStoreLink()) michael@0: typesArray.push("ms-meta-data"); michael@0: if (ConsolePanelView.enabled) michael@0: typesArray.push("open-error-console"); michael@0: if (!Services.metro.immersive) michael@0: typesArray.push("open-jsshell"); michael@0: michael@0: typesArray.push("view-on-desktop"); michael@0: michael@0: var x = this.menuButton.getBoundingClientRect().left; michael@0: var y = Elements.toolbar.getBoundingClientRect().top; michael@0: ContextMenuUI.showContextMenu({ michael@0: json: { michael@0: types: typesArray, michael@0: string: '', michael@0: xPos: x, michael@0: yPos: y, michael@0: leftAligned: true, michael@0: bottomAligned: true michael@0: } michael@0: michael@0: }); michael@0: }, michael@0: michael@0: onViewOnDesktop: function() { michael@0: let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. michael@0: getService(Components.interfaces.nsIAppStartup); michael@0: michael@0: Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); michael@0: this._incrementCountableEvent("switch-to-desktop-button"); michael@0: appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | michael@0: Components.interfaces.nsIAppStartup.eRestart); michael@0: }, michael@0: michael@0: onAutocompleteCloseButton: function () { michael@0: Elements.autocomplete.closePopup(); michael@0: }, michael@0: michael@0: dispatchContextualAction: function(aActionName){ michael@0: let activeTileset = this.activeTileset; michael@0: if (activeTileset && ('isBound' in this.activeTileset)) { michael@0: // fire event on the richgrid, others can listen michael@0: // but we keep coupling loose so grid doesn't need to know about appbar michael@0: let event = document.createEvent("Events"); michael@0: event.action = aActionName; michael@0: event.initEvent("context-action", true, true); // is cancelable michael@0: activeTileset.dispatchEvent(event); michael@0: if (!event.defaultPrevented) { michael@0: activeTileset.clearSelection(); michael@0: Elements.contextappbar.dismiss(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: showContextualActions: function(aVerbs, aSetName) { michael@0: // When the appbar is not visible, we want the icons to refresh right away michael@0: let immediate = !Elements.contextappbar.isShowing; michael@0: michael@0: if (aVerbs.length) { michael@0: Elements.contextappbar.show(); michael@0: } michael@0: michael@0: // Look up all of the buttons for the verbs that should be visible. michael@0: let idsToVisibleVerbs = new Map(); michael@0: for (let verb of aVerbs) { michael@0: let id = verb + "-selected-button"; michael@0: if (!document.getElementById(id)) { michael@0: throw new Error("Appbar.showContextualActions: no button for " + verb); michael@0: } michael@0: idsToVisibleVerbs.set(id, verb); michael@0: } michael@0: michael@0: // Sort buttons into 2 buckets - needing showing and needing hiding. michael@0: let toHide = [], toShow = []; michael@0: let buttons = Elements.contextappbar.getElementsByTagName("toolbarbutton"); michael@0: for (let button of buttons) { michael@0: let verb = idsToVisibleVerbs.get(button.id); michael@0: if (verb != undefined) { michael@0: // Button should be visible, and may or may not be showing. michael@0: this._updateContextualActionLabel(button, verb, aSetName); michael@0: if (button.hidden) { michael@0: toShow.push(button); michael@0: } michael@0: } else if (!button.hidden) { michael@0: // Button is visible, but shouldn't be. michael@0: toHide.push(button); michael@0: } michael@0: } michael@0: michael@0: if (immediate) { michael@0: toShow.forEach(function(element) { michael@0: element.removeAttribute("fade"); michael@0: element.hidden = false; michael@0: }); michael@0: toHide.forEach(function(element) { michael@0: element.setAttribute("fade", true); michael@0: element.hidden = true; michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: return Task.spawn(function() { michael@0: if (toHide.length) { michael@0: yield Util.transitionElementVisibility(toHide, false); michael@0: } michael@0: if (toShow.length) { michael@0: yield Util.transitionElementVisibility(toShow, true); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: _clearContextualActions: function() { michael@0: this.showContextualActions([]); michael@0: }, michael@0: michael@0: _updateContextualActionLabel: function(aButton, aVerb, aSetName) { michael@0: // True if the action's label string contains the set name and michael@0: // thus has to be selected based on the list passed in. michael@0: let usesSetName = aButton.hasAttribute("label-uses-set-name"); michael@0: let name = "contextAppbar2." + aVerb + (usesSetName ? "." + aSetName : ""); michael@0: aButton.label = Strings.browser.GetStringFromName(name); michael@0: }, michael@0: michael@0: _onTileSelectionChanged: function _onTileSelectionChanged(aEvent){ michael@0: let activeTileset = aEvent.target; michael@0: michael@0: // deselect tiles in other tile groups, michael@0: // ensure previousyl-activeTileset is bound before calling methods on it michael@0: if (this.activeTileset && michael@0: ('isBound' in this.activeTileset) && michael@0: this.activeTileset !== activeTileset) { michael@0: this.activeTileset.clearSelection(); michael@0: } michael@0: // keep track of which view is the target/scope for the contextual actions michael@0: this.activeTileset = activeTileset; michael@0: michael@0: // ask the view for the list verbs/action-names it thinks are michael@0: // appropriate for the tiles selected michael@0: let contextActions = activeTileset.contextActions; michael@0: let verbs = [v for (v of contextActions)]; michael@0: michael@0: // fire event with these verbs as payload michael@0: let event = document.createEvent("Events"); michael@0: event.actions = verbs; michael@0: event.initEvent("MozContextActionsChange", true, false); michael@0: activeTileset.dispatchEvent(event); michael@0: michael@0: if (verbs.length) { michael@0: Elements.contextappbar.show(); // should be no-op if we're already showing michael@0: } else { michael@0: Elements.contextappbar.dismiss(); michael@0: } michael@0: }, michael@0: michael@0: // track certain appbar events and interactions for the UITelemetry probe michael@0: _countableEvents: {}, michael@0: michael@0: _incrementCountableEvent: function(aName) { michael@0: if (!(aName in this._countableEvents)) { michael@0: this._countableEvents[aName] = 0; michael@0: } michael@0: this._countableEvents[aName]++; michael@0: }, michael@0: michael@0: getAppbarMeasures: function() { michael@0: return { michael@0: countableEvents: this._countableEvents michael@0: }; michael@0: }, michael@0: michael@0: _updatePinButton: function() { michael@0: this.pinButton.checked = Browser.isSitePinned(); michael@0: }, michael@0: michael@0: _updateStarButton: function() { michael@0: Browser.isSiteStarredAsync(function (isStarred) { michael@0: this.starButton.checked = isStarred; michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: };