|
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 |
|
5 // Fired when the tabtray is displayed |
|
6 const kContextUITabsShowEvent = "MozContextUITabsShow"; |
|
7 // add more as needed... |
|
8 |
|
9 /* |
|
10 * Manages context UI (navbar, tabbar, appbar) and track visibility. Also |
|
11 * tracks events that summon and hide the context UI. |
|
12 */ |
|
13 var ContextUI = { |
|
14 _expandable: true, |
|
15 _hidingId: 0, |
|
16 |
|
17 /******************************************* |
|
18 * init |
|
19 */ |
|
20 |
|
21 init: function init() { |
|
22 Elements.browsers.addEventListener('URLChanged', this, true); |
|
23 Elements.browsers.addEventListener("AlertActive", this, true); |
|
24 Elements.browsers.addEventListener("AlertClose", this, true); |
|
25 Elements.tabList.addEventListener('TabSelect', this, true); |
|
26 Elements.panelUI.addEventListener('ToolPanelShown', this, false); |
|
27 Elements.panelUI.addEventListener('ToolPanelHidden', this, false); |
|
28 |
|
29 Elements.tray.addEventListener("mousemove", this, false); |
|
30 Elements.tray.addEventListener("mouseleave", this, false); |
|
31 |
|
32 window.addEventListener("touchstart", this, true); |
|
33 window.addEventListener("mousedown", this, true); |
|
34 window.addEventListener("MozEdgeUIStarted", this, true); |
|
35 window.addEventListener("MozEdgeUICanceled", this, true); |
|
36 window.addEventListener("MozEdgeUICompleted", this, true); |
|
37 window.addEventListener("keypress", this, true); |
|
38 window.addEventListener("KeyboardChanged", this, false); |
|
39 window.addEventListener("MozFlyoutPanelShowing", this, false); |
|
40 |
|
41 Elements.tray.addEventListener("transitionend", this, true); |
|
42 |
|
43 Appbar.init(); |
|
44 }, |
|
45 |
|
46 /******************************************* |
|
47 * Context UI state getters & setters |
|
48 */ |
|
49 |
|
50 // any visiblilty |
|
51 get isVisible() { |
|
52 return this.navbarVisible || this.tabbarVisible || this.contextAppbarVisible; |
|
53 }, |
|
54 |
|
55 // navbar visiblilty |
|
56 get navbarVisible() { |
|
57 return (Elements.navbar.hasAttribute("visible") || |
|
58 Elements.navbar.hasAttribute("startpage")); |
|
59 }, |
|
60 |
|
61 // tabbar visiblilty |
|
62 get tabbarVisible() { |
|
63 return Elements.tray.hasAttribute("expanded"); |
|
64 }, |
|
65 |
|
66 // appbar visiblilty |
|
67 get contextAppbarVisible() { |
|
68 return Elements.contextappbar.isShowing; |
|
69 }, |
|
70 |
|
71 // currently not in use, for the always show tabs feature |
|
72 get isExpandable() { return this._expandable; }, |
|
73 set isExpandable(aFlag) { |
|
74 this._expandable = aFlag; |
|
75 if (!this._expandable) |
|
76 this.dismiss(); |
|
77 }, |
|
78 |
|
79 /******************************************* |
|
80 * Public api |
|
81 */ |
|
82 |
|
83 /* |
|
84 * Toggle the current nav UI state. Fires context ui events. |
|
85 */ |
|
86 toggleNavUI: function () { |
|
87 // The navbar is forced open when the start page is visible. appbar.js |
|
88 // controls the "visible" property, and browser-ui controls the "startpage" |
|
89 // property. Hence we rely on the tabbar for current toggle state. |
|
90 if (this.tabbarVisible) { |
|
91 this.dismiss(); |
|
92 } else { |
|
93 this.displayNavUI(); |
|
94 } |
|
95 }, |
|
96 |
|
97 /* |
|
98 * Show the nav and tabs bar. Returns true if any non-visible UI |
|
99 * was shown. Fires context ui events. |
|
100 */ |
|
101 displayNavUI: function () { |
|
102 let shown = false; |
|
103 |
|
104 if (!this.navbarVisible) { |
|
105 BrowserUI.updateURI(); |
|
106 this.displayNavbar(); |
|
107 shown = true; |
|
108 } |
|
109 |
|
110 if (!this.tabbarVisible) { |
|
111 this.displayTabs(); |
|
112 shown = true; |
|
113 } |
|
114 |
|
115 if (shown) { |
|
116 ContentAreaObserver.updateContentArea(); |
|
117 } |
|
118 |
|
119 return shown; |
|
120 }, |
|
121 |
|
122 /* |
|
123 * Dismiss any context UI currently visible. Returns true if any |
|
124 * visible UI was dismissed. Fires context ui events. |
|
125 */ |
|
126 dismiss: function () { |
|
127 let dismissed = false; |
|
128 |
|
129 this._clearDelayedTimeout(); |
|
130 |
|
131 // No assurances this will hide the nav bar. It may have the |
|
132 // 'startpage' property set. This removes the 'visible' property. |
|
133 if (this.navbarVisible) { |
|
134 BrowserUI.blurNavBar(); |
|
135 this.dismissNavbar(); |
|
136 dismissed = true; |
|
137 } |
|
138 if (this.tabbarVisible) { |
|
139 this.dismissTabs(); |
|
140 dismissed = true; |
|
141 } |
|
142 if (Elements.contextappbar.isShowing) { |
|
143 this.dismissContextAppbar(); |
|
144 dismissed = true; |
|
145 } |
|
146 |
|
147 if (dismissed) { |
|
148 ContentAreaObserver.updateContentArea(); |
|
149 } |
|
150 |
|
151 return dismissed; |
|
152 }, |
|
153 |
|
154 /* |
|
155 * Briefly show the tab bar and then hide it. Fires context ui events. |
|
156 */ |
|
157 peekTabs: function peekTabs(aDelay) { |
|
158 if (!this.tabbarVisible) |
|
159 this.displayTabs(); |
|
160 |
|
161 ContextUI.dismissTabsWithDelay(aDelay); |
|
162 }, |
|
163 |
|
164 /* |
|
165 * Dismiss tab bar after a delay. Fires context ui events. |
|
166 */ |
|
167 dismissTabsWithDelay: function (aDelay) { |
|
168 aDelay = aDelay || kForegroundTabAnimationDelay; |
|
169 this._clearDelayedTimeout(); |
|
170 this._lastTimeoutDelay = aDelay; |
|
171 this._hidingId = setTimeout(function () { |
|
172 ContextUI.dismissTabs(); |
|
173 }, aDelay); |
|
174 }, |
|
175 |
|
176 /* |
|
177 * Display the nav bar. |
|
178 * |
|
179 * @return false if we were already visible, and didn't do anything. |
|
180 */ |
|
181 displayNavbar: function () { |
|
182 if (Elements.chromeState.getAttribute("navbar") == "visible") { |
|
183 return false; |
|
184 } |
|
185 |
|
186 Elements.navbar.show(); |
|
187 Elements.chromeState.setAttribute("navbar", "visible"); |
|
188 ContentAreaObserver.updateContentArea(); |
|
189 return true; |
|
190 }, |
|
191 |
|
192 // Display the tab tray |
|
193 displayTabs: function () { |
|
194 this._clearDelayedTimeout(); |
|
195 this._setIsExpanded(true); |
|
196 }, |
|
197 |
|
198 // Dismiss the navbar if visible. |
|
199 dismissNavbar: function dismissNavbar() { |
|
200 if (!BrowserUI.isStartTabVisible) { |
|
201 Elements.autocomplete.closePopup(); |
|
202 Elements.navbar.dismiss(); |
|
203 Elements.chromeState.removeAttribute("navbar"); |
|
204 ContentAreaObserver.updateContentArea(); |
|
205 } |
|
206 }, |
|
207 |
|
208 // Dismiss the tabstray if visible. |
|
209 dismissTabs: function dimissTabs() { |
|
210 this._clearDelayedTimeout(); |
|
211 this._setIsExpanded(false); |
|
212 }, |
|
213 |
|
214 // Dismiss the appbar if visible. |
|
215 dismissContextAppbar: function dismissContextAppbar() { |
|
216 Elements.contextappbar.dismiss(); |
|
217 }, |
|
218 |
|
219 /******************************************* |
|
220 * Internal utils |
|
221 */ |
|
222 |
|
223 // tabtray state |
|
224 _setIsExpanded: function _setIsExpanded(aFlag, setSilently) { |
|
225 // if the tray can't be expanded, don't expand it. |
|
226 if (!this.isExpandable || this.tabbarVisible == aFlag) |
|
227 return; |
|
228 |
|
229 if (aFlag) |
|
230 Elements.tray.setAttribute("expanded", "true"); |
|
231 else |
|
232 Elements.tray.removeAttribute("expanded"); |
|
233 |
|
234 if (!setSilently) |
|
235 this._fire(kContextUITabsShowEvent); |
|
236 }, |
|
237 |
|
238 _clearDelayedTimeout: function _clearDelayedTimeout() { |
|
239 if (this._hidingId) { |
|
240 clearTimeout(this._hidingId); |
|
241 this._hidingId = 0; |
|
242 this._delayedHide = false; |
|
243 } |
|
244 }, |
|
245 |
|
246 _resetDelayedTimeout: function () { |
|
247 this._hidingId = setTimeout(function () { |
|
248 ContextUI.dismissTabs(); |
|
249 }, this._lastTimeoutDelay); |
|
250 }, |
|
251 |
|
252 /******************************************* |
|
253 * Events |
|
254 */ |
|
255 |
|
256 _onEdgeUIStarted: function(aEvent) { |
|
257 this._hasEdgeSwipeStarted = true; |
|
258 this._clearDelayedTimeout(); |
|
259 this.toggleNavUI(); |
|
260 }, |
|
261 |
|
262 _onEdgeUICanceled: function(aEvent) { |
|
263 this._hasEdgeSwipeStarted = false; |
|
264 this.dismiss(); |
|
265 }, |
|
266 |
|
267 _onEdgeUICompleted: function(aEvent) { |
|
268 if (this._hasEdgeSwipeStarted) { |
|
269 this._hasEdgeSwipeStarted = false; |
|
270 return; |
|
271 } |
|
272 |
|
273 this._clearDelayedTimeout(); |
|
274 this.toggleNavUI(); |
|
275 }, |
|
276 |
|
277 onDownInput: function onDownInput(aEvent) { |
|
278 if (!this.isVisible) { |
|
279 return; |
|
280 } |
|
281 |
|
282 // Various ui element containers we do not update context ui for. |
|
283 let whitelist = [ |
|
284 // Clicks on tab bar elements should not close the tab bar. the tabbar |
|
285 // handles this. |
|
286 Elements.tabs, |
|
287 // Don't let a click on an infobar button dismiss the appbar or navbar. |
|
288 // Note the notification box should always hover above these other two |
|
289 // bars. |
|
290 Browser.getNotificationBox() |
|
291 ]; |
|
292 |
|
293 if (whitelist.some(elem => elem.contains(aEvent.target))) { |
|
294 return; |
|
295 } |
|
296 |
|
297 // If a start tab is visible only dismiss the tab bar. |
|
298 if (BrowserUI.isStartTabVisible) { |
|
299 ContextUI.dismissTabs(); |
|
300 return; |
|
301 } |
|
302 |
|
303 // content, dismiss anything visible |
|
304 if (aEvent.target.ownerDocument.defaultView.top == getBrowser().contentWindow) { |
|
305 this.dismiss(); |
|
306 return; |
|
307 } |
|
308 |
|
309 // dismiss tabs and context app bar if visible |
|
310 this.dismissTabs(); |
|
311 this.dismissContextAppbar(); |
|
312 }, |
|
313 |
|
314 onMouseMove: function (aEvent) { |
|
315 if (this._hidingId) { |
|
316 this._clearDelayedTimeout(); |
|
317 this._delayedHide = true; |
|
318 } |
|
319 }, |
|
320 |
|
321 onMouseLeave: function (aEvent) { |
|
322 if (this._delayedHide) { |
|
323 this._delayedHide = false; |
|
324 this._resetDelayedTimeout(); |
|
325 } |
|
326 }, |
|
327 |
|
328 handleEvent: function handleEvent(aEvent) { |
|
329 switch (aEvent.type) { |
|
330 case "URLChanged": |
|
331 // "aEvent.detail" is a boolean value that indicates whether actual URL |
|
332 // has changed ignoring URL fragment changes. |
|
333 if (aEvent.target == Browser.selectedBrowser && aEvent.detail) { |
|
334 this.displayNavbar(); |
|
335 } |
|
336 break; |
|
337 case "MozEdgeUIStarted": |
|
338 this._onEdgeUIStarted(aEvent); |
|
339 break; |
|
340 case "MozEdgeUICanceled": |
|
341 this._onEdgeUICanceled(aEvent); |
|
342 break; |
|
343 case "MozEdgeUICompleted": |
|
344 this._onEdgeUICompleted(aEvent); |
|
345 break; |
|
346 case "keypress": |
|
347 if (String.fromCharCode(aEvent.which) == "z" && |
|
348 aEvent.getModifierState("Win")) |
|
349 this.toggleNavUI(); |
|
350 break; |
|
351 case "transitionend": |
|
352 setTimeout(function () { |
|
353 ContentAreaObserver.updateContentArea(); |
|
354 }, 0); |
|
355 break; |
|
356 case "KeyboardChanged": |
|
357 this.dismissTabs(); |
|
358 break; |
|
359 case "mousedown": |
|
360 if (aEvent.button != 0) { |
|
361 break; |
|
362 } |
|
363 this.onDownInput(aEvent); |
|
364 break; |
|
365 case "mousemove": |
|
366 this.onMouseMove(aEvent); |
|
367 break; |
|
368 case "mouseleave": |
|
369 this.onMouseLeave(aEvent); |
|
370 break; |
|
371 case "touchstart": |
|
372 this.onDownInput(aEvent); |
|
373 break; |
|
374 case "ToolPanelShown": |
|
375 case "ToolPanelHidden": |
|
376 this.dismiss(); |
|
377 break; |
|
378 case "AlertActive": |
|
379 case "AlertClose": |
|
380 case "TabSelect": |
|
381 ContentAreaObserver.updateContentArea(); |
|
382 break; |
|
383 case "MozFlyoutPanelShowing": |
|
384 if (BrowserUI.isStartTabVisible) { |
|
385 this.dismissTabs(); |
|
386 this.dismissContextAppbar(); |
|
387 } else { |
|
388 this.dismiss(); |
|
389 } |
|
390 break; |
|
391 } |
|
392 }, |
|
393 |
|
394 _fire: function (name) { |
|
395 let event = document.createEvent("Events"); |
|
396 event.initEvent(name, true, true); |
|
397 window.dispatchEvent(event); |
|
398 } |
|
399 }; |