browser/base/content/browser-fullScreen.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:25d518f0f08f
1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6 var FullScreen = {
7 _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
8 get _fullScrToggler() {
9 delete this._fullScrToggler;
10 return this._fullScrToggler = document.getElementById("fullscr-toggler");
11 },
12 toggle: function (event) {
13 var enterFS = window.fullScreen;
14
15 // We get the fullscreen event _before_ the window transitions into or out of FS mode.
16 if (event && event.type == "fullscreen")
17 enterFS = !enterFS;
18
19 // Toggle the View:FullScreen command, which controls elements like the
20 // fullscreen menuitem, and menubars.
21 let fullscreenCommand = document.getElementById("View:FullScreen");
22 if (enterFS) {
23 fullscreenCommand.setAttribute("checked", enterFS);
24 } else {
25 fullscreenCommand.removeAttribute("checked");
26 }
27
28 #ifdef XP_MACOSX
29 // Make sure the menu items are adjusted.
30 document.getElementById("enterFullScreenItem").hidden = enterFS;
31 document.getElementById("exitFullScreenItem").hidden = !enterFS;
32 #endif
33
34 // On OS X Lion we don't want to hide toolbars when entering fullscreen, unless
35 // we're entering DOM fullscreen, in which case we should hide the toolbars.
36 // If we're leaving fullscreen, then we'll go through the exit code below to
37 // make sure toolbars are made visible in the case of DOM fullscreen.
38 if (enterFS && this.useLionFullScreen) {
39 if (document.mozFullScreen) {
40 this.showXULChrome("toolbar", false);
41 }
42 else {
43 gNavToolbox.setAttribute("inFullscreen", true);
44 document.documentElement.setAttribute("inFullscreen", true);
45 }
46 return;
47 }
48
49 // show/hide menubars, toolbars (except the full screen toolbar)
50 this.showXULChrome("toolbar", !enterFS);
51
52 if (enterFS) {
53 // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance.
54 // This will help simulate the "collapse" metaphor while also requiring less code and
55 // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen
56 // mode, only browser full-screen mode.
57 if (!document.mozFullScreen) {
58 this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
59 this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
60 }
61 if (gPrefService.getBoolPref("browser.fullscreen.autohide"))
62 gBrowser.mPanelContainer.addEventListener("mousemove",
63 this._collapseCallback, false);
64
65 document.addEventListener("keypress", this._keyToggleCallback, false);
66 document.addEventListener("popupshown", this._setPopupOpen, false);
67 document.addEventListener("popuphidden", this._setPopupOpen, false);
68 // We don't animate the toolbar collapse if in DOM full-screen mode,
69 // as the size of the content area would still be changing after the
70 // mozfullscreenchange event fired, which could confuse content script.
71 this._shouldAnimate = !document.mozFullScreen;
72 this.mouseoverToggle(false);
73
74 // Autohide prefs
75 gPrefService.addObserver("browser.fullscreen", this, false);
76 }
77 else {
78 // The user may quit fullscreen during an animation
79 this._cancelAnimation();
80 gNavToolbox.style.marginTop = "";
81 if (this._isChromeCollapsed)
82 this.mouseoverToggle(true);
83 // This is needed if they use the context menu to quit fullscreen
84 this._isPopupOpen = false;
85
86 this.cleanup();
87 }
88 },
89
90 exitDomFullScreen : function() {
91 document.mozCancelFullScreen();
92 },
93
94 handleEvent: function (event) {
95 switch (event.type) {
96 case "activate":
97 if (document.mozFullScreen) {
98 this.showWarning(this.fullscreenDoc);
99 }
100 break;
101 case "transitionend":
102 if (event.propertyName == "opacity")
103 this.cancelWarning();
104 break;
105 }
106 },
107
108 enterDomFullscreen : function(event) {
109 if (!document.mozFullScreen)
110 return;
111
112 // However, if we receive a "MozEnteredDomFullScreen" event for a document
113 // which is not a subdocument of a currently active (ie. visible) browser
114 // or iframe, we know that we've switched to a different frame since the
115 // request to enter full-screen was made, so we should exit full-screen
116 // since the "full-screen document" isn't acutally visible.
117 if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
118 .getInterface(Ci.nsIWebNavigation)
119 .QueryInterface(Ci.nsIDocShell).isActive) {
120 document.mozCancelFullScreen();
121 return;
122 }
123
124 let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
125 if (focusManager.activeWindow != window) {
126 // The top-level window has lost focus since the request to enter
127 // full-screen was made. Cancel full-screen.
128 document.mozCancelFullScreen();
129 return;
130 }
131
132 // Ensure the sidebar is hidden.
133 if (!document.getElementById("sidebar-box").hidden)
134 toggleSidebar();
135
136 if (gFindBarInitialized)
137 gFindBar.close();
138
139 this.showWarning(event.target);
140
141 // Exit DOM full-screen mode upon open, close, or change tab.
142 gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
143 gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
144 gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
145
146 // Add listener to detect when the fullscreen window is re-focused.
147 // If a fullscreen window loses focus, we show a warning when the
148 // fullscreen window is refocused.
149 if (!this.useLionFullScreen) {
150 window.addEventListener("activate", this);
151 }
152
153 // Cancel any "hide the toolbar" animation which is in progress, and make
154 // the toolbar hide immediately.
155 this._cancelAnimation();
156 this.mouseoverToggle(false);
157
158 // Remove listeners on the full-screen toggler, so that mouseover
159 // the top of the screen will not cause the toolbar to re-appear.
160 this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
161 this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
162 },
163
164 cleanup: function () {
165 if (window.fullScreen) {
166 gBrowser.mPanelContainer.removeEventListener("mousemove",
167 this._collapseCallback, false);
168 document.removeEventListener("keypress", this._keyToggleCallback, false);
169 document.removeEventListener("popupshown", this._setPopupOpen, false);
170 document.removeEventListener("popuphidden", this._setPopupOpen, false);
171 gPrefService.removeObserver("browser.fullscreen", this);
172
173 this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
174 this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
175 this.cancelWarning();
176 gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
177 gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
178 gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
179 if (!this.useLionFullScreen)
180 window.removeEventListener("activate", this);
181 this.fullscreenDoc = null;
182 }
183 },
184
185 observe: function(aSubject, aTopic, aData)
186 {
187 if (aData == "browser.fullscreen.autohide") {
188 if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
189 gBrowser.mPanelContainer.addEventListener("mousemove",
190 this._collapseCallback, false);
191 }
192 else {
193 gBrowser.mPanelContainer.removeEventListener("mousemove",
194 this._collapseCallback, false);
195 }
196 }
197 },
198
199 // Event callbacks
200 _expandCallback: function()
201 {
202 FullScreen.mouseoverToggle(true);
203 },
204 _collapseCallback: function()
205 {
206 FullScreen.mouseoverToggle(false);
207 },
208 _keyToggleCallback: function(aEvent)
209 {
210 // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
211 // should provide a way to collapse them too.
212 if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
213 FullScreen._shouldAnimate = false;
214 FullScreen.mouseoverToggle(false, true);
215 }
216 // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
217 else if (aEvent.keyCode == aEvent.DOM_VK_F6)
218 FullScreen.mouseoverToggle(true);
219 },
220
221 // Checks whether we are allowed to collapse the chrome
222 _isPopupOpen: false,
223 _isChromeCollapsed: false,
224 _safeToCollapse: function(forceHide)
225 {
226 if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
227 return false;
228
229 // a popup menu is open in chrome: don't collapse chrome
230 if (!forceHide && this._isPopupOpen)
231 return false;
232
233 // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
234 if (document.commandDispatcher.focusedElement &&
235 document.commandDispatcher.focusedElement.ownerDocument == document &&
236 document.commandDispatcher.focusedElement.localName == "input") {
237 if (forceHide)
238 // hidden textboxes that still have focus are bad bad bad
239 document.commandDispatcher.focusedElement.blur();
240 else
241 return false;
242 }
243 return true;
244 },
245
246 _setPopupOpen: function(aEvent)
247 {
248 // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
249 // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
250 // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
251 // toggles chrome when moving mouse to the top, it doesn't go away again.
252 if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
253 aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
254 FullScreen._isPopupOpen = true;
255 else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
256 aEvent.target.localName != "window")
257 FullScreen._isPopupOpen = false;
258 },
259
260 // Autohide helpers for the context menu item
261 getAutohide: function(aItem)
262 {
263 aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
264 },
265 setAutohide: function()
266 {
267 gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
268 },
269
270 // Animate the toolbars disappearing
271 _shouldAnimate: true,
272 _isAnimating: false,
273 _animationTimeout: 0,
274 _animationHandle: 0,
275 _animateUp: function() {
276 // check again, the user may have done something before the animation was due to start
277 if (!window.fullScreen || !this._safeToCollapse(false)) {
278 this._isAnimating = false;
279 this._shouldAnimate = true;
280 return;
281 }
282
283 this._animateStartTime = window.mozAnimationStartTime;
284 if (!this._animationHandle)
285 this._animationHandle = window.mozRequestAnimationFrame(this);
286 },
287
288 sample: function (timeStamp) {
289 const duration = 1500;
290 const timePassed = timeStamp - this._animateStartTime;
291 const pos = timePassed >= duration ? 1 :
292 1 - Math.pow(1 - timePassed / duration, 4);
293
294 if (pos >= 1) {
295 // We've animated enough
296 this._cancelAnimation();
297 gNavToolbox.style.marginTop = "";
298 this.mouseoverToggle(false);
299 return;
300 }
301
302 gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px";
303 this._animationHandle = window.mozRequestAnimationFrame(this);
304 },
305
306 _cancelAnimation: function() {
307 window.mozCancelAnimationFrame(this._animationHandle);
308 this._animationHandle = 0;
309 clearTimeout(this._animationTimeout);
310 this._isAnimating = false;
311 this._shouldAnimate = false;
312 },
313
314 cancelWarning: function(event) {
315 if (!this.warningBox)
316 return;
317 this.warningBox.removeEventListener("transitionend", this);
318 if (this.warningFadeOutTimeout) {
319 clearTimeout(this.warningFadeOutTimeout);
320 this.warningFadeOutTimeout = null;
321 }
322
323 // Ensure focus switches away from the (now hidden) warning box. If the user
324 // clicked buttons in the fullscreen key authorization UI, it would have been
325 // focused, and any key events would be directed at the (now hidden) chrome
326 // document instead of the target document.
327 gBrowser.selectedBrowser.focus();
328
329 this.warningBox.setAttribute("hidden", true);
330 this.warningBox.removeAttribute("fade-warning-out");
331 this.warningBox.removeAttribute("obscure-browser");
332 this.warningBox = null;
333 },
334
335 setFullscreenAllowed: function(isApproved) {
336 // The "remember decision" checkbox is hidden when showing for documents that
337 // the permission manager can't handle (documents with URIs without a host).
338 // We simply require those to be approved every time instead.
339 let rememberCheckbox = document.getElementById("full-screen-remember-decision");
340 let uri = this.fullscreenDoc.nodePrincipal.URI;
341 if (!rememberCheckbox.hidden) {
342 if (rememberCheckbox.checked)
343 Services.perms.add(uri,
344 "fullscreen",
345 isApproved ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION,
346 Services.perms.EXPIRE_NEVER);
347 else if (isApproved) {
348 // The user has only temporarily approved fullscren for this fullscreen
349 // session only. Add the permission (so Gecko knows to approve any further
350 // fullscreen requests for this host in this fullscreen session) but add
351 // a listener to revoke the permission when the chrome document exits
352 // fullscreen.
353 Services.perms.add(uri,
354 "fullscreen",
355 Services.perms.ALLOW_ACTION,
356 Services.perms.EXPIRE_SESSION);
357 let host = uri.host;
358 var onFullscreenchange = function onFullscreenchange(event) {
359 if (event.target == document && document.mozFullScreenElement == null) {
360 // The chrome document has left fullscreen. Remove the temporary permission grant.
361 Services.perms.remove(host, "fullscreen");
362 document.removeEventListener("mozfullscreenchange", onFullscreenchange);
363 }
364 }
365 document.addEventListener("mozfullscreenchange", onFullscreenchange);
366 }
367 }
368 if (this.warningBox)
369 this.warningBox.setAttribute("fade-warning-out", "true");
370 // If the document has been granted fullscreen, notify Gecko so it can resume
371 // any pending pointer lock requests, otherwise exit fullscreen; the user denied
372 // the fullscreen request.
373 if (isApproved)
374 Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
375 else
376 document.mozCancelFullScreen();
377 },
378
379 warningBox: null,
380 warningFadeOutTimeout: null,
381 fullscreenDoc: null,
382
383 // Shows the fullscreen approval UI, or if the domain has already been approved
384 // for fullscreen, shows a warning that the site has entered fullscreen for a short
385 // duration.
386 showWarning: function(targetDoc) {
387 if (!document.mozFullScreen ||
388 !gPrefService.getBoolPref("full-screen-api.approval-required"))
389 return;
390
391 // Set the strings on the fullscreen approval UI.
392 this.fullscreenDoc = targetDoc;
393 let uri = this.fullscreenDoc.nodePrincipal.URI;
394 let host = null;
395 try {
396 host = uri.host;
397 } catch (e) { }
398 let hostLabel = document.getElementById("full-screen-domain-text");
399 let rememberCheckbox = document.getElementById("full-screen-remember-decision");
400 let isApproved = false;
401 if (host) {
402 // Document's principal's URI has a host. Display a warning including the hostname and
403 // show UI to enable the user to permanently grant this host permission to enter fullscreen.
404 let utils = {};
405 Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
406 let displayHost = utils.DownloadUtils.getURIHost(uri.spec)[0];
407 let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
408
409 hostLabel.textContent = bundle.formatStringFromName("fullscreen.entered", [displayHost], 1);
410 hostLabel.removeAttribute("hidden");
411
412 rememberCheckbox.label = bundle.formatStringFromName("fullscreen.rememberDecision", [displayHost], 1);
413 rememberCheckbox.checked = false;
414 rememberCheckbox.removeAttribute("hidden");
415
416 // Note we only allow documents whose principal's URI has a host to
417 // store permission grants.
418 isApproved = Services.perms.testPermission(uri, "fullscreen") == Services.perms.ALLOW_ACTION;
419 } else {
420 hostLabel.setAttribute("hidden", "true");
421 rememberCheckbox.setAttribute("hidden", "true");
422 }
423
424 // Note: the warning box can be non-null if the warning box from the previous request
425 // wasn't hidden before another request was made.
426 if (!this.warningBox) {
427 this.warningBox = document.getElementById("full-screen-warning-container");
428 // Add a listener to clean up state after the warning is hidden.
429 this.warningBox.addEventListener("transitionend", this);
430 this.warningBox.removeAttribute("hidden");
431 } else {
432 if (this.warningFadeOutTimeout) {
433 clearTimeout(this.warningFadeOutTimeout);
434 this.warningFadeOutTimeout = null;
435 }
436 this.warningBox.removeAttribute("fade-warning-out");
437 }
438
439 // If fullscreen mode has not yet been approved for the fullscreen
440 // document's domain, show the approval UI and don't auto fade out the
441 // fullscreen warning box. Otherwise, we're just notifying of entry into
442 // fullscreen mode. Note if the resource's host is null, we must be
443 // showing a local file or a local data URI, and we require explicit
444 // approval every time.
445 let authUI = document.getElementById("full-screen-approval-pane");
446 if (isApproved) {
447 authUI.setAttribute("hidden", "true");
448 this.warningBox.removeAttribute("obscure-browser");
449 } else {
450 // Partially obscure the <browser> element underneath the approval UI.
451 this.warningBox.setAttribute("obscure-browser", "true");
452 authUI.removeAttribute("hidden");
453 }
454
455 // If we're not showing the fullscreen approval UI, we're just notifying the user
456 // of the transition, so set a timeout to fade the warning out after a few moments.
457 if (isApproved)
458 this.warningFadeOutTimeout =
459 setTimeout(
460 function() {
461 if (this.warningBox)
462 this.warningBox.setAttribute("fade-warning-out", "true");
463 }.bind(this),
464 3000);
465 },
466
467 mouseoverToggle: function(aShow, forceHide)
468 {
469 // Don't do anything if:
470 // a) we're already in the state we want,
471 // b) we're animating and will become collapsed soon, or
472 // c) we can't collapse because it would be undesirable right now
473 if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
474 (!aShow && !this._safeToCollapse(forceHide)))
475 return;
476
477 // browser.fullscreen.animateUp
478 // 0 - never animate up
479 // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
480 // 2 - animate every time it collapses
481 if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0)
482 this._shouldAnimate = false;
483
484 if (!aShow && this._shouldAnimate) {
485 this._isAnimating = true;
486 this._shouldAnimate = false;
487 this._animationTimeout = setTimeout(this._animateUp.bind(this), 800);
488 return;
489 }
490
491 // The chrome is collapsed so don't spam needless mousemove events
492 if (aShow) {
493 gBrowser.mPanelContainer.addEventListener("mousemove",
494 this._collapseCallback, false);
495 }
496 else {
497 gBrowser.mPanelContainer.removeEventListener("mousemove",
498 this._collapseCallback, false);
499 }
500
501 // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
502 // so we just move it off-screen instead. See bug 430687.
503 gNavToolbox.style.marginTop =
504 aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
505
506 this._fullScrToggler.collapsed = aShow;
507 this._isChromeCollapsed = !aShow;
508 if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
509 this._shouldAnimate = true;
510 },
511
512 showXULChrome: function(aTag, aShow)
513 {
514 var els = document.getElementsByTagNameNS(this._XULNS, aTag);
515
516 for (let el of els) {
517 // XXX don't interfere with previously collapsed toolbars
518 if (el.getAttribute("fullscreentoolbar") == "true") {
519 if (!aShow) {
520 // Give the main nav bar and the tab bar the fullscreen context menu,
521 // otherwise remove context menu to prevent breakage
522 el.setAttribute("saved-context", el.getAttribute("context"));
523 if (el.id == "nav-bar" || el.id == "TabsToolbar")
524 el.setAttribute("context", "autohide-context");
525 else
526 el.removeAttribute("context");
527
528 // Set the inFullscreen attribute to allow specific styling
529 // in fullscreen mode
530 el.setAttribute("inFullscreen", true);
531 }
532 else {
533 if (el.hasAttribute("saved-context")) {
534 el.setAttribute("context", el.getAttribute("saved-context"));
535 el.removeAttribute("saved-context");
536 }
537 el.removeAttribute("inFullscreen");
538 }
539 } else {
540 // use moz-collapsed so it doesn't persist hidden/collapsed,
541 // so that new windows don't have missing toolbars
542 if (aShow)
543 el.removeAttribute("moz-collapsed");
544 else
545 el.setAttribute("moz-collapsed", "true");
546 }
547 }
548
549 if (aShow) {
550 gNavToolbox.removeAttribute("inFullscreen");
551 document.documentElement.removeAttribute("inFullscreen");
552 } else {
553 gNavToolbox.setAttribute("inFullscreen", true);
554 document.documentElement.setAttribute("inFullscreen", true);
555 }
556
557 var fullscreenctls = document.getElementById("window-controls");
558 var navbar = document.getElementById("nav-bar");
559 var ctlsOnTabbar = window.toolbar.visible;
560 if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
561 fullscreenctls.removeAttribute("flex");
562 document.getElementById("TabsToolbar").appendChild(fullscreenctls);
563 }
564 else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
565 fullscreenctls.setAttribute("flex", "1");
566 navbar.appendChild(fullscreenctls);
567 }
568 fullscreenctls.hidden = aShow;
569
570 ToolbarIconColor.inferFromText();
571 }
572 };
573 XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
574 // We'll only use OS X Lion full screen if we're
575 // * on OS X
576 // * on Lion or higher (Darwin 11+)
577 // * have fullscreenbutton="true"
578 #ifdef XP_MACOSX
579 return parseFloat(Services.sysinfo.getProperty("version")) >= 11 &&
580 document.documentElement.getAttribute("fullscreenbutton") == "true";
581 #else
582 return false;
583 #endif
584 });

mercurial