1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/content/appbar.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,323 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +var Appbar = { 1.10 + get starButton() { return document.getElementById('star-button'); }, 1.11 + get pinButton() { return document.getElementById('pin-button'); }, 1.12 + get menuButton() { return document.getElementById('menu-button'); }, 1.13 + 1.14 + // track selected/active richgrid/tilegroup - the context for contextual action buttons 1.15 + activeTileset: null, 1.16 + 1.17 + init: function Appbar_init() { 1.18 + // fired from appbar bindings 1.19 + Elements.contextappbar.addEventListener('MozAppbarShowing', this, false); 1.20 + Elements.contextappbar.addEventListener('MozAppbarDismissing', this, false); 1.21 + 1.22 + // fired when a context sensitive item (bookmarks) changes state 1.23 + window.addEventListener('MozContextActionsChange', this, false); 1.24 + 1.25 + // browser events we need to update button state on 1.26 + Elements.browsers.addEventListener('URLChanged', this, true); 1.27 + Elements.tabList.addEventListener('TabSelect', this, true); 1.28 + 1.29 + // tilegroup selection events for all modules get bubbled up 1.30 + window.addEventListener("selectionchange", this, false); 1.31 + Services.obs.addObserver(this, "metro_on_async_tile_created", false); 1.32 + 1.33 + // gather appbar telemetry data 1.34 + try { 1.35 + UITelemetry.addSimpleMeasureFunction("metro-appbar", 1.36 + this.getAppbarMeasures.bind(this)); 1.37 + } catch (ex) { 1.38 + // swallow exception that occurs if metro-appbar measure is already set up 1.39 + } 1.40 + }, 1.41 + 1.42 + observe: function(aSubject, aTopic, aData) { 1.43 + switch (aTopic) { 1.44 + case "metro_on_async_tile_created": 1.45 + this._updatePinButton(); 1.46 + break; 1.47 + } 1.48 + }, 1.49 + 1.50 + handleEvent: function Appbar_handleEvent(aEvent) { 1.51 + switch (aEvent.type) { 1.52 + case 'URLChanged': 1.53 + case 'TabSelect': 1.54 + this.update(); 1.55 + this.flushActiveTileset(aEvent.lastTab); 1.56 + break; 1.57 + 1.58 + case 'MozAppbarShowing': 1.59 + this.update(); 1.60 + break; 1.61 + 1.62 + case 'MozAppbarDismissing': 1.63 + if (this.activeTileset && ('isBound' in this.activeTileset)) { 1.64 + this.activeTileset.clearSelection(); 1.65 + } 1.66 + this._clearContextualActions(); 1.67 + this.activeTileset = null; 1.68 + break; 1.69 + 1.70 + case 'MozContextActionsChange': 1.71 + let actions = aEvent.actions; 1.72 + let setName = aEvent.target.contextSetName; 1.73 + // could transition in old, new buttons? 1.74 + this.showContextualActions(actions, setName); 1.75 + break; 1.76 + 1.77 + case "selectionchange": 1.78 + let nodeName = aEvent.target.nodeName; 1.79 + if ('richgrid' === nodeName) { 1.80 + this._onTileSelectionChanged(aEvent); 1.81 + } 1.82 + break; 1.83 + } 1.84 + }, 1.85 + 1.86 + flushActiveTileset: function flushActiveTileset(aTab) { 1.87 + try { 1.88 + let tab = aTab || Browser.selectedTab; 1.89 + // Switching away from or loading a site into a startui tab that has actions 1.90 + // pending, we consider this confirmation that the user wants to flush changes. 1.91 + if (this.activeTileset && tab && tab.browser && tab.browser.currentURI.spec == kStartURI) { 1.92 + ContextUI.dismiss(); 1.93 + } 1.94 + } catch (ex) {} 1.95 + }, 1.96 + 1.97 + shutdown: function shutdown() { 1.98 + this.flushActiveTileset(); 1.99 + }, 1.100 + 1.101 + /* 1.102 + * Called from various places when the visible content 1.103 + * has changed such that button states may need to be 1.104 + * updated. 1.105 + */ 1.106 + update: function update() { 1.107 + this._updatePinButton(); 1.108 + this._updateStarButton(); 1.109 + }, 1.110 + 1.111 + onPinButton: function() { 1.112 + if (this.pinButton.checked) { 1.113 + Browser.pinSite(); 1.114 + } else { 1.115 + Browser.unpinSite(); 1.116 + } 1.117 + }, 1.118 + 1.119 + onStarButton: function(aValue) { 1.120 + if (aValue === undefined) { 1.121 + aValue = this.starButton.checked; 1.122 + } 1.123 + 1.124 + if (aValue) { 1.125 + Browser.starSite(function () { 1.126 + Appbar._updateStarButton(); 1.127 + }); 1.128 + } else { 1.129 + Browser.unstarSite(function () { 1.130 + Appbar._updateStarButton(); 1.131 + }); 1.132 + } 1.133 + }, 1.134 + 1.135 + onMenuButton: function(aEvent) { 1.136 + let typesArray = []; 1.137 + 1.138 + if (BrowserUI.isPrivateBrowsingEnabled) { 1.139 + typesArray.push("private-browsing"); 1.140 + } 1.141 + if (!BrowserUI.isStartTabVisible) { 1.142 + typesArray.push("find-in-page"); 1.143 + if (ContextCommands.getPageSource()) 1.144 + typesArray.push("view-page-source"); 1.145 + } 1.146 + if (ContextCommands.getStoreLink()) 1.147 + typesArray.push("ms-meta-data"); 1.148 + if (ConsolePanelView.enabled) 1.149 + typesArray.push("open-error-console"); 1.150 + if (!Services.metro.immersive) 1.151 + typesArray.push("open-jsshell"); 1.152 + 1.153 + typesArray.push("view-on-desktop"); 1.154 + 1.155 + var x = this.menuButton.getBoundingClientRect().left; 1.156 + var y = Elements.toolbar.getBoundingClientRect().top; 1.157 + ContextMenuUI.showContextMenu({ 1.158 + json: { 1.159 + types: typesArray, 1.160 + string: '', 1.161 + xPos: x, 1.162 + yPos: y, 1.163 + leftAligned: true, 1.164 + bottomAligned: true 1.165 + } 1.166 + 1.167 + }); 1.168 + }, 1.169 + 1.170 + onViewOnDesktop: function() { 1.171 + let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. 1.172 + getService(Components.interfaces.nsIAppStartup); 1.173 + 1.174 + Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); 1.175 + this._incrementCountableEvent("switch-to-desktop-button"); 1.176 + appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | 1.177 + Components.interfaces.nsIAppStartup.eRestart); 1.178 + }, 1.179 + 1.180 + onAutocompleteCloseButton: function () { 1.181 + Elements.autocomplete.closePopup(); 1.182 + }, 1.183 + 1.184 + dispatchContextualAction: function(aActionName){ 1.185 + let activeTileset = this.activeTileset; 1.186 + if (activeTileset && ('isBound' in this.activeTileset)) { 1.187 + // fire event on the richgrid, others can listen 1.188 + // but we keep coupling loose so grid doesn't need to know about appbar 1.189 + let event = document.createEvent("Events"); 1.190 + event.action = aActionName; 1.191 + event.initEvent("context-action", true, true); // is cancelable 1.192 + activeTileset.dispatchEvent(event); 1.193 + if (!event.defaultPrevented) { 1.194 + activeTileset.clearSelection(); 1.195 + Elements.contextappbar.dismiss(); 1.196 + } 1.197 + } 1.198 + }, 1.199 + 1.200 + showContextualActions: function(aVerbs, aSetName) { 1.201 + // When the appbar is not visible, we want the icons to refresh right away 1.202 + let immediate = !Elements.contextappbar.isShowing; 1.203 + 1.204 + if (aVerbs.length) { 1.205 + Elements.contextappbar.show(); 1.206 + } 1.207 + 1.208 + // Look up all of the buttons for the verbs that should be visible. 1.209 + let idsToVisibleVerbs = new Map(); 1.210 + for (let verb of aVerbs) { 1.211 + let id = verb + "-selected-button"; 1.212 + if (!document.getElementById(id)) { 1.213 + throw new Error("Appbar.showContextualActions: no button for " + verb); 1.214 + } 1.215 + idsToVisibleVerbs.set(id, verb); 1.216 + } 1.217 + 1.218 + // Sort buttons into 2 buckets - needing showing and needing hiding. 1.219 + let toHide = [], toShow = []; 1.220 + let buttons = Elements.contextappbar.getElementsByTagName("toolbarbutton"); 1.221 + for (let button of buttons) { 1.222 + let verb = idsToVisibleVerbs.get(button.id); 1.223 + if (verb != undefined) { 1.224 + // Button should be visible, and may or may not be showing. 1.225 + this._updateContextualActionLabel(button, verb, aSetName); 1.226 + if (button.hidden) { 1.227 + toShow.push(button); 1.228 + } 1.229 + } else if (!button.hidden) { 1.230 + // Button is visible, but shouldn't be. 1.231 + toHide.push(button); 1.232 + } 1.233 + } 1.234 + 1.235 + if (immediate) { 1.236 + toShow.forEach(function(element) { 1.237 + element.removeAttribute("fade"); 1.238 + element.hidden = false; 1.239 + }); 1.240 + toHide.forEach(function(element) { 1.241 + element.setAttribute("fade", true); 1.242 + element.hidden = true; 1.243 + }); 1.244 + return; 1.245 + } 1.246 + 1.247 + return Task.spawn(function() { 1.248 + if (toHide.length) { 1.249 + yield Util.transitionElementVisibility(toHide, false); 1.250 + } 1.251 + if (toShow.length) { 1.252 + yield Util.transitionElementVisibility(toShow, true); 1.253 + } 1.254 + }); 1.255 + }, 1.256 + 1.257 + _clearContextualActions: function() { 1.258 + this.showContextualActions([]); 1.259 + }, 1.260 + 1.261 + _updateContextualActionLabel: function(aButton, aVerb, aSetName) { 1.262 + // True if the action's label string contains the set name and 1.263 + // thus has to be selected based on the list passed in. 1.264 + let usesSetName = aButton.hasAttribute("label-uses-set-name"); 1.265 + let name = "contextAppbar2." + aVerb + (usesSetName ? "." + aSetName : ""); 1.266 + aButton.label = Strings.browser.GetStringFromName(name); 1.267 + }, 1.268 + 1.269 + _onTileSelectionChanged: function _onTileSelectionChanged(aEvent){ 1.270 + let activeTileset = aEvent.target; 1.271 + 1.272 + // deselect tiles in other tile groups, 1.273 + // ensure previousyl-activeTileset is bound before calling methods on it 1.274 + if (this.activeTileset && 1.275 + ('isBound' in this.activeTileset) && 1.276 + this.activeTileset !== activeTileset) { 1.277 + this.activeTileset.clearSelection(); 1.278 + } 1.279 + // keep track of which view is the target/scope for the contextual actions 1.280 + this.activeTileset = activeTileset; 1.281 + 1.282 + // ask the view for the list verbs/action-names it thinks are 1.283 + // appropriate for the tiles selected 1.284 + let contextActions = activeTileset.contextActions; 1.285 + let verbs = [v for (v of contextActions)]; 1.286 + 1.287 + // fire event with these verbs as payload 1.288 + let event = document.createEvent("Events"); 1.289 + event.actions = verbs; 1.290 + event.initEvent("MozContextActionsChange", true, false); 1.291 + activeTileset.dispatchEvent(event); 1.292 + 1.293 + if (verbs.length) { 1.294 + Elements.contextappbar.show(); // should be no-op if we're already showing 1.295 + } else { 1.296 + Elements.contextappbar.dismiss(); 1.297 + } 1.298 + }, 1.299 + 1.300 + // track certain appbar events and interactions for the UITelemetry probe 1.301 + _countableEvents: {}, 1.302 + 1.303 + _incrementCountableEvent: function(aName) { 1.304 + if (!(aName in this._countableEvents)) { 1.305 + this._countableEvents[aName] = 0; 1.306 + } 1.307 + this._countableEvents[aName]++; 1.308 + }, 1.309 + 1.310 + getAppbarMeasures: function() { 1.311 + return { 1.312 + countableEvents: this._countableEvents 1.313 + }; 1.314 + }, 1.315 + 1.316 + _updatePinButton: function() { 1.317 + this.pinButton.checked = Browser.isSitePinned(); 1.318 + }, 1.319 + 1.320 + _updateStarButton: function() { 1.321 + Browser.isSiteStarredAsync(function (isStarred) { 1.322 + this.starButton.checked = isStarred; 1.323 + }.bind(this)); 1.324 + }, 1.325 + 1.326 +};