mirror of
https://github.com/videojs/video.js.git
synced 2024-12-10 11:10:19 +02:00
fix: make sure hotkeys are not triggered outside the player or in form fields within the player (#5969)
This commit is contained in:
parent
5a7fe48b07
commit
79eadac252
@ -47,8 +47,7 @@ class BigPlayButton extends Button {
|
||||
// exit early if clicked via the mouse
|
||||
if (this.mouseused_ && event.clientX && event.clientY) {
|
||||
silencePromise(playPromise);
|
||||
// call handleFocus manually to get hotkeys working
|
||||
this.player_.handleFocus({});
|
||||
this.player_.tech(true).focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -56,7 +55,7 @@ class BigPlayButton extends Button {
|
||||
const playToggle = cb && cb.getChild('playToggle');
|
||||
|
||||
if (!playToggle) {
|
||||
this.player_.focus();
|
||||
this.player_.tech(true).focus();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,10 +68,10 @@ class BigPlayButton extends Button {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
this.mouseused_ = false;
|
||||
|
||||
super.handleKeyPress(event);
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
|
||||
handleMouseDown(event) {
|
||||
|
@ -104,13 +104,20 @@ class Button extends ClickableComponent {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Ignore Space or Enter key operation, which is handled by the browser for a button.
|
||||
if (!(keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter'))) {
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
// Ignore Space or Enter key operation, which is handled by the browser for
|
||||
// a button - though not for its super class, ClickableComponent. Also,
|
||||
// prevent the event from propagating through the DOM and triggering Player
|
||||
// hotkeys. We do not preventDefault here because we _want_ the browser to
|
||||
// handle it.
|
||||
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,16 +3,13 @@
|
||||
*/
|
||||
import Component from './component';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as Events from './utils/events.js';
|
||||
import * as Fn from './utils/fn.js';
|
||||
import log from './utils/log.js';
|
||||
import document from 'global/document';
|
||||
import {assign} from './utils/obj';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
* Clickable Component which is clickable or keyboard actionable,
|
||||
* but is not a native HTML button.
|
||||
* Component which is clickable or keyboard actionable, but is not a
|
||||
* native HTML button.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
@ -36,7 +33,7 @@ class ClickableComponent extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`s DOM element.
|
||||
* Create the `ClickableComponent`s DOM element.
|
||||
*
|
||||
* @param {string} [tag=div]
|
||||
* The element's node type.
|
||||
@ -83,7 +80,7 @@ class ClickableComponent extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a control text element on this `Component`
|
||||
* Create a control text element on this `ClickableComponent`
|
||||
*
|
||||
* @param {Element} [el]
|
||||
* Parent element for the control text.
|
||||
@ -109,7 +106,7 @@ class ClickableComponent extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the localize text to use for the controls on the `Component`.
|
||||
* Get or set the localize text to use for the controls on the `ClickableComponent`.
|
||||
*
|
||||
* @param {string} [text]
|
||||
* Control text for element.
|
||||
@ -146,7 +143,7 @@ class ClickableComponent extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable this `Component`s element.
|
||||
* Enable this `ClickableComponent`
|
||||
*/
|
||||
enable() {
|
||||
if (!this.enabled_) {
|
||||
@ -157,13 +154,12 @@ class ClickableComponent extends Component {
|
||||
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
||||
}
|
||||
this.on(['tap', 'click'], this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
this.on('keydown', this.handleKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this `Component`s element.
|
||||
* Disable this `ClickableComponent`
|
||||
*/
|
||||
disable() {
|
||||
this.enabled_ = false;
|
||||
@ -173,27 +169,15 @@ class ClickableComponent extends Component {
|
||||
this.el_.removeAttribute('tabIndex');
|
||||
}
|
||||
this.off(['tap', 'click'], this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
this.off('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `ClickableComponent` gets:
|
||||
* - Clicked (via the `click` event, listening starts in the constructor)
|
||||
* - Tapped (via the `tap` event, listening starts in the constructor)
|
||||
* - The following things happen in order:
|
||||
* 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
|
||||
* `ClickableComponent`.
|
||||
* 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
|
||||
* {@link ClickableComponent#handleKeyPress}.
|
||||
* 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
|
||||
* the space or enter key.
|
||||
* 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
|
||||
* event as a parameter.
|
||||
* Event handler that is called when a `ClickableComponent` receives a
|
||||
* `click` or `tap` event.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown`, `tap`, or `click` event that caused this function to be
|
||||
* called.
|
||||
* The `tap` or `click` event that caused this function to be called.
|
||||
*
|
||||
* @listens tap
|
||||
* @listens click
|
||||
@ -202,52 +186,31 @@ class ClickableComponent extends Component {
|
||||
handleClick(event) {}
|
||||
|
||||
/**
|
||||
* This gets called when a `ClickableComponent` gains focus via a `focus` event.
|
||||
* Turns on listening for `keydown` events. When they happen it
|
||||
* calls `this.handleKeyPress`.
|
||||
* Event handler that is called when a `ClickableComponent` receives a
|
||||
* `keydown` event.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `focus` event that caused this function to be called.
|
||||
*
|
||||
* @listens focus
|
||||
*/
|
||||
handleFocus(event) {
|
||||
Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this ClickableComponent has focus and a key gets pressed down. By
|
||||
* default it will call `this.handleClick` when the key is space or enter.
|
||||
* By default, if the key is Space or Enter, it will trigger a `click` event.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Support Space or Enter key operation to fire a click event
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Support Space or Enter key operation to fire a click event. Also,
|
||||
// prevent the event from propagating through the DOM and triggering
|
||||
// Player hotkeys.
|
||||
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.trigger('click');
|
||||
} else {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a `ClickableComponent` loses focus. Turns off the listener for
|
||||
* `keydown` events. Which Stops `this.handleKeyPress` from getting called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `blur` event that caused this function to be called.
|
||||
*
|
||||
* @listens blur
|
||||
*/
|
||||
handleBlur(event) {
|
||||
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('ClickableComponent', ClickableComponent);
|
||||
|
@ -36,25 +36,10 @@ class CloseButton extends Button {
|
||||
return `vjs-close-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `CloseButton` has focus and `keydown` is triggered via a key
|
||||
* press.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The event that caused this function to get called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
// Override the default `Button` behavior, and don't pass the keypress event
|
||||
// up to the player because this button is part of a `ModalDialog`, which
|
||||
// doesn't pass keypresses to the player either.
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `CloseButton` gets clicked. See
|
||||
* {@link ClickableComponent#handleClick} for more information on when this will be
|
||||
* triggered
|
||||
* {@link ClickableComponent#handleClick} for more information on when
|
||||
* this will be triggered
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown`, `tap`, or `click` event that caused this function to be
|
||||
|
@ -1078,18 +1078,35 @@ class Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* When this Component receives a keydown event which it does not process,
|
||||
* When this Component receives a `keydown` event which it does not process,
|
||||
* it passes the event to the Player for handling.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
if (this.player_) {
|
||||
this.player_.handleKeyPress(event);
|
||||
|
||||
// We only stop propagation here because we want unhandled events to fall
|
||||
// back to the browser.
|
||||
event.stopPropagation();
|
||||
this.player_.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Many components used to have a `handleKeyPress` method, which was poorly
|
||||
* named because it listened to a `keydown` event. This method name now
|
||||
* delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
|
||||
* will not see their method calls stop working.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The event that caused this function to be called.
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
this.handleKeyDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a 'tap' events when touch event support gets detected. This gets used to
|
||||
* support toggling the controls through a tap on the video. They get enabled
|
||||
|
@ -407,30 +407,36 @@ class SeekBar extends Slider {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.handleAction(event);
|
||||
} else if (keycode.isEventKey(event, 'Home')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.player_.currentTime(0);
|
||||
} else if (keycode.isEventKey(event, 'End')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.player_.currentTime(this.player_.duration());
|
||||
} else if (/^[0-9]$/.test(keycode(event))) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
|
||||
|
||||
this.player_.currentTime(this.player_.duration() * gotoFraction);
|
||||
} else if (keycode.isEventKey(event, 'PgDn')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.player_.currentTime(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
|
||||
} else if (keycode.isEventKey(event, 'PgUp')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.player_.currentTime(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
|
||||
} else {
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
// Pass keydown handling up for unsupported keys
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,8 @@ import Button from '../button.js';
|
||||
import Component from '../component.js';
|
||||
import Menu from './menu.js';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as Events from '../utils/events.js';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
import { IS_IOS } from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
import keycode from 'keycode';
|
||||
|
||||
/**
|
||||
@ -50,12 +47,12 @@ class MenuButton extends Component {
|
||||
|
||||
this.on(this.menuButton_, 'tap', this.handleClick);
|
||||
this.on(this.menuButton_, 'click', this.handleClick);
|
||||
this.on(this.menuButton_, 'focus', this.handleFocus);
|
||||
this.on(this.menuButton_, 'blur', this.handleBlur);
|
||||
this.on(this.menuButton_, 'keydown', this.handleKeyDown);
|
||||
this.on(this.menuButton_, 'mouseenter', () => {
|
||||
this.menu.show();
|
||||
});
|
||||
this.on('keydown', this.handleSubmenuKeyPress);
|
||||
|
||||
this.on('keydown', this.handleSubmenuKeyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,48 +243,23 @@ class MenuButton extends Component {
|
||||
this.menuButton_.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `MenuButton` gains focus via a `focus` event.
|
||||
* Turns on listening for `keydown` events. When they happen it
|
||||
* calls `this.handleKeyPress`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `focus` event that caused this function to be called.
|
||||
*
|
||||
* @listens focus
|
||||
*/
|
||||
handleFocus() {
|
||||
Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a `MenuButton` loses focus. Turns off the listener for
|
||||
* `keydown` events. Which Stops `this.handleKeyPress` from getting called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `blur` event that caused this function to be called.
|
||||
*
|
||||
* @listens blur
|
||||
*/
|
||||
handleBlur() {
|
||||
Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
|
||||
* {@link ClickableComponent#handleKeyPress} for instances where this is called.
|
||||
* {@link ClickableComponent#handleKeyDown} for instances where this is called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Escape or Tab unpress the 'button'
|
||||
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
|
||||
if (this.buttonPressed_) {
|
||||
this.unpressButton();
|
||||
}
|
||||
|
||||
// Don't preventDefault for Tab key - we still want to lose focus
|
||||
if (!keycode.isEventKey(event, 'Tab')) {
|
||||
event.preventDefault();
|
||||
@ -300,14 +272,21 @@ class MenuButton extends Component {
|
||||
event.preventDefault();
|
||||
this.pressButton();
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because it is
|
||||
// just entending the keypress handling of the actual `Button`
|
||||
// in the `MenuButton` which already passes unused keys up.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method name now delegates to `handleSubmenuKeyDown`. This means
|
||||
* anyone calling `handleSubmenuKeyPress` will not see their method calls
|
||||
* stop working.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The event that caused this function to be called.
|
||||
*/
|
||||
handleSubmenuKeyPress(event) {
|
||||
this.handleSubmenuKeyDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a `keydown` event on a sub-menu. The listener for this is added in
|
||||
* the constructor.
|
||||
@ -317,7 +296,7 @@ class MenuButton extends Component {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleSubmenuKeyPress(event) {
|
||||
handleSubmenuKeyDown(event) {
|
||||
// Escape or Tab unpress the 'button'
|
||||
if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) {
|
||||
if (this.buttonPressed_) {
|
||||
@ -331,8 +310,8 @@ class MenuButton extends Component {
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because it is
|
||||
// just entending the keypress handling of the `MenuItem`
|
||||
// keydown events up to the Component handler, because it is
|
||||
// just entending the keydown handling of the `MenuItem`
|
||||
// in the `Menu` which already passes unused keys up.
|
||||
}
|
||||
}
|
||||
|
@ -72,17 +72,17 @@ class MenuItem extends ClickableComponent {
|
||||
|
||||
/**
|
||||
* Ignore keys which are used by the menu, but pass any other ones up. See
|
||||
* {@link ClickableComponent#handleKeyPress} for instances where this is called.
|
||||
* {@link ClickableComponent#handleKeyDown} for instances where this is called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
if (!MenuKeys.some((key) => keycode.isEventKey(event, key))) {
|
||||
// Pass keypress handling up for unused keys
|
||||
super.handleKeyPress(event);
|
||||
// Pass keydown handling up for unused keys
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ class Menu extends Component {
|
||||
|
||||
this.focusedChild_ = -1;
|
||||
|
||||
this.on('keydown', this.handleKeyPress);
|
||||
this.on('keydown', this.handleKeyDown);
|
||||
|
||||
// All the menu item instances share the same blur handler provided by the menu container.
|
||||
this.boundHandleBlur_ = Fn.bind(this, this.handleBlur);
|
||||
@ -211,22 +211,19 @@ class Menu extends Component {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Left and Down Arrows
|
||||
if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.stepForward();
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.stepBack();
|
||||
} else {
|
||||
// NOTE: This is a special case where we don't pass unhandled
|
||||
// keypress events up to the Component handler, because this
|
||||
// is just adding a keypress handler on top of the MenuItem's
|
||||
// existing keypress handler, which already handles passing keypress
|
||||
// events up.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
* @file modal-dialog.js
|
||||
*/
|
||||
import * as Dom from './utils/dom';
|
||||
import * as Fn from './utils/fn';
|
||||
import Component from './component';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
@ -119,21 +118,6 @@ class ModalDialog extends Component {
|
||||
return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles `keydown` events on the document, looking for ESC, which closes
|
||||
* the modal.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The keypress that triggered this event.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label string for this modal. Primarily used for accessibility.
|
||||
*
|
||||
@ -195,9 +179,7 @@ class ModalDialog extends Component {
|
||||
player.pause();
|
||||
}
|
||||
|
||||
if (this.closeable()) {
|
||||
this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
this.on('keydown', this.handleKeyDown);
|
||||
|
||||
// Hide controls and note if they were enabled.
|
||||
this.hadControls_ = player.controls();
|
||||
@ -260,9 +242,7 @@ class ModalDialog extends Component {
|
||||
player.play();
|
||||
}
|
||||
|
||||
if (this.closeable()) {
|
||||
this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
this.off('keydown', this.handleKeyDown);
|
||||
|
||||
if (this.hadControls_) {
|
||||
player.controls(true);
|
||||
@ -444,8 +424,6 @@ class ModalDialog extends Component {
|
||||
this.previouslyActiveEl_ = activeEl;
|
||||
|
||||
this.focus();
|
||||
|
||||
this.on(document, 'keydown', this.handleKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,8 +437,6 @@ class ModalDialog extends Component {
|
||||
this.previouslyActiveEl_.focus();
|
||||
this.previouslyActiveEl_ = null;
|
||||
}
|
||||
|
||||
this.off(document, 'keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -469,6 +445,16 @@ class ModalDialog extends Component {
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Do not allow keydowns to reach out of the modal dialog.
|
||||
event.stopPropagation();
|
||||
|
||||
if (keycode.isEventKey(event, 'Escape') && this.closeable()) {
|
||||
event.preventDefault();
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// exit early if it isn't a tab key
|
||||
if (!keycode.isEventKey(event, 'Tab')) {
|
||||
return;
|
||||
|
@ -354,7 +354,6 @@ class Player extends Component {
|
||||
// Create bound methods for document listeners.
|
||||
this.boundDocumentFullscreenChange_ = Fn.bind(this, this.documentFullscreenChange_);
|
||||
this.boundFullWindowOnEscKey_ = Fn.bind(this, this.fullWindowOnEscKey);
|
||||
this.boundHandleKeyPress_ = Fn.bind(this, this.handleKeyPress);
|
||||
|
||||
// create logger
|
||||
this.log = createLogger(this.id_);
|
||||
@ -532,9 +531,8 @@ class Player extends Component {
|
||||
this.reportUserActivity();
|
||||
|
||||
this.one('play', this.listenForUserActivity_);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
this.on('stageclick', this.handleStageClick_);
|
||||
this.on('keydown', this.handleKeyDown);
|
||||
|
||||
this.breakpoints(this.options_.breakpoints);
|
||||
this.responsive(this.options_.responsive);
|
||||
@ -563,7 +561,6 @@ class Player extends Component {
|
||||
// Make sure all player-specific document listeners are unbound. This is
|
||||
Events.off(document, FullscreenApi.fullscreenchange, this.boundDocumentFullscreenChange_);
|
||||
Events.off(document, 'keydown', this.boundFullWindowOnEscKey_);
|
||||
Events.off(document, 'keydown', this.boundHandleKeyPress_);
|
||||
|
||||
if (this.styleEl_ && this.styleEl_.parentNode) {
|
||||
this.styleEl_.parentNode.removeChild(this.styleEl_);
|
||||
@ -2804,35 +2801,6 @@ class Player extends Component {
|
||||
this.trigger('exitFullWindow');
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when a `Player` gains focus via a `focus` event.
|
||||
* Turns on listening for `keydown` events. When they happen it
|
||||
* calls `this.handleKeyPress`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `focus` event that caused this function to be called.
|
||||
*
|
||||
* @listens focus
|
||||
*/
|
||||
handleFocus(event) {
|
||||
// call off first to make sure we don't keep adding keydown handlers
|
||||
Events.off(document, 'keydown', this.boundHandleKeyPress_);
|
||||
Events.on(document, 'keydown', this.boundHandleKeyPress_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a `Player` loses focus. Turns off the listener for
|
||||
* `keydown` events. Which Stops `this.handleKeyPress` from getting called.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `blur` event that caused this function to be called.
|
||||
*
|
||||
* @listens blur
|
||||
*/
|
||||
handleBlur(event) {
|
||||
Events.off(document, 'keydown', this.boundHandleKeyPress_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this Player has focus and a key gets pressed down, or when
|
||||
* any Component of this player receives a key press that it doesn't handle.
|
||||
@ -2844,19 +2812,49 @@ class Player extends Component {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
const {userActions} = this.options_;
|
||||
|
||||
if (this.options_.userActions && this.options_.userActions.hotkeys && (this.options_.userActions.hotkeys !== false)) {
|
||||
// Bail out if hotkeys are not configured.
|
||||
if (!userActions || !userActions.hotkeys) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.options_.userActions.hotkeys === 'function') {
|
||||
// Function that determines whether or not to exclude an element from
|
||||
// hotkeys handling.
|
||||
const excludeElement = (el) => {
|
||||
const tagName = el.tagName.toLowerCase();
|
||||
|
||||
this.options_.userActions.hotkeys.call(this, event);
|
||||
// These tags will be excluded entirely.
|
||||
const excludedTags = ['textarea'];
|
||||
|
||||
} else {
|
||||
|
||||
this.handleHotkeys(event);
|
||||
// Inputs matching these types will still trigger hotkey handling as
|
||||
// they are not text inputs.
|
||||
const allowedInputTypes = [
|
||||
'button',
|
||||
'checkbox',
|
||||
'hidden',
|
||||
'radio',
|
||||
'reset',
|
||||
'submit'
|
||||
];
|
||||
|
||||
if (tagName === 'input') {
|
||||
return allowedInputTypes.indexOf(el.type) === -1;
|
||||
}
|
||||
|
||||
return excludedTags.indexOf(tagName) !== -1;
|
||||
};
|
||||
|
||||
// Bail out if the user is focused on an interactive form element.
|
||||
if (excludeElement(this.el_.ownerDocument.activeElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof userActions.hotkeys === 'function') {
|
||||
userActions.hotkeys.call(this, event);
|
||||
} else {
|
||||
this.handleHotkeys(event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2882,8 +2880,8 @@ class Player extends Component {
|
||||
} = hotkeys;
|
||||
|
||||
if (fullscreenKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const FSToggle = Component.getComponent('FullscreenToggle');
|
||||
|
||||
@ -2892,16 +2890,16 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
} else if (muteKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const MuteToggle = Component.getComponent('MuteToggle');
|
||||
|
||||
MuteToggle.prototype.handleClick.call(this);
|
||||
|
||||
} else if (playPauseKey.call(this, event)) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const PlayToggle = Component.getComponent('PlayToggle');
|
||||
|
||||
|
@ -112,14 +112,13 @@ class PosterImage extends ClickableComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.player_.tech(true).focus();
|
||||
|
||||
if (this.player_.paused()) {
|
||||
silencePromise(this.player_.play());
|
||||
} else {
|
||||
this.player_.pause();
|
||||
}
|
||||
|
||||
// call handleFocus manually to get hotkeys working
|
||||
this.player_.handleFocus({});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,8 +56,7 @@ class Slider extends Component {
|
||||
|
||||
this.on('mousedown', this.handleMouseDown);
|
||||
this.on('touchstart', this.handleMouseDown);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
this.on('keydown', this.handleKeyDown);
|
||||
this.on('click', this.handleClick);
|
||||
|
||||
this.on(this.player_, 'controlsvisible', this.update);
|
||||
@ -83,8 +82,7 @@ class Slider extends Component {
|
||||
|
||||
this.off('mousedown', this.handleMouseDown);
|
||||
this.off('touchstart', this.handleMouseDown);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
this.off('keydown', this.handleKeyDown);
|
||||
this.off('click', this.handleClick);
|
||||
this.off(this.player_, 'controlsvisible', this.update);
|
||||
this.off(doc, 'mousemove', this.handleMouseMove);
|
||||
@ -293,18 +291,6 @@ class Slider extends Component {
|
||||
return position.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a `focus` event on this `Slider`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `focus` event that caused this function to run.
|
||||
*
|
||||
* @listens focus
|
||||
*/
|
||||
handleFocus() {
|
||||
this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
|
||||
* arrow keys. This function will only be called when the slider has focus. See
|
||||
@ -315,36 +301,26 @@ class Slider extends Component {
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
handleKeyDown(event) {
|
||||
|
||||
// Left and Down Arrows
|
||||
if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.stepBack();
|
||||
|
||||
// Up and Right Arrows
|
||||
} else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.stepForward();
|
||||
} else {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
// Pass keydown handling up for unsupported keys
|
||||
super.handleKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a `blur` event on this `Slider`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `blur` event that caused this function to run.
|
||||
*
|
||||
* @listens blur
|
||||
*/
|
||||
|
||||
handleBlur() {
|
||||
this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for click events on slider, used to prevent clicks
|
||||
* from bubbling up to parent elements like button menus.
|
||||
@ -353,7 +329,7 @@ class Slider extends Component {
|
||||
* Event that caused this object to run
|
||||
*/
|
||||
handleClick(event) {
|
||||
event.stopImmediatePropagation();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
* @file text-track-settings.js
|
||||
*/
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import Component from '../component';
|
||||
import ModalDialog from '../modal-dialog';
|
||||
import {createEl} from '../utils/dom';
|
||||
@ -590,7 +589,6 @@ class TextTrackSettings extends ModalDialog {
|
||||
*/
|
||||
conditionalBlur_() {
|
||||
this.previouslyActiveEl_ = null;
|
||||
this.off(document, 'keydown', this.handleKeyDown);
|
||||
|
||||
const cb = this.player_.controlBar;
|
||||
const subsCapsBtn = cb && cb.subsCapsButton;
|
||||
|
@ -146,35 +146,25 @@ QUnit.test('should remove old event listeners when the menu item adds to the new
|
||||
// `Menu`.`children` will be called when triggering blur event on the menu item.
|
||||
const menuChildrenSpy = sinon.spy(watchedMenu, 'children');
|
||||
|
||||
// The number of blur listeners is two because `ClickableComponent`
|
||||
// adds the blur event listener during the construction and
|
||||
assert.strictEqual(eventData.handlers.blur.length, 1, 'the number of blur listeners is one');
|
||||
|
||||
// The number of click listeners is two because `ClickableComponent`
|
||||
// adds the click event listener during the construction and
|
||||
// `MenuItem` inherits from `ClickableComponent`.
|
||||
assert.strictEqual(eventData.handlers.blur.length, 2, 'the number of blur listeners is two');
|
||||
// Same reason mentioned above.
|
||||
assert.strictEqual(eventData.handlers.click.length, 2, 'the number of click listeners is two');
|
||||
|
||||
const blurListenerAddedByMenu = eventData.handlers.blur[1];
|
||||
const clickListenerAddedByMenu = eventData.handlers.click[1];
|
||||
|
||||
assert.strictEqual(
|
||||
typeof blurListenerAddedByMenu.calledOnce,
|
||||
'undefined',
|
||||
'previous blur listener wrapped in the spy should be removed'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
typeof clickListenerAddedByMenu.calledOnce,
|
||||
'undefined',
|
||||
'previous click listener wrapped in the spy should be removed'
|
||||
);
|
||||
|
||||
const blurListenerSpy = eventData.handlers.blur[1] = sinon.spy(blurListenerAddedByMenu);
|
||||
const clickListenerSpy = eventData.handlers.click[1] = sinon.spy(clickListenerAddedByMenu);
|
||||
|
||||
TestHelpers.triggerDomEvent(menuItem.el(), 'blur');
|
||||
|
||||
assert.ok(blurListenerSpy.calledOnce, 'blur event listener should be called');
|
||||
assert.strictEqual(blurListenerSpy.getCall(0).args[0].target, menuItem.el(), 'event target should be the `menuItem`');
|
||||
assert.ok(menuChildrenSpy.calledOnce, '`watchedMenu`.`children` has been called');
|
||||
|
||||
TestHelpers.triggerDomEvent(menuItem.el(), 'click');
|
||||
|
@ -5,7 +5,11 @@ import ModalDialog from '../../src/js/modal-dialog';
|
||||
import * as Dom from '../../src/js/utils/dom';
|
||||
import TestHelpers from './test-helpers';
|
||||
|
||||
const ESC = 27;
|
||||
const getMockEscapeEvent = () => ({
|
||||
which: 27,
|
||||
preventDefault() {},
|
||||
stopPropagation() {}
|
||||
});
|
||||
|
||||
QUnit.module('ModalDialog', {
|
||||
|
||||
@ -55,6 +59,8 @@ const tabTestHelper = function(assert, player) {
|
||||
shiftKey: shift,
|
||||
preventDefault() {
|
||||
prevented = true;
|
||||
},
|
||||
stopPropagation() {
|
||||
}
|
||||
});
|
||||
|
||||
@ -208,12 +214,12 @@ QUnit.test('pressing ESC triggers close(), but only when the modal is opened', f
|
||||
const spy = sinon.spy();
|
||||
|
||||
this.modal.on('modalclose', spy);
|
||||
this.modal.handleKeyPress({which: ESC});
|
||||
this.modal.handleKeyDown(getMockEscapeEvent());
|
||||
assert.expect(2);
|
||||
assert.strictEqual(spy.callCount, 0, 'ESC did not close the closed modal');
|
||||
|
||||
this.modal.open();
|
||||
this.modal.handleKeyPress({which: ESC});
|
||||
this.modal.handleKeyDown(getMockEscapeEvent());
|
||||
assert.strictEqual(spy.callCount, 1, 'ESC closed the now-opened modal');
|
||||
});
|
||||
|
||||
@ -392,7 +398,7 @@ QUnit.test('closeable()', function(assert) {
|
||||
assert.notOk(this.modal.getChild('closeButton'), 'the close button is no longer a child of the modal');
|
||||
assert.notOk(initialCloseButton.el(), 'the initial close button was disposed');
|
||||
|
||||
this.modal.handleKeyPress({which: ESC});
|
||||
this.modal.handleKeyDown(getMockEscapeEvent());
|
||||
assert.ok(this.modal.opened(), 'the modal was not closed by the ESC key');
|
||||
|
||||
this.modal.close();
|
||||
@ -406,7 +412,7 @@ QUnit.test('closeable()', function(assert) {
|
||||
assert.notOk(this.modal.opened(), 'the modal was closed by the new close button');
|
||||
|
||||
this.modal.open();
|
||||
this.modal.handleKeyPress({which: ESC});
|
||||
this.modal.handleKeyDown(getMockEscapeEvent());
|
||||
assert.notOk(this.modal.opened(), 'the modal was closed by the ESC key');
|
||||
});
|
||||
|
||||
@ -494,7 +500,7 @@ QUnit.test('"uncloseable" option', function(assert) {
|
||||
assert.notOk(modal.getChild('closeButton'), 'the close button is not present');
|
||||
|
||||
modal.open();
|
||||
modal.handleKeyPress({which: ESC});
|
||||
modal.handleKeyDown(getMockEscapeEvent());
|
||||
assert.strictEqual(spy.callCount, 0, 'ESC did not close the modal');
|
||||
modal.dispose();
|
||||
});
|
||||
|
493
test/unit/player-user-actions.test.js
Normal file
493
test/unit/player-user-actions.test.js
Normal file
@ -0,0 +1,493 @@
|
||||
/* eslint-env qunit */
|
||||
import document from 'global/document';
|
||||
import keycode from 'keycode';
|
||||
import sinon from 'sinon';
|
||||
import TestHelpers from './test-helpers';
|
||||
import FullscreenApi from '../../src/js/fullscreen-api.js';
|
||||
|
||||
QUnit.module('Player: User Actions: Double Click', {
|
||||
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.player = TestHelpers.makePlayer({controls: true});
|
||||
},
|
||||
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('by default, double-click opens fullscreen', function(assert) {
|
||||
let fullscreen = false;
|
||||
|
||||
this.player.isFullscreen = () => fullscreen;
|
||||
this.player.requestFullscreen = sinon.spy();
|
||||
this.player.exitFullscreen = sinon.spy();
|
||||
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen once');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
|
||||
fullscreen = true;
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen once');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 1, 'has exited fullscreen');
|
||||
});
|
||||
|
||||
QUnit.test('when controls are disabled, double-click does nothing', function(assert) {
|
||||
let fullscreen = false;
|
||||
|
||||
this.player.controls(false);
|
||||
|
||||
this.player.isFullscreen = () => fullscreen;
|
||||
this.player.requestFullscreen = sinon.spy();
|
||||
this.player.exitFullscreen = sinon.spy();
|
||||
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
|
||||
fullscreen = true;
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.doubleClick is false, double-click does nothing', function(assert) {
|
||||
let fullscreen = false;
|
||||
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
doubleClick: false
|
||||
}
|
||||
});
|
||||
|
||||
this.player.isFullscreen = () => fullscreen;
|
||||
this.player.requestFullscreen = sinon.spy();
|
||||
this.player.exitFullscreen = sinon.spy();
|
||||
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
|
||||
fullscreen = true;
|
||||
this.player.handleTechDoubleClick_({target: this.player.tech_.el_});
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.doubleClick is a function, that function is called instead of going fullscreen', function(assert) {
|
||||
let fullscreen = false;
|
||||
|
||||
const doubleClickSpy = sinon.spy();
|
||||
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
doubleClick: doubleClickSpy
|
||||
}
|
||||
});
|
||||
|
||||
this.player.isFullscreen = () => fullscreen;
|
||||
this.player.requestFullscreen = sinon.spy();
|
||||
this.player.exitFullscreen = sinon.spy();
|
||||
|
||||
let event = {target: this.player.tech_.el_};
|
||||
|
||||
this.player.handleTechDoubleClick_(event);
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
assert.strictEqual(doubleClickSpy.callCount, 1, 'has called the doubleClick handler');
|
||||
assert.strictEqual(doubleClickSpy.getCall(0).args[0], event, 'has passed the event to the handler');
|
||||
|
||||
fullscreen = true;
|
||||
event = {target: this.player.tech_.el_};
|
||||
this.player.handleTechDoubleClick_(event);
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
assert.strictEqual(doubleClickSpy.callCount, 2, 'has called the doubleClick handler');
|
||||
assert.strictEqual(doubleClickSpy.getCall(1).args[0], event, 'has passed the event to the handler');
|
||||
});
|
||||
|
||||
QUnit.module('Player: User Actions: Hotkeys', {
|
||||
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.player = TestHelpers.makePlayer();
|
||||
},
|
||||
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
const mockKeyDownEvent = (key) => {
|
||||
return {
|
||||
preventDefault() {},
|
||||
stopPropagation() {},
|
||||
type: 'keydown',
|
||||
which: keycode.codes[key]
|
||||
};
|
||||
};
|
||||
|
||||
const defaultKeyTests = {
|
||||
fullscreen(player, assert, positive) {
|
||||
let fullscreen;
|
||||
|
||||
if (document[FullscreenApi.fullscreenEnabled] === false) {
|
||||
assert.ok(true, 'skipped fullscreen test because not supported');
|
||||
assert.ok(true, 'skipped fullscreen test because not supported');
|
||||
assert.ok(true, 'skipped fullscreen test because not supported');
|
||||
assert.ok(true, 'skipped fullscreen test because not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
player.isFullscreen = () => fullscreen;
|
||||
player.requestFullscreen = sinon.spy();
|
||||
player.exitFullscreen = sinon.spy();
|
||||
|
||||
fullscreen = false;
|
||||
player.handleKeyDown(mockKeyDownEvent('f'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.requestFullscreen.callCount, 1, 'has gone fullscreen');
|
||||
assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
} else {
|
||||
assert.strictEqual(player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
}
|
||||
|
||||
fullscreen = true;
|
||||
player.handleKeyDown(mockKeyDownEvent('f'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.requestFullscreen.callCount, 1, 'has gone fullscreen');
|
||||
assert.strictEqual(player.exitFullscreen.callCount, 1, 'has exited fullscreen');
|
||||
} else {
|
||||
assert.strictEqual(player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
}
|
||||
},
|
||||
mute(player, assert, positive) {
|
||||
let muted = false;
|
||||
|
||||
player.muted = sinon.spy((val) => {
|
||||
if (val !== undefined) {
|
||||
muted = val;
|
||||
}
|
||||
return muted;
|
||||
});
|
||||
|
||||
player.handleKeyDown(mockKeyDownEvent('m'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.muted.callCount, 2, 'muted was called twice (get and set)');
|
||||
assert.strictEqual(player.muted.lastCall.args[0], true, 'most recent call was to mute');
|
||||
} else {
|
||||
assert.strictEqual(player.muted.callCount, 0, 'muted was not called');
|
||||
}
|
||||
|
||||
player.handleKeyDown(mockKeyDownEvent('m'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.muted.callCount, 4, 'muted was called twice (get and set)');
|
||||
assert.strictEqual(player.muted.lastCall.args[0], false, 'most recent call was to unmute');
|
||||
} else {
|
||||
assert.strictEqual(player.muted.callCount, 0, 'muted was not called');
|
||||
}
|
||||
},
|
||||
playPause(player, assert, positive) {
|
||||
let paused;
|
||||
|
||||
player.paused = () => paused;
|
||||
player.pause = sinon.spy();
|
||||
player.play = sinon.spy();
|
||||
|
||||
paused = true;
|
||||
player.handleKeyDown(mockKeyDownEvent('k'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(player.play.callCount, 1, 'has played');
|
||||
} else {
|
||||
assert.strictEqual(player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(player.play.callCount, 0, 'has not played');
|
||||
}
|
||||
|
||||
paused = false;
|
||||
player.handleKeyDown(mockKeyDownEvent('k'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.pause.callCount, 1, 'has paused');
|
||||
assert.strictEqual(player.play.callCount, 1, 'has played');
|
||||
} else {
|
||||
assert.strictEqual(player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(player.play.callCount, 0, 'has not played');
|
||||
}
|
||||
|
||||
paused = true;
|
||||
player.handleKeyDown(mockKeyDownEvent('space'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.pause.callCount, 1, 'has paused');
|
||||
assert.strictEqual(player.play.callCount, 2, 'has played twice');
|
||||
} else {
|
||||
assert.strictEqual(player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(player.play.callCount, 0, 'has not played');
|
||||
}
|
||||
|
||||
paused = false;
|
||||
player.handleKeyDown(mockKeyDownEvent('space'));
|
||||
|
||||
if (positive) {
|
||||
assert.strictEqual(player.pause.callCount, 2, 'has paused twice');
|
||||
assert.strictEqual(player.play.callCount, 2, 'has played twice');
|
||||
} else {
|
||||
assert.strictEqual(player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(player.play.callCount, 0, 'has not played');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QUnit.test('by default, hotkeys are disabled', function(assert) {
|
||||
assert.expect(14);
|
||||
defaultKeyTests.fullscreen(this.player, assert, false);
|
||||
defaultKeyTests.mute(this.player, assert, false);
|
||||
defaultKeyTests.playPause(this.player, assert, false);
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.hotkeys is true, hotkeys are enabled', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
assert.expect(16);
|
||||
defaultKeyTests.fullscreen(this.player, assert, true);
|
||||
defaultKeyTests.mute(this.player, assert, true);
|
||||
defaultKeyTests.playPause(this.player, assert, true);
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.hotkeys is an object, hotkeys are enabled', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: {}
|
||||
}
|
||||
});
|
||||
|
||||
assert.expect(16);
|
||||
defaultKeyTests.fullscreen(this.player, assert, true);
|
||||
defaultKeyTests.mute(this.player, assert, true);
|
||||
defaultKeyTests.playPause(this.player, assert, true);
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.hotkeys.fullscreenKey can be a function', function(assert) {
|
||||
if (document[FullscreenApi.fullscreenEnabled] === false) {
|
||||
assert.expect(0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: {
|
||||
fullscreenKey: sinon.spy((e) => keycode.isEventKey(e, 'x'))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let fullscreen;
|
||||
|
||||
this.player.isFullscreen = () => fullscreen;
|
||||
this.player.requestFullscreen = sinon.spy();
|
||||
this.player.exitFullscreen = sinon.spy();
|
||||
|
||||
fullscreen = false;
|
||||
this.player.handleKeyDown(mockKeyDownEvent('f'));
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 0, 'has not gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 0, 'has not exited fullscreen');
|
||||
|
||||
fullscreen = true;
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.requestFullscreen.callCount, 1, 'has gone fullscreen');
|
||||
assert.strictEqual(this.player.exitFullscreen.callCount, 1, 'has exited fullscreen');
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.hotkeys.muteKey can be a function', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: {
|
||||
muteKey: sinon.spy((e) => keycode.isEventKey(e, 'x'))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let muted = false;
|
||||
|
||||
this.player.muted = sinon.spy((val) => {
|
||||
if (val !== undefined) {
|
||||
muted = val;
|
||||
}
|
||||
return muted;
|
||||
});
|
||||
|
||||
this.player.handleKeyDown(mockKeyDownEvent('m'));
|
||||
|
||||
assert.strictEqual(this.player.muted.callCount, 0, 'muted was not called');
|
||||
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.muted.callCount, 2, 'muted was called twice (get and set)');
|
||||
assert.strictEqual(this.player.muted.lastCall.args[0], true, 'most recent call was to mute');
|
||||
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.muted.callCount, 4, 'muted was called twice (get and set)');
|
||||
assert.strictEqual(this.player.muted.lastCall.args[0], false, 'most recent call was to unmute');
|
||||
});
|
||||
|
||||
QUnit.test('when userActions.hotkeys.playPauseKey can be a function', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: {
|
||||
playPauseKey: sinon.spy((e) => keycode.isEventKey(e, 'x'))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let paused;
|
||||
|
||||
this.player.paused = () => paused;
|
||||
this.player.pause = sinon.spy();
|
||||
this.player.play = sinon.spy();
|
||||
|
||||
paused = true;
|
||||
this.player.handleKeyDown(mockKeyDownEvent('k'));
|
||||
this.player.handleKeyDown(mockKeyDownEvent('space'));
|
||||
|
||||
assert.strictEqual(this.player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(this.player.play.callCount, 0, 'has not played');
|
||||
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.pause.callCount, 0, 'has not paused');
|
||||
assert.strictEqual(this.player.play.callCount, 1, 'has played');
|
||||
|
||||
paused = false;
|
||||
this.player.handleKeyDown(mockKeyDownEvent('x'));
|
||||
|
||||
assert.strictEqual(this.player.pause.callCount, 1, 'has paused');
|
||||
assert.strictEqual(this.player.play.callCount, 1, 'has played');
|
||||
});
|
||||
|
||||
QUnit.test('hotkeys are ignored when focus is in a textarea', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
this.player.el_.appendChild(textarea);
|
||||
textarea.focus();
|
||||
|
||||
assert.expect(14);
|
||||
defaultKeyTests.fullscreen(this.player, assert, false);
|
||||
defaultKeyTests.mute(this.player, assert, false);
|
||||
defaultKeyTests.playPause(this.player, assert, false);
|
||||
});
|
||||
|
||||
QUnit.test('hotkeys are ignored when focus is in a text input', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
const input = document.createElement('input');
|
||||
|
||||
input.type = 'text';
|
||||
this.player.el_.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
assert.expect(14);
|
||||
defaultKeyTests.fullscreen(this.player, assert, false);
|
||||
defaultKeyTests.mute(this.player, assert, false);
|
||||
defaultKeyTests.playPause(this.player, assert, false);
|
||||
});
|
||||
|
||||
QUnit.test('hotkeys are NOT ignored when focus is on a button element', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
const button = document.createElement('button');
|
||||
|
||||
this.player.el_.appendChild(button);
|
||||
button.focus();
|
||||
|
||||
assert.expect(16);
|
||||
defaultKeyTests.fullscreen(this.player, assert, true);
|
||||
defaultKeyTests.mute(this.player, assert, true);
|
||||
defaultKeyTests.playPause(this.player, assert, true);
|
||||
});
|
||||
|
||||
QUnit.test('hotkeys are NOT ignored when focus is on a button input', function(assert) {
|
||||
this.player.dispose();
|
||||
this.player = TestHelpers.makePlayer({
|
||||
controls: true,
|
||||
userActions: {
|
||||
hotkeys: true
|
||||
}
|
||||
});
|
||||
|
||||
const input = document.createElement('input');
|
||||
|
||||
input.type = 'button';
|
||||
this.player.el_.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
assert.expect(16);
|
||||
defaultKeyTests.fullscreen(this.player, assert, true);
|
||||
defaultKeyTests.mute(this.player, assert, true);
|
||||
defaultKeyTests.playPause(this.player, assert, true);
|
||||
});
|
Loading…
Reference in New Issue
Block a user