diff --git a/apps/system/js/hardware_buttons.js b/apps/system/js/hardware_buttons.js index 626c9558bf9b..bd87191da8a6 100644 --- a/apps/system/js/hardware_buttons.js +++ b/apps/system/js/hardware_buttons.js @@ -1,82 +1,188 @@ -// hardware_buttons.js: -// -// Gecko code in b2g/chrome/content/shell.js sends mozChromeEvents -// when the user presses or releases a hardware button such as Home, Sleep, -// and Volume Up and Down. -// -// This module listens for those low-level mozChromeEvents, processes them -// and generates higher-level events to handle autorepeat on the volume keys -// long presses on Home and Sleep, and the Home+Sleep key combination. -// -// Other system app modules should listen for the high-level button events -// generated by this module. -// -// The low-level input events processed by this module have type set -// to "mozChromeEvent" and detail.type set to one of: -// -// home-button-press -// home-button-release -// sleep-button-press -// sleep-button-release -// volume-up-button-press -// volume-up-button-release -// volume-down-button-press -// volume-down-button-release -// -// The high-level events generated by this module are simple Event objects -// that are not cancelable and do not bubble. The are dispatched at the -// window object. The type property is set to one of these: -// -// Event Type Meaning -// -------------------------------------------------------------- -// home short press and release of home button -// holdhome long press and hold of home button -// sleep short press and release of sleep button -// wake sleep or home pressed while sleeping -// holdsleep long press and hold of sleep button -// volumeup volume up pressed and released or autorepeated -// volumedown volume down pressed and released or autorepeated -// home+sleep home and sleep pressed at same time (used for screenshots) -// -// Because these events are fired at the window object, they cannot be -// captured. Many modules listen for the home event. Those that want -// to respond to it and prevent others from responding should call -// stopImmediatePropagation(). Overlays that want to prevent the window -// manager from showing the homescreen on the home event should call that -// method. Note, however, that this only works for scripts that run and -// register their event handlers before window_manager.js does. -// 'use strict'; -(function() { - var HOLD_INTERVAL = 750; // How long for press and hold Home or Sleep - var REPEAT_DELAY = 700; // How long before volume autorepeat begins - var REPEAT_INTERVAL = 100; // How fast the autorepeat is. +/* global ScreenManager */ - // Dispatch a high-level event of the specified type - function fire(type) { - window.dispatchEvent(new Event(type)); - } +(function(exports) { - // We process events with a finite state machine. - // Each state object has a process() method for handling events. - // And optionally has enter() and exit() methods called when the FSM - // enters and exits that state - var state; + // Tell JSHint the following script are allow to use these variables. + // The actual functions will be defined too. + var HardwareButtonsBaseState; + var HardwareButtonsHomeState; + var HardwareButtonsSleepState; + var HardwareButtonsVolumeState; + var HardwareButtonsWakeState; + + /** + * Gecko code in `b2g/chrome/content/shell.js` sends `mozChromeEvents` + * when the user presses or releases a hardware button such as Home, Sleep, + * and Volume Up and Down. + * + * This module listens for those low-level `mozChromeEvents`, processes them + * and generates higher-level events to handle autorepeat on the volume keys + * long presses on Home and Sleep, and the Home+Sleep key combination. + * + * Other system app modules should listen for the high-level button events + * generated by this module. + * + * The low-level input events processed by this module have type set + * to `mozChromeEvent` and `detail.type` set to one of: + * + * * home-button-press + * * home-button-release + * * sleep-button-press + * * sleep-button-release + * * volume-up-button-press + * * volume-up-button-release + * * volume-down-button-press + * * volume-down-button-release + * + * The high-level events generated by this module are simple Event objects + * that are not cancelable and do not bubble. The are dispatched at the + * window object. The type property is set to one of these: + * + * | Event Type | Meaning | + * |-------------|-----------------------------------------------------------| + * | home | short press and release of home button | + * | holdhome | long press and hold of home button | + * | sleep | short press and release of sleep button | + * | wake | sleep or home pressed while sleeping | + * | holdsleep | long press and hold of sleep button | + * | volumeup | volume up pressed and released or autorepeated | + * | volumedown | volume down pressed and released or autorepeated | + * | home+sleep | home and sleep pressed at same time (used for screenshots)| + * + * Because these events are fired at the window object, they cannot be + * captured. Many modules listen for the home event. Those that want + * to respond to it and prevent others from responding should call + * `stopImmediatePropagation()`. Overlays that want to prevent the window + * manager from showing the homescreen on the home event should call that + * method. Note, however, that this only works for scripts that run and + * register their event handlers before `AppWindowManager` does. + * + * As of the implementation itself, we process events with a + * finite state machine. + * Each state object has a `process()` method for handling events. + * And optionally has `enter()` and `exit()` methods called when the FSM + * enters and exits that state. + * + * @example + * var hardwareButtons = new HardwareButtons(); + * hardwareButtons.start(); // Attach the event listeners. + * hardwareButtons.stop(); // Deattach the event listeners. + * + * @class HardwareButtons + * @requires ScreenManager + **/ + var HardwareButtons = function HardwareButtons() { + this._started = false; + }; + + /** + * A mapping between state keyboard and the constructor. + * @type {Object} + */ + HardwareButtons.STATES = {}; + + /** + * How long for press and hold Home or Sleep. + * @memberof HardwareButtons.prototype + * @type {Number} + */ + HardwareButtons.prototype.HOLD_INTERVAL = 750; + + /** + * How long before volume autorepeat begins. + * @memberof HardwareButtons.prototype + * @type {Number} + */ + HardwareButtons.prototype.REPEAT_DELAY = 700; + + /** + * How fast the autorepeat is. + * @memberof HardwareButtons.prototype + * @type {Number} + */ + HardwareButtons.prototype.REPEAT_INTERVAL = 100; + + /** + * Start listening to events from Gecko and FSM. + * @memberof HardwareButtons.prototype + */ + HardwareButtons.prototype.start = function hb_start() { + if (this._started) { + throw 'Instance should not be start()\'ed twice.'; + } + this._started = true; + + // Kick off the FSM in the base state + this.state = new HardwareButtonsBaseState(this); + + // This event handler listens for hardware button events and passes the + // event type to the process() method of the current state for processing + window.addEventListener('mozChromeEvent', this); + + window.addEventListener('softwareButtonEvent', this); + }; + + /** + * Stop listening to events. Must call before throwing away the instance + * to avoid memory leaks. + * @memberof HardwareButtons.prototype + */ + HardwareButtons.prototype.stop = function hb_stop() { + if (!this._started) { + throw 'Instance was never start()\'ed but stop() is called.'; + } + this._started = false; - // This function transitions to a new state - function setState(s, type) { // Exit the current state() - if (state && state.exit) - state.exit(type); - state = s; - // Enter the new state - if (state && state.enter) - state.enter(type); - } - - function buttonEventHandler(e) { - var type = e.detail.type; + if (this.state && this.state.exit) { + this.state.exit(); + } + + this.state = null; + + window.removeEventListener('mozChromeEvent', this); + + window.removeEventListener('softwareButtonEvent', this); + }; + + /** + * Dispatch a high-level event of the specified type. + * @memberof HardwareButtons.prototype + * @param {String} type name of the event. + */ + HardwareButtons.prototype.publish = function hb_publish(type) { + window.dispatchEvent(new CustomEvent(type)); + }; + + /** + * Transit the FSM to a new state. + * @memberof HardwareButtons.prototype + * @param {String} s Keyword of the new state to switch to. + * @param {String} type Name of the event to handle + */ + HardwareButtons.prototype.setState = function hb_setState(s, type) { + // Exit the current state() + if (this.state && this.state.exit) { + this.state.exit(type); + } + + this.state = new HardwareButtons.STATES[s](this); + + // Enter the new this.state + if (this.state && this.state.enter) { + this.state.enter(type); + } + }; + + /** + * Handle events from Gecko. + * @memberof HardwareButtons.prototype + * @param {Object} evt Event. + */ + HardwareButtons.prototype.handleEvent = function hb_handleEvent(evt) { + var type = evt.detail.type; switch (type) { case 'home-button-press': case 'home-button-release': @@ -86,44 +192,59 @@ case 'volume-up-button-release': case 'volume-down-button-press': case 'volume-down-button-release': - state.process(type); + this.state.process(type); break; } - } - - // This event handler listens for hardware button events and passes the - // event type to the process() method of the current state for processing - window.addEventListener('mozChromeEvent', buttonEventHandler); + }; - window.addEventListener('softwareButtonEvent', buttonEventHandler); + /** + * The base state is the default, when no hardware buttons are pressed. + * + * @class HardwareButtonsBaseState + */ + HardwareButtonsBaseState = + HardwareButtons.STATES.base = function HardwareButtonsBaseState(hb) { + this.hardwareButtons = hb; + }; - // The base state is the default, when no hardware buttons are pressed - var baseState = { - process: function(type) { - switch (type) { + /** + * Process the event, maybe transition the state. + * @memberof HardwareButtonsBaseState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsBaseState.prototype.process = function(type) { + switch (type) { case 'home-button-press': - // If the phone is sleeping, then pressing Home wakes it - // (on press, not release) + /** + * If the phone is sleeping, then pressing Home wakes it + * (on press, not release) + * @event HardwareButtonsBaseState#wake + */ + // XXX: Unresolved dependency to screenManager if (!ScreenManager.screenEnabled) { - fire('wake'); - setState(wakeState, type); + this.hardwareButtons.publish('wake'); + this.hardwareButtons.setState('wake', type); } else { - setState(homeState, type); + this.hardwareButtons.setState('home', type); } return; case 'sleep-button-press': - // If the phone is sleeping, then pressing Sleep wakes it - // (on press, not release) + /** + * If the phone is sleeping, then pressing Home wakes it + * (on press, not release) + * @event HardwareButtonsBaseState#wake + */ + // XXX: Unresolved dependency to screenManager if (!ScreenManager.screenEnabled) { - fire('wake'); - setState(wakeState, type); + this.hardwareButtons.publish('wake'); + this.hardwareButtons.setState('wake', type); } else { - setState(sleepState, type); + this.hardwareButtons.setState('sleep', type); } return; case 'volume-up-button-press': case 'volume-down-button-press': - setState(volumeState, type); + this.hardwareButtons.setState('volume', type); return; case 'home-button-release': case 'sleep-button-release': @@ -132,188 +253,324 @@ // Ignore button releases that occur in this state. // These can happen after home+sleep and home+volume. return; - } - console.error('Unexpected hardware key: ', type); } + console.error('Unexpected hardware key: ', type); }; - // We enter the home state when the user presses the Home button - // We can fire home, holdhome, or homesleep events from this state - var homeState = { - timer: null, - enter: function() { - this.timer = setTimeout(function() { - fire('holdhome'); - navigator.vibrate(50); - setState(baseState); - }, HOLD_INTERVAL); - }, - exit: function() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - }, - process: function(type) { - switch (type) { + /** + * We enter the home state when the user presses the Home button + * We can fire home, holdhome, or homesleep events from this state + * + * @class HardwareButtonsHomeState + */ + HardwareButtonsHomeState = + HardwareButtons.STATES.home = function HardwareButtonsHomeState(hb) { + this.hardwareButtons = hb; + this.timer = undefined; + }; + + /** + * Entering the state. + * @memberof HardwareButtonsHomeState.prototype + */ + HardwareButtonsHomeState.prototype.enter = function() { + this.timer = setTimeout(function() { + /** + * When the user holds Home button more than HOLD_INTERVAL. + * @event HardwareButtonsHomeState#holdhome + */ + this.hardwareButtons.publish('holdhome'); + navigator.vibrate(50); + this.hardwareButtons.setState('base'); + }.bind(this), this.hardwareButtons.HOLD_INTERVAL); + }; + + /** + * Leaving the state. + * @memberof HardwareButtonsHomeState.prototype + */ + HardwareButtonsHomeState.prototype.exit = function() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + }; + + /** + * Process the event, maybe transition the state. + * @memberof HardwareButtonsHomeState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsHomeState.prototype.process = function(type) { + switch (type) { case 'home-button-release': - fire('home'); + /** + * When the user releases Home button before HOLD_INTERVAL. + * @event HardwareButtonsHomeState#home + */ + this.hardwareButtons.publish('home'); navigator.vibrate(50); - setState(baseState, type); + this.hardwareButtons.setState('base', type); return; case 'sleep-button-press': - fire('home+sleep'); - setState(baseState, type); + /** + * When the user presses Sleep button, before HOLD_INTERVAL, + * while holding the Home button. + * @event HardwareButtonsHomeState#home+sleep + */ + this.hardwareButtons.publish('home+sleep'); + this.hardwareButtons.setState('base', type); return; case 'volume-up-button-press': case 'volume-down-button-press': - setState(baseState, type); + this.hardwareButtons.setState('base', type); return; - } - console.error('Unexpected hardware key: ', type); - setState(baseState, type); } + console.error('Unexpected hardware key: ', type); + this.hardwareButtons.setState('base', type); }; - // We enter the sleep state when the user presses the Sleep button - // We can fire sleep, holdsleep, or homesleep events from this state - var sleepState = { - timer: null, - enter: function() { - this.timer = setTimeout(function() { - fire('holdsleep'); - setState(baseState); - }, HOLD_INTERVAL); - }, - exit: function() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - }, - process: function(type) { - switch (type) { + /** + * We enter the sleep state when the user presses the Sleep button + * We can fire sleep, holdsleep, or homesleep events from this state. + * + * @class HardwareButtonsSleepState + */ + HardwareButtonsSleepState = + HardwareButtons.STATES.sleep = function HardwareButtonsSleepState(hb) { + this.hardwareButtons = hb; + this.timer = undefined; + }; + + /** + * Entering the state. + * @memberof HardwareButtonsSleepState.prototype + */ + HardwareButtonsSleepState.prototype.enter = function() { + this.timer = setTimeout(function() { + /** + * When the user holds Sleep button more than HOLD_INTERVAL. + * @event HardwareButtonsSleepState#holdsleep + */ + this.hardwareButtons.publish('holdsleep'); + this.hardwareButtons.setState('base'); + }.bind(this), this.hardwareButtons.HOLD_INTERVAL); + }; + + /** + * Leaving the state. + * @memberof HardwareButtonsSleepState.prototype + */ + HardwareButtonsSleepState.prototype.exit = function() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + }; + + /** + * Process the event, maybe transition the state. + * @memberof HardwareButtonsSleepState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsSleepState.prototype.process = function(type) { + switch (type) { case 'sleep-button-release': - fire('sleep'); - setState(baseState, type); + /** + * When the user releases Sleep button before HOLD_INTERVAL. + * @event HardwareButtonsSleepState#sleep + */ + this.hardwareButtons.publish('sleep'); + this.hardwareButtons.setState('base', type); return; case 'home-button-press': - fire('home+sleep'); - setState(baseState, type); + /** + * When the user presses Home button, before HOLD_INTERVAL, + * while holding the Sleep button. + * @event HardwareButtonsSleepState#home+sleep + */ + this.hardwareButtons.publish('home+sleep'); + this.hardwareButtons.setState('base', type); return; case 'volume-up-button-press': case 'volume-down-button-press': - setState(volumeState, type); + this.hardwareButtons.setState('volume', type); return; - } - console.error('Unexpected hardware key: ', type); - setState(baseState, type); } + console.error('Unexpected hardware key: ', type); + this.hardwareButtons.setState('base', type); }; - // We enter the volume state when the user presses the volume up or - // volume down buttons. - // We can fire volumeup and volumedown events from this state - var volumeState = { - direction: null, - timer: null, - repeating: false, - repeat: function() { - this.repeating = true; - if (this.direction === 'volume-up-button-press') - fire('volumeup'); - else - fire('volumedown'); - this.timer = setTimeout(this.repeat.bind(this), REPEAT_INTERVAL); - }, - enter: function(type) { - var self = this; - this.direction = type; // Is volume going up or down? + /** + * We enter the volume state when the user presses the volume up or + * volume down buttons. + * We can fire volumeup and volumedown events from this state + * + * @class HardwareButtonsVolumeState + * + */ + HardwareButtonsVolumeState = + HardwareButtons.STATES.volume = function HardwareButtonsVolumeState(hb) { + this.hardwareButtons = hb; + this.timer = undefined; this.repeating = false; - this.timer = setTimeout(this.repeat.bind(this), REPEAT_DELAY); - }, - exit: function() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - }, - process: function(type) { - switch (type) { + }; + + /** + * Trigger repeat actions for volume buttons. + * @memberof HardwareButtonsVolumeState.prototype + */ + HardwareButtonsVolumeState.prototype.repeat = function() { + this.repeating = true; + if (this.direction === 'volume-up-button-press') { + /** + * Volume up pressed and released or autorepeated. + * @event HardwareButtonsVolumeState#volumeup + */ + this.hardwareButtons.publish('volumeup'); + } else { + /** + * Volume down pressed and released or autorepeated. + * @event HardwareButtonsVolumeState#volumedown + */ + this.hardwareButtons.publish('volumedown'); + } + this.timer = setTimeout(this.repeat.bind(this), + this.hardwareButtons.REPEAT_INTERVAL); + }; + + /** + * Entering the state. + * @memberof HardwareButtonsVolumeState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsVolumeState.prototype.enter = function(type) { + this.direction = type; // Is volume going up or down? + this.repeating = false; + this.timer = + setTimeout(this.repeat.bind(this), this.hardwareButtons.REPEAT_DELAY); + }; + + /** + * Process the event, maybe transition the state. + * @memberof HardwareButtonsVolumeState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsVolumeState.prototype.process = function(type) { + switch (type) { case 'home-button-press': - setState(baseState, type); + this.hardwareButtons.setState('base', type); return; case 'sleep-button-press': - setState(sleepState, type); + this.hardwareButtons.setState('sleep', type); return; case 'volume-up-button-release': if (this.direction === 'volume-up-button-press') { - if (!this.repeating) - fire('volumeup'); - setState(baseState, type); + if (!this.repeating) { + this.hardwareButtons.publish('volumeup'); + } + this.hardwareButtons.setState('base', type); return; } break; case 'volume-down-button-release': if (this.direction === 'volume-down-button-press') { - if (!this.repeating) - fire('volumedown'); - setState(baseState, type); + if (!this.repeating) { + this.hardwareButtons.publish('volumedown'); + } + this.hardwareButtons.setState('base', type); return; } break; default: // Ignore anything else (such as sleep button release) return; - } - console.error('Unexpected hardware key: ', type); - setState(baseState, type); } + console.error('Unexpected hardware key: ', type); + this.hardwareButtons.setState('base', type); }; - // We enter this state when the user presses Home or Sleep on a sleeping - // phone. We give immediate feedback by waking the phone up on the press - // rather than waiting for the release, but this means we need a special - // state so that we don't actually send a home or sleep event on the - // key release. Note, however, that this state does set a timer so that - // it can send holdhome or holdsleep events. (This means that pressing and - // holding sleep will bring up the power menu, even on a sleeping phone.) - var wakeState = { - timer: null, - delegateState: null, - enter: function(type) { - if (type === 'home-button-press') - this.delegateState = homeState; - else - this.delegateState = sleepState; - this.timer = setTimeout(function() { - if (type === 'home-button-press') { - fire('holdhome'); - } else if (type === 'sleep-button-press') { - fire('holdsleep'); - } - setState(baseState, type); - }, HOLD_INTERVAL); - }, - exit: function() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; + /** + * We enter this state when the user presses Home or Sleep on a sleeping + * phone. We give immediate feedback by waking the phone up on the press + * rather than waiting for the release, but this means we need a special + * state so that we don't actually send a home or sleep event on the + * key release. Note, however, that this state does set a timer so that + * it can send holdhome or holdsleep events. (This means that pressing and + * holding sleep will bring up the power menu, even on a sleeping phone.) + * + * @class HardwareButtonsWakeState + */ + HardwareButtonsWakeState = + HardwareButtons.STATES.wake = function HardwareButtonsWakeState(hb) { + this.hardwareButtons = hb; + this.timer = undefined; + this.delegateState = null; + }; + + /** + * Entering the state. + * @memberof HardwareButtonsWakeState.prototype + */ + HardwareButtonsWakeState.prototype.enter = function(type) { + if (type === 'home-button-press') { + this.delegateState = new HardwareButtonsHomeState(this.hardwareButtons); + } else { + this.delegateState = new HardwareButtonsSleepState(this.hardwareButtons); + } + + this.timer = setTimeout(function() { + if (type === 'home-button-press') { + /** + * When the user holds Home button more than HOLD_INTERVAL. + * @event HardwareButtonsWakeState#holdhome + */ + this.hardwareButtons.publish('holdhome'); + } else if (type === 'sleep-button-press') { + /** + * When the user holds Sleep button more than HOLD_INTERVAL. + * @event HardwareButtonsWakeState#holdsleep + */ + this.hardwareButtons.publish('holdsleep'); } - }, - process: function(type) { - switch (type) { + this.hardwareButtons.setState('base', type); + }.bind(this), this.hardwareButtons.HOLD_INTERVAL); + }; + + /** + * Leaving the state. + * @memberof HardwareButtonsWakeState.prototype + */ + HardwareButtonsWakeState.prototype.exit = function() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + }; + + /** + * Process the event, maybe transition the state. + * @memberof HardwareButtonsWakeState.prototype + * @param {String} type Name of the event to process. + */ + HardwareButtonsWakeState.prototype.process = function(type) { + switch (type) { case 'home-button-release': case 'sleep-button-release': - setState(baseState, type); + this.hardwareButtons.setState('base', type); return; default: this.delegateState.process(type); return; - } } }; - // Kick off the FSM in the base state - setState(baseState); -}()); + /* + * Start the hardware buttons events. + * XXX: To be moved. + */ + exports.hardwareButtons = new HardwareButtons(); + exports.hardwareButtons.start(); + + exports.HardwareButtons = HardwareButtons; +}(window)); diff --git a/apps/system/test/unit/hardware_buttons_test.js b/apps/system/test/unit/hardware_buttons_test.js new file mode 100644 index 000000000000..dea31cc6efaf --- /dev/null +++ b/apps/system/test/unit/hardware_buttons_test.js @@ -0,0 +1,386 @@ +'use strict'; + +/* global HardwareButtons, MocksHelper, ScreenManager */ + +mocha.globals(['HardwareButtons', 'ScreenManager']); + +requireApp('system/js/hardware_buttons.js'); +requireApp('system/test/unit/mock_screen_manager.js'); + +var mocksForHardwareButtons = new MocksHelper(['ScreenManager']).init(); + +suite('system/HardwareButtons', function() { + mocksForHardwareButtons.attachTestHelpers(); + + var hardwareButtons; + + //var realDispatchEvent = window.dispatchEvent; + var CustomEvent = window.CustomEvent; + + var fireChromeEvent = function(type) { + var evt = new CustomEvent('mozChromeEvent', { + detail: { + type: type + } + }); + + /** + * XXX: Instead of dispatch the event through real dispatchEvent here + * (bypass stub), we call handleEvent() directly to avoid possible conflict + * within our dirty unit test environment. See bug 864178 for detail. + */ + //realDispatchEvent.call(window, evt); + hardwareButtons.handleEvent(evt); + }; + + setup(function() { + /** + * Since the script still initialize itself, we should not allow + * the "global" instance from being responsive in our tests here. + */ + if (window.hardwareButtons) { + window.hardwareButtons.stop(); + window.hardwareButtons = null; + } + + hardwareButtons = new HardwareButtons(); + hardwareButtons.start(); + + window.CustomEvent = function MockCustomEvent(type) { + return { type: type }; + }; + }); + + teardown(function() { + hardwareButtons.stop(); + + ScreenManager.screenEnabled = true; + window.CustomEvent = CustomEvent; + }); + + test('press and release home (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('home-button-press'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'home' })); + }); + + test('press and release home (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('home-button-press'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'wake' })); + }); + + test('press and release sleep (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('sleep-button-press'); + fireChromeEvent('sleep-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'sleep' })); + }); + + test('press and release sleep (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('sleep-button-press'); + fireChromeEvent('sleep-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'wake' })); + }); + + test('hold home and press sleep (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('sleep-button-press'); + fireChromeEvent('home-button-press'); + fireChromeEvent('sleep-button-release'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'home+sleep' })); + }); + + test('hold home and press sleep (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('sleep-button-press'); + fireChromeEvent('home-button-press'); + fireChromeEvent('sleep-button-release'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'wake' })); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'home+sleep' })); + }); + + test('hold sleep and press home (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('home-button-press'); + fireChromeEvent('sleep-button-press'); + fireChromeEvent('sleep-button-release'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'home+sleep' })); + }); + + test('hold sleep and press home (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('home-button-press'); + fireChromeEvent('sleep-button-press'); + fireChromeEvent('sleep-button-release'); + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'wake' })); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'home+sleep' })); + }); + + test('press and hold home (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + fireChromeEvent('home-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.HOLD_INTERVAL); + stubSetTimeout.getCall(0).args[0].call(window); + + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'holdhome' })); + }); + + test('press and hold home (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('home-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.HOLD_INTERVAL); + stubSetTimeout.getCall(0).args[0].call(window); + + fireChromeEvent('home-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'wake' })); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'holdhome' })); + }); + + test('press and hold sleep (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + fireChromeEvent('sleep-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.HOLD_INTERVAL); + stubSetTimeout.getCall(0).args[0].call(window); + + fireChromeEvent('sleep-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'holdsleep' })); + }); + + test('press and hold sleep (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('sleep-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.HOLD_INTERVAL); + stubSetTimeout.getCall(0).args[0].call(window); + + fireChromeEvent('sleep-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'wake' })); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'holdsleep' })); + }); + + test('press and release volume up (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('volume-up-button-press'); + fireChromeEvent('volume-up-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'volumeup' })); + }); + + test('press and release volume up (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('volume-up-button-press'); + fireChromeEvent('volume-up-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'volumeup' })); + }); + + test('press and hold volume up (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + fireChromeEvent('volume-up-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.REPEAT_DELAY); + stubSetTimeout.getCall(0).args[0].call(window); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'volumeup' })); + + assert.isTrue(stubSetTimeout.calledTwice); + assert.equal(stubSetTimeout.getCall(1).args[1], + hardwareButtons.REPEAT_INTERVAL); + stubSetTimeout.getCall(1).args[0].call(window); + + fireChromeEvent('volume-up-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'volumeup' })); + }); + + test('press and hold volume up (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('volume-up-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.REPEAT_DELAY); + stubSetTimeout.getCall(0).args[0].call(window); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'volumeup' })); + + assert.isTrue(stubSetTimeout.calledTwice); + assert.equal(stubSetTimeout.getCall(1).args[1], + hardwareButtons.REPEAT_INTERVAL); + stubSetTimeout.getCall(1).args[0].call(window); + + fireChromeEvent('volume-up-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'volumeup' })); + }); + + test('press and release volume down (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + fireChromeEvent('volume-down-button-press'); + fireChromeEvent('volume-down-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'volumedown' })); + }); + + test('press and release volume down (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('volume-down-button-press'); + fireChromeEvent('volume-down-button-release'); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue(stubDispatchEvent.calledWith({ type: 'volumedown' })); + }); + + test('press and hold volume down (screen enabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + fireChromeEvent('volume-down-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.REPEAT_DELAY); + stubSetTimeout.getCall(0).args[0].call(window); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'volumedown' })); + + assert.isTrue(stubSetTimeout.calledTwice); + assert.equal(stubSetTimeout.getCall(1).args[1], + hardwareButtons.REPEAT_INTERVAL); + stubSetTimeout.getCall(1).args[0].call(window); + + fireChromeEvent('volume-down-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'volumedown' })); + }); + + test('press and hold volume down (screen disabled)', function() { + var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent'); + var stubSetTimeout = this.sinon.stub(window, 'setTimeout'); + + ScreenManager.screenEnabled = false; + fireChromeEvent('volume-down-button-press'); + + assert.isTrue(stubSetTimeout.calledOnce); + assert.equal(stubSetTimeout.getCall(0).args[1], + hardwareButtons.REPEAT_DELAY); + stubSetTimeout.getCall(0).args[0].call(window); + + assert.isTrue(stubDispatchEvent.calledOnce); + assert.isTrue( + stubDispatchEvent.getCall(0).calledWith({ type: 'volumedown' })); + + assert.isTrue(stubSetTimeout.calledTwice); + assert.equal(stubSetTimeout.getCall(1).args[1], + hardwareButtons.REPEAT_INTERVAL); + stubSetTimeout.getCall(1).args[0].call(window); + + fireChromeEvent('volume-down-button-release'); + + assert.isTrue(stubDispatchEvent.calledTwice); + assert.isTrue( + stubDispatchEvent.getCall(1).calledWith({ type: 'volumedown' })); + }); +}); diff --git a/build/jshint/xfail.list b/build/jshint/xfail.list index f517c7de0610..2331ddff73db 100644 --- a/build/jshint/xfail.list +++ b/build/jshint/xfail.list @@ -920,7 +920,6 @@ apps/system/js/fxa_client.js apps/system/js/fxa_manager.js apps/system/js/fxa_ui.js apps/system/js/gridview.js -apps/system/js/hardware_buttons.js apps/system/js/home_gesture.js apps/system/js/homescreen_launcher.js apps/system/js/homescreen_window.js