/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* global Service, TrackingNotice, LazyLoader */ 'use strict'; (function(exports) { const TRACKING_NOTICE_KEY = 'privacy.trackingprotection.shown'; var DEBUG = false; /** * A manager for each SystemDialog `show` and `hide`. * Show a dialog - the dialog publish `show` event from SystemDialog. * Hide a dialog - the dialog publish `hide` event from SystemDialog. * - a new active dialog publish 'system-dialog-show' event, * then hide the previous active dialog. * - received 'home', 'holdhome' event from the home button. * And pass the event type to be the argument * for options `onHide` attribute. * * @class SystemDialogManager * @requires module:Service */ var SystemDialogManager = function SystemDialogManager() {}; SystemDialogManager.prototype = { /** * System app is made of a top-level `
` DOM element * which contain all possible screens displayed by the app. * Multiple screens can be displayed at a time. * We store the list of currently * visible screens into this DOM element class attribute. * * @memberof SystemDialogManager * @prop {DOMElement} windows - the `#windows` element, which is the same * element that the would AppWindowManager use. * @prop {DOMElement} screen - the `#screen` element. */ elements: { windows: null, screen: null, containerElement: document.getElementById('dialog-overlay') }, /** * @memberof SystemDialogManager# * */ states: { activeDialog: null, runningDialogs: {} }, /** * @memberof SystemDialogManager# */ configs: { listens: ['system-dialog-started', 'system-dialog-stopped', 'simlockstarted', 'simlockstopped', 'actionmenucreated', 'actionmenudestroyed', 'system-dialog-show', 'system-dialog-hide', 'simlockshow', 'actionmenushow', 'simlockhide', 'actionmenuhide', 'system-dialog-requestfocus', 'simlockrequestfocus', 'home', 'holdhome', 'hierarchytopmostwindowchanged'] } }; SystemDialogManager.prototype.isActive = function() { return !!this.states.activeDialog; }; SystemDialogManager.prototype.setHierarchy = function(active) { if (this.states.activeDialog) { this.states.activeDialog._setVisibleForScreenReader(active); } }; SystemDialogManager.prototype.setFocus = function(active) { if (!this.states.activeDialog) { return false; } if (active) { this.states.activeDialog.focus(); } return true; }; SystemDialogManager.prototype.name = 'SystemDialogManager'; SystemDialogManager.prototype.EVENT_PREFIX = 'systemdialogmanager'; SystemDialogManager.prototype.publish = function(evtName) { this.debug('publishing ' + evtName); window.dispatchEvent(new CustomEvent(this.EVENT_PREFIX + evtName, { detail: this })); }; SystemDialogManager.prototype['_handle_system-resize'] = function() { if (this.states.activeDialog) { this.states.activeDialog.resize(); return false; } return true; }; SystemDialogManager.prototype._handle_inputfocus = function(evt) { if (!this.states.activeDialog) { return true; } this.states.activeDialog.broadcast('inputfocus', evt.detail); return false; }; SystemDialogManager.prototype._handle_inputblur = function() { if (!this.states.activeDialog) { return true; } this.states.activeDialog.broadcast('inputblur'); return false; }; SystemDialogManager.prototype._handle_home = function(evt) { // Automatically hide the dialog on home button press if (this.states.activeDialog) { // Deactivate the dialog and pass the event type in the two cases this.deactivateDialog(this.states.activeDialog, evt.type); } return true; }; SystemDialogManager.prototype._handle_holdhome = function(evt) { // Automatically hide the dialog on home button press if (this.states.activeDialog) { // Deactivate the dialog and pass the event type in the two cases this.deactivateDialog(this.states.activeDialog, evt.type); } return true; }; SystemDialogManager.prototype.respondToHierarchyEvent = function(evt) { if (this['_handle_' + evt.type]) { return this['_handle_' + evt.type](evt); } return true; }; /** * @listens system-dialog-started - when a system dialog got started, * it would fire this event. * @listens system-dialog-show - when a system dialog got show request, * it would fire this event. * @listens system-dialog-hide - when a system dialog got hide request, * it would fire this event. * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.handleEvent = function sdm_handleEvent(evt) { var dialog = null; switch (evt.type) { // We only care about appWindow's fullscreen state because // we are on top of the appWindow. case 'hierarchytopmostwindowchanged': var appWindow = evt.detail.getTopMostWindow(); var isFullScreen = appWindow && appWindow.isFullScreen(); var container = this.elements.containerElement; container.classList.toggle('fullscreen', isFullScreen); if (this.states.activeDialog) { this.states.activeDialog.resize(); } break; case 'system-dialog-requestfocus': case 'simlockrequestfocus': if (evt.detail !== this.states.activeDialog) { return; } Service.request('focus', this); break; case 'simlockstarted': case 'actionmenucreated': case 'system-dialog-started': dialog = evt.detail; this.registerDialog(dialog); break; case 'simlockshow': case 'system-dialog-show': case 'actionmenushow': dialog = evt.detail; this.activateDialog(dialog); break; case 'simlockhide': case 'actionmenuhide': case 'system-dialog-hide': dialog = evt.detail; this.deactivateDialog(dialog); break; case 'simlockstopped': case 'actionmenudestroyed': case 'system-dialog-stopped': dialog = evt.detail; this.deactivateDialog(dialog); this.unregisterDialog(dialog); break; } }; /** * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.initElements = function sdm_initElements() { var selectors = { windows: 'windows', screen: 'screen', containerElement: 'dialog-overlay'}; for (var name in selectors) { var id = selectors[name]; this.elements[name] = document.getElementById(id); } }; /** * Hook listeners of events this manager interested in. * * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.start = function sdm_start() { this.initElements(); this.configs.listens.forEach((function _initEvent(type) { self.addEventListener(type, this); }).bind(this)); Service.request('registerHierarchy', this); this._initTrackingNotice(); }; /** * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.registerDialog = function sdm_registerDialog(dialog) { this.states.runningDialogs[dialog.instanceID] = dialog; }; /** * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.unregisterDialog = function sdm_unregisterDialog(dialog) { delete this.states.runningDialogs[dialog.instanceID]; }; /** * Set an dialog as the active dialog. * * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.activateDialog = function sdm_activateDialog(dialog) { this.debug('activateDialog: dialog.instanceID = ' + dialog.instanceID); // Hide the previous active dialog. if (this.states.activeDialog && dialog.instanceID != this.states.activeDialog.instanceID) { // Means other dialog interrupted. // Then, have to hide the previous active dialog. this.states.activeDialog.hide('interrupted', true); } // Record new active dialog. this.states.activeDialog = dialog; // Activate dialog on screen element. if (!this.elements.screen.classList.contains('dialog')) { this.elements.screen.classList.add('dialog'); this.publish('-activated'); } }; /** * Deactivate the current active dialog. * * @private * @this {SystemDialogManager} * @memberof SystemDialogManager */ SystemDialogManager.prototype.deactivateDialog = function sdm_deactivateDialog(dialog, reason) { this.debug('deactivateDialog: dialog.instanceID = ' + dialog.instanceID); if (this.states.activeDialog && dialog.instanceID == this.states.activeDialog.instanceID) { // Hide itself if (reason) { // The request is coming from SystemDialogManager. this.states.activeDialog.hide(reason, true); } else { // The request is coming from dialog controller. // Do nothing since the dialog is hidden already. } // Deactivate dialog on screen element this.elements.screen.classList.remove('dialog'); // Clear activeDialog this.states.activeDialog = null; this.publish('-deactivated'); } else { // The dialog is not active. // Just hide itself, no need to disturb other active dialog. // Since the dialog is hidden already, do nothing here. } }; SystemDialogManager.prototype.debug = function sd_debug() { if (DEBUG) { console.log('[SystemDialogManager]' + '[' + Service.currentTime() + ']' + '[' + Array.slice(arguments).concat() + ']'); } }; SystemDialogManager.prototype._initTrackingNotice = function() { var req = navigator.mozSettings.createLock().get(TRACKING_NOTICE_KEY); req.onsuccess = () => { var alreadyShown = req.result[TRACKING_NOTICE_KEY]; if (!alreadyShown) { this._includeTrackingNotice(); } }; req.onerror = () => { this._includeTrackingNotice(); }; }; SystemDialogManager.prototype._includeTrackingNotice = function() { LazyLoader.load('/js/tracking_notice.js').then(() => { this.trackingNotice = new TrackingNotice(SystemDialogManager); }); }; exports.SystemDialogManager = SystemDialogManager; }(window));