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