diff options
Diffstat (limited to 'apps/system/js/attention_screen.js')
-rw-r--r-- | apps/system/js/attention_screen.js | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/apps/system/js/attention_screen.js b/apps/system/js/attention_screen.js new file mode 100644 index 0000000..c14dbe8 --- /dev/null +++ b/apps/system/js/attention_screen.js @@ -0,0 +1,289 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +'use strict'; + +var AttentionScreen = { + get mainScreen() { + delete this.mainScreen; + return this.mainScreen = document.getElementById('screen'); + }, + + get attentionScreen() { + delete this.attentionScreen; + return this.attentionScreen = document.getElementById('attention-screen'); + }, + + get bar() { + delete this.bar; + return this.bar = document.getElementById('attention-bar'); + }, + + isVisible: function as_isVisible() { + return this.attentionScreen.classList.contains('displayed'); + }, + + isFullyVisible: function as_isFullyVisible() { + return (this.isVisible() && + !this.mainScreen.classList.contains('active-statusbar')); + }, + + init: function as_init() { + window.addEventListener('mozbrowseropenwindow', this.open.bind(this), true); + window.addEventListener('mozbrowserclose', this.close.bind(this), true); + window.addEventListener('mozbrowsererror', this.close.bind(this), true); + window.addEventListener('keyboardchange', this.resize.bind(this), true); + window.addEventListener('keyboardhide', this.resize.bind(this), true); + + this.bar.addEventListener('click', this.show.bind(this)); + window.addEventListener('home', this.hide.bind(this)); + window.addEventListener('holdhome', this.hide.bind(this)); + window.addEventListener('appwillopen', this.hide.bind(this)); + }, + + resize: function as_resize(evt) { + if (evt.type == 'keyboardchange') { + if (!this.isFullyVisible()) + return; + + this.attentionScreen.style.height = + window.innerHeight - evt.detail.height + 'px'; + } else if (evt.type == 'keyboardhide') { + // We still need to reset the height property even when the attention + // screen is not fully visible, or it will overrides the height + // we defined with #attention-screen.status-mode + this.attentionScreen.style.height = ''; + } + }, + + // show the attention screen overlay with newly created frame + open: function as_open(evt) { + if (evt.detail.features != 'attention') + return; + + // stopPropagation means we are not allowing + // Popup Manager to handle this event + evt.stopPropagation(); + + // Canceling any full screen web content + if (document.mozFullScreen) + document.mozCancelFullScreen(); + + // Check if the app has the permission to open attention screens + var manifestURL = evt.target.getAttribute('mozapp'); + var app = Applications.getByManifestURL(manifestURL); + + if (!app || !this._hasAttentionPermission(app)) + return; + + // Hide sleep menu/list menu if it is shown now + ListMenu.hide(); + SleepMenu.hide(); + + // We want the user attention, so we need to turn the screen on + // if it's off. The lockscreen will grab the focus when we do that + // so we need to do it before adding the new iframe to the dom + if (!ScreenManager.screenEnabled) + ScreenManager.turnScreenOn(); + + var attentionFrame = evt.detail.frameElement; + attentionFrame.dataset.frameType = 'attention'; + attentionFrame.dataset.frameName = evt.detail.name; + attentionFrame.dataset.frameOrigin = evt.target.dataset.frameOrigin; + + // We would like to put the dialer call screen on top of all other + // attention screens by ensure it is the last iframe in the DOM tree + if (this._hasTelephonyPermission(app)) { + this.attentionScreen.appendChild(attentionFrame); + } else { + this.attentionScreen.insertBefore(attentionFrame, + this.bar.nextElementSibling); + } + + this._updateAttentionFrameVisibility(); + + // Make the overlay visible if we are not displayed yet. + // alternatively, if the newly appended frame is the visible frame + // and we are in the status bar mode, expend to full screen mode. + if (!this.isVisible()) { + this.attentionScreen.classList.add('displayed'); + this.mainScreen.classList.add('attention'); + this.dispatchEvent('attentionscreenshow', { + origin: attentionFrame.dataset.frameOrigin + }); + } else if (!this.isFullyVisible() && + this.attentionScreen.lastElementChild === attentionFrame) { + this.show(); + } + }, + + // Make sure visibililty state of all attention screens are set correctly + _updateAttentionFrameVisibility: function as_updateAtteFrameVisibility() { + var frames = this.attentionScreen.querySelectorAll('iframe'); + var i = frames.length - 1; + + // In case someone call this function w/o checking for frame first + if (i < 0) + return; + + // set the last one in the DOM to visible + // The setTimeout() and the closure is used to workaround + // https://bugzilla.mozilla.org/show_bug.cgi?id=810431 + setTimeout(function(frame) { + frame.setVisible(true); + frame.focus(); + }, 0, frames[i]); + + while (i--) { + // The setTimeout() and the closure is used to workaround + // https://bugzilla.mozilla.org/show_bug.cgi?id=810431 + setTimeout(function(frame) { + frame.setVisible(false); + frame.blur(); + }, 0, frames[i]); + } + }, + + // close the attention screen overlay + close: function as_close(evt) { + if (!'frameType' in evt.target.dataset || + evt.target.dataset.frameType !== 'attention' || + (evt.type === 'mozbrowsererror' && evt.detail.type !== 'fatal')) + return; + + // Remove the frame + var origin = evt.target.dataset.frameOrigin; + this.attentionScreen.removeChild(evt.target); + + // We've just removed the focused window leaving the system + // without any focused window, let's fix this. + window.focus(); + + // if there are other attention frames, + // we need to update the visibility and show() the overlay. + if (this.attentionScreen.querySelectorAll('iframe').length) { + this._updateAttentionFrameVisibility(); + + this.dispatchEvent('attentionscreenclose', { origin: origin }); + + if (!this.isFullyVisible()) + this.show(); + + return; + } + + // There is no iframes left; + // we should close the attention screen overlay. + + // If the the attention screen is closed during active-statusbar + // mode, we would need to leave that mode. + if (!this.isFullyVisible()) { + this.mainScreen.classList.remove('active-statusbar'); + this.attentionScreen.classList.remove('status-mode'); + this.dispatchEvent('status-inactive', + { origin: this.attentionScreen.lastElementChild.dataset.frameOrigin }); + } + + this.attentionScreen.classList.remove('displayed'); + this.mainScreen.classList.remove('attention'); + this.dispatchEvent('attentionscreenhide', { origin: origin }); + }, + + // expend the attention screen overlay to full screen + show: function as_show() { + // leaving "status-mode". + this.attentionScreen.classList.remove('status-mode'); + // there shouldn't be a transition from "status-mode" to "active-statusbar" + this.attentionScreen.style.transition = 'none'; + + var self = this; + setTimeout(function nextTick() { + self.attentionScreen.style.transition = ''; + + // leaving "active-statusbar" mode, + // with a transform: translateY() slide down transition. + self.mainScreen.classList.remove('active-statusbar'); + self.dispatchEvent('status-inactive', { + origin: self.attentionScreen.lastElementChild.dataset.frameOrigin + }); + }); + }, + + // shrink the attention screen overlay to status bar + // invoked when we get a "home" event + hide: function as_hide() { + if (!this.isFullyVisible()) + return; + + // entering "active-statusbar" mode, + // with a transform: translateY() slide up transition. + this.mainScreen.classList.add('active-statusbar'); + + // The only way to hide attention screen is the home/holdhome event. + // So we don't fire any origin information here. + // The expected behavior is restore homescreen visibility to 'true' + // in the Window Manager. + this.dispatchEvent('status-active'); + + var attentionScreen = this.attentionScreen; + attentionScreen.addEventListener('transitionend', function trWait() { + attentionScreen.removeEventListener('transitionend', trWait); + + // transition completed, entering "status-mode" (40px height iframe) + attentionScreen.classList.add('status-mode'); + }); + }, + + dispatchEvent: function as_dispatchEvent(name, detail) { + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(name, true, true, detail); + window.dispatchEvent(evt); + }, + + // If an app with an active attention screen is switched to, + // we would need to cover it with it's attention screen. + // Invoked when displayedApp in Window Manager changes + // XXX should be replaced with a call that listens to appwillopen + // TBD: display the attention screen underneath other attention screens. + showForOrigin: function as_showForOrigin(origin) { + if (!this.isVisible() || this.isFullyVisible()) + return; + + var attentionFrame = this.attentionScreen.lastElementChild; + var frameOrigin = attentionFrame.dataset.frameOrigin; + if (origin === frameOrigin) { + this.show(); + } + }, + + getAttentionScreenOrigins: function as_getAttentionScreenOrigins() { + var attentionScreen = this.attentionScreen; + var frames = this.attentionScreen.querySelectorAll('iframe'); + var attentiveApps = []; + Array.prototype.forEach.call(frames, function pushFrame(frame) { + attentiveApps.push(frame.dataset.frameOrigin); + }); + return attentiveApps; + }, + + _hasAttentionPermission: function as_hasAttentionPermission(app) { + var mozPerms = navigator.mozPermissionSettings; + if (!mozPerms) + return false; + + var value = mozPerms.get('attention', app.manifestURL, app.origin, false); + + return (value === 'allow'); + }, + + _hasTelephonyPermission: function as_hasAttentionPermission(app) { + var mozPerms = navigator.mozPermissionSettings; + if (!mozPerms) + return false; + + var value = mozPerms.get('telephony', app.manifestURL, app.origin, false); + + return (value === 'allow'); + } +}; + +AttentionScreen.init(); |