|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 var Appbar = { |
|
7 get starButton() { return document.getElementById('star-button'); }, |
|
8 get pinButton() { return document.getElementById('pin-button'); }, |
|
9 get menuButton() { return document.getElementById('menu-button'); }, |
|
10 |
|
11 // track selected/active richgrid/tilegroup - the context for contextual action buttons |
|
12 activeTileset: null, |
|
13 |
|
14 init: function Appbar_init() { |
|
15 // fired from appbar bindings |
|
16 Elements.contextappbar.addEventListener('MozAppbarShowing', this, false); |
|
17 Elements.contextappbar.addEventListener('MozAppbarDismissing', this, false); |
|
18 |
|
19 // fired when a context sensitive item (bookmarks) changes state |
|
20 window.addEventListener('MozContextActionsChange', this, false); |
|
21 |
|
22 // browser events we need to update button state on |
|
23 Elements.browsers.addEventListener('URLChanged', this, true); |
|
24 Elements.tabList.addEventListener('TabSelect', this, true); |
|
25 |
|
26 // tilegroup selection events for all modules get bubbled up |
|
27 window.addEventListener("selectionchange", this, false); |
|
28 Services.obs.addObserver(this, "metro_on_async_tile_created", false); |
|
29 |
|
30 // gather appbar telemetry data |
|
31 try { |
|
32 UITelemetry.addSimpleMeasureFunction("metro-appbar", |
|
33 this.getAppbarMeasures.bind(this)); |
|
34 } catch (ex) { |
|
35 // swallow exception that occurs if metro-appbar measure is already set up |
|
36 } |
|
37 }, |
|
38 |
|
39 observe: function(aSubject, aTopic, aData) { |
|
40 switch (aTopic) { |
|
41 case "metro_on_async_tile_created": |
|
42 this._updatePinButton(); |
|
43 break; |
|
44 } |
|
45 }, |
|
46 |
|
47 handleEvent: function Appbar_handleEvent(aEvent) { |
|
48 switch (aEvent.type) { |
|
49 case 'URLChanged': |
|
50 case 'TabSelect': |
|
51 this.update(); |
|
52 this.flushActiveTileset(aEvent.lastTab); |
|
53 break; |
|
54 |
|
55 case 'MozAppbarShowing': |
|
56 this.update(); |
|
57 break; |
|
58 |
|
59 case 'MozAppbarDismissing': |
|
60 if (this.activeTileset && ('isBound' in this.activeTileset)) { |
|
61 this.activeTileset.clearSelection(); |
|
62 } |
|
63 this._clearContextualActions(); |
|
64 this.activeTileset = null; |
|
65 break; |
|
66 |
|
67 case 'MozContextActionsChange': |
|
68 let actions = aEvent.actions; |
|
69 let setName = aEvent.target.contextSetName; |
|
70 // could transition in old, new buttons? |
|
71 this.showContextualActions(actions, setName); |
|
72 break; |
|
73 |
|
74 case "selectionchange": |
|
75 let nodeName = aEvent.target.nodeName; |
|
76 if ('richgrid' === nodeName) { |
|
77 this._onTileSelectionChanged(aEvent); |
|
78 } |
|
79 break; |
|
80 } |
|
81 }, |
|
82 |
|
83 flushActiveTileset: function flushActiveTileset(aTab) { |
|
84 try { |
|
85 let tab = aTab || Browser.selectedTab; |
|
86 // Switching away from or loading a site into a startui tab that has actions |
|
87 // pending, we consider this confirmation that the user wants to flush changes. |
|
88 if (this.activeTileset && tab && tab.browser && tab.browser.currentURI.spec == kStartURI) { |
|
89 ContextUI.dismiss(); |
|
90 } |
|
91 } catch (ex) {} |
|
92 }, |
|
93 |
|
94 shutdown: function shutdown() { |
|
95 this.flushActiveTileset(); |
|
96 }, |
|
97 |
|
98 /* |
|
99 * Called from various places when the visible content |
|
100 * has changed such that button states may need to be |
|
101 * updated. |
|
102 */ |
|
103 update: function update() { |
|
104 this._updatePinButton(); |
|
105 this._updateStarButton(); |
|
106 }, |
|
107 |
|
108 onPinButton: function() { |
|
109 if (this.pinButton.checked) { |
|
110 Browser.pinSite(); |
|
111 } else { |
|
112 Browser.unpinSite(); |
|
113 } |
|
114 }, |
|
115 |
|
116 onStarButton: function(aValue) { |
|
117 if (aValue === undefined) { |
|
118 aValue = this.starButton.checked; |
|
119 } |
|
120 |
|
121 if (aValue) { |
|
122 Browser.starSite(function () { |
|
123 Appbar._updateStarButton(); |
|
124 }); |
|
125 } else { |
|
126 Browser.unstarSite(function () { |
|
127 Appbar._updateStarButton(); |
|
128 }); |
|
129 } |
|
130 }, |
|
131 |
|
132 onMenuButton: function(aEvent) { |
|
133 let typesArray = []; |
|
134 |
|
135 if (BrowserUI.isPrivateBrowsingEnabled) { |
|
136 typesArray.push("private-browsing"); |
|
137 } |
|
138 if (!BrowserUI.isStartTabVisible) { |
|
139 typesArray.push("find-in-page"); |
|
140 if (ContextCommands.getPageSource()) |
|
141 typesArray.push("view-page-source"); |
|
142 } |
|
143 if (ContextCommands.getStoreLink()) |
|
144 typesArray.push("ms-meta-data"); |
|
145 if (ConsolePanelView.enabled) |
|
146 typesArray.push("open-error-console"); |
|
147 if (!Services.metro.immersive) |
|
148 typesArray.push("open-jsshell"); |
|
149 |
|
150 typesArray.push("view-on-desktop"); |
|
151 |
|
152 var x = this.menuButton.getBoundingClientRect().left; |
|
153 var y = Elements.toolbar.getBoundingClientRect().top; |
|
154 ContextMenuUI.showContextMenu({ |
|
155 json: { |
|
156 types: typesArray, |
|
157 string: '', |
|
158 xPos: x, |
|
159 yPos: y, |
|
160 leftAligned: true, |
|
161 bottomAligned: true |
|
162 } |
|
163 |
|
164 }); |
|
165 }, |
|
166 |
|
167 onViewOnDesktop: function() { |
|
168 let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]. |
|
169 getService(Components.interfaces.nsIAppStartup); |
|
170 |
|
171 Services.prefs.setBoolPref('browser.sessionstore.resume_session_once', true); |
|
172 this._incrementCountableEvent("switch-to-desktop-button"); |
|
173 appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit | |
|
174 Components.interfaces.nsIAppStartup.eRestart); |
|
175 }, |
|
176 |
|
177 onAutocompleteCloseButton: function () { |
|
178 Elements.autocomplete.closePopup(); |
|
179 }, |
|
180 |
|
181 dispatchContextualAction: function(aActionName){ |
|
182 let activeTileset = this.activeTileset; |
|
183 if (activeTileset && ('isBound' in this.activeTileset)) { |
|
184 // fire event on the richgrid, others can listen |
|
185 // but we keep coupling loose so grid doesn't need to know about appbar |
|
186 let event = document.createEvent("Events"); |
|
187 event.action = aActionName; |
|
188 event.initEvent("context-action", true, true); // is cancelable |
|
189 activeTileset.dispatchEvent(event); |
|
190 if (!event.defaultPrevented) { |
|
191 activeTileset.clearSelection(); |
|
192 Elements.contextappbar.dismiss(); |
|
193 } |
|
194 } |
|
195 }, |
|
196 |
|
197 showContextualActions: function(aVerbs, aSetName) { |
|
198 // When the appbar is not visible, we want the icons to refresh right away |
|
199 let immediate = !Elements.contextappbar.isShowing; |
|
200 |
|
201 if (aVerbs.length) { |
|
202 Elements.contextappbar.show(); |
|
203 } |
|
204 |
|
205 // Look up all of the buttons for the verbs that should be visible. |
|
206 let idsToVisibleVerbs = new Map(); |
|
207 for (let verb of aVerbs) { |
|
208 let id = verb + "-selected-button"; |
|
209 if (!document.getElementById(id)) { |
|
210 throw new Error("Appbar.showContextualActions: no button for " + verb); |
|
211 } |
|
212 idsToVisibleVerbs.set(id, verb); |
|
213 } |
|
214 |
|
215 // Sort buttons into 2 buckets - needing showing and needing hiding. |
|
216 let toHide = [], toShow = []; |
|
217 let buttons = Elements.contextappbar.getElementsByTagName("toolbarbutton"); |
|
218 for (let button of buttons) { |
|
219 let verb = idsToVisibleVerbs.get(button.id); |
|
220 if (verb != undefined) { |
|
221 // Button should be visible, and may or may not be showing. |
|
222 this._updateContextualActionLabel(button, verb, aSetName); |
|
223 if (button.hidden) { |
|
224 toShow.push(button); |
|
225 } |
|
226 } else if (!button.hidden) { |
|
227 // Button is visible, but shouldn't be. |
|
228 toHide.push(button); |
|
229 } |
|
230 } |
|
231 |
|
232 if (immediate) { |
|
233 toShow.forEach(function(element) { |
|
234 element.removeAttribute("fade"); |
|
235 element.hidden = false; |
|
236 }); |
|
237 toHide.forEach(function(element) { |
|
238 element.setAttribute("fade", true); |
|
239 element.hidden = true; |
|
240 }); |
|
241 return; |
|
242 } |
|
243 |
|
244 return Task.spawn(function() { |
|
245 if (toHide.length) { |
|
246 yield Util.transitionElementVisibility(toHide, false); |
|
247 } |
|
248 if (toShow.length) { |
|
249 yield Util.transitionElementVisibility(toShow, true); |
|
250 } |
|
251 }); |
|
252 }, |
|
253 |
|
254 _clearContextualActions: function() { |
|
255 this.showContextualActions([]); |
|
256 }, |
|
257 |
|
258 _updateContextualActionLabel: function(aButton, aVerb, aSetName) { |
|
259 // True if the action's label string contains the set name and |
|
260 // thus has to be selected based on the list passed in. |
|
261 let usesSetName = aButton.hasAttribute("label-uses-set-name"); |
|
262 let name = "contextAppbar2." + aVerb + (usesSetName ? "." + aSetName : ""); |
|
263 aButton.label = Strings.browser.GetStringFromName(name); |
|
264 }, |
|
265 |
|
266 _onTileSelectionChanged: function _onTileSelectionChanged(aEvent){ |
|
267 let activeTileset = aEvent.target; |
|
268 |
|
269 // deselect tiles in other tile groups, |
|
270 // ensure previousyl-activeTileset is bound before calling methods on it |
|
271 if (this.activeTileset && |
|
272 ('isBound' in this.activeTileset) && |
|
273 this.activeTileset !== activeTileset) { |
|
274 this.activeTileset.clearSelection(); |
|
275 } |
|
276 // keep track of which view is the target/scope for the contextual actions |
|
277 this.activeTileset = activeTileset; |
|
278 |
|
279 // ask the view for the list verbs/action-names it thinks are |
|
280 // appropriate for the tiles selected |
|
281 let contextActions = activeTileset.contextActions; |
|
282 let verbs = [v for (v of contextActions)]; |
|
283 |
|
284 // fire event with these verbs as payload |
|
285 let event = document.createEvent("Events"); |
|
286 event.actions = verbs; |
|
287 event.initEvent("MozContextActionsChange", true, false); |
|
288 activeTileset.dispatchEvent(event); |
|
289 |
|
290 if (verbs.length) { |
|
291 Elements.contextappbar.show(); // should be no-op if we're already showing |
|
292 } else { |
|
293 Elements.contextappbar.dismiss(); |
|
294 } |
|
295 }, |
|
296 |
|
297 // track certain appbar events and interactions for the UITelemetry probe |
|
298 _countableEvents: {}, |
|
299 |
|
300 _incrementCountableEvent: function(aName) { |
|
301 if (!(aName in this._countableEvents)) { |
|
302 this._countableEvents[aName] = 0; |
|
303 } |
|
304 this._countableEvents[aName]++; |
|
305 }, |
|
306 |
|
307 getAppbarMeasures: function() { |
|
308 return { |
|
309 countableEvents: this._countableEvents |
|
310 }; |
|
311 }, |
|
312 |
|
313 _updatePinButton: function() { |
|
314 this.pinButton.checked = Browser.isSitePinned(); |
|
315 }, |
|
316 |
|
317 _updateStarButton: function() { |
|
318 Browser.isSiteStarredAsync(function (isStarred) { |
|
319 this.starButton.checked = isStarred; |
|
320 }.bind(this)); |
|
321 }, |
|
322 |
|
323 }; |