summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/tests/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/mediacontrol/tests/browser/head.js402
1 files changed, 402 insertions, 0 deletions
diff --git a/dom/media/mediacontrol/tests/browser/head.js b/dom/media/mediacontrol/tests/browser/head.js
new file mode 100644
index 0000000000..cac96c0bff
--- /dev/null
+++ b/dom/media/mediacontrol/tests/browser/head.js
@@ -0,0 +1,402 @@
+/**
+ * This function would create a new foreround tab and load the url for it. In
+ * addition, instead of returning a tab element, we return a tab wrapper that
+ * helps us to automatically detect if the media controller of that tab
+ * dispatches the first (activated) and the last event (deactivated) correctly.
+ * @ param url
+ * the page url which tab would load
+ * @ param input window (optional)
+ * if it exists, the tab would be created from the input window. If not,
+ * then the tab would be created in current window.
+ * @ param needCheck (optional)
+ * it decides if we would perform the check for the first and last event
+ * on the media controller. It's default true.
+ */
+async function createLoadedTabWrapper(
+ url,
+ { inputWindow = window, needCheck = true } = {}
+) {
+ class tabWrapper {
+ constructor(tab, needCheck) {
+ this._tab = tab;
+ this._controller = tab.linkedBrowser.browsingContext.mediaController;
+ this._firstEvent = "";
+ this._lastEvent = "";
+ this._events = [
+ "activated",
+ "deactivated",
+ "metadatachange",
+ "playbackstatechange",
+ "positionstatechange",
+ "supportedkeyschange",
+ ];
+ this._needCheck = needCheck;
+ if (this._needCheck) {
+ this._registerAllEvents();
+ }
+ }
+ _registerAllEvents() {
+ for (let event of this._events) {
+ this._controller.addEventListener(event, this._handleEvent.bind(this));
+ }
+ }
+ _unregisterAllEvents() {
+ for (let event of this._events) {
+ this._controller.removeEventListener(
+ event,
+ this._handleEvent.bind(this)
+ );
+ }
+ }
+ _handleEvent(event) {
+ info(`handle event=${event.type}`);
+ if (this._firstEvent === "") {
+ this._firstEvent = event.type;
+ }
+ this._lastEvent = event.type;
+ }
+ get linkedBrowser() {
+ return this._tab.linkedBrowser;
+ }
+ get controller() {
+ return this._controller;
+ }
+ get tabElement() {
+ return this._tab;
+ }
+ async close() {
+ info(`wait until finishing close tab wrapper`);
+ const deactivationPromise = this._controller.isActive
+ ? new Promise(r => (this._controller.ondeactivated = r))
+ : Promise.resolve();
+ BrowserTestUtils.removeTab(this._tab);
+ await deactivationPromise;
+ if (this._needCheck) {
+ is(this._firstEvent, "activated", "First event should be 'activated'");
+ is(
+ this._lastEvent,
+ "deactivated",
+ "Last event should be 'deactivated'"
+ );
+ this._unregisterAllEvents();
+ }
+ }
+ }
+ const browser = inputWindow ? inputWindow.gBrowser : window.gBrowser;
+ let tab = await BrowserTestUtils.openNewForegroundTab(browser, url);
+ return new tabWrapper(tab, needCheck);
+}
+
+/**
+ * Returns a promise that resolves when generated media control keys has
+ * triggered the main media controller's corresponding method and changes its
+ * playback state.
+ *
+ * @param {string} event
+ * The event name of the media control key
+ * @return {Promise}
+ * Resolve when the main controller receives the media control key event
+ * and change its playback state.
+ */
+function generateMediaControlKeyEvent(event) {
+ const playbackStateChanged = waitUntilDisplayedPlaybackChanged();
+ MediaControlService.generateMediaControlKey(event);
+ return playbackStateChanged;
+}
+
+/**
+ * Play the specific media and wait until it plays successfully and the main
+ * controller has been updated.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would play
+ * @param {string} elementId
+ * The element Id of the media which we would play
+ * @return {Promise}
+ * Resolve when the media has been starting playing and the main
+ * controller has been updated.
+ */
+async function playMedia(tab, elementId) {
+ const playbackStatePromise = waitUntilDisplayedPlaybackChanged();
+ await SpecialPowers.spawn(tab.linkedBrowser, [elementId], async Id => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ ok(
+ await video.play().then(
+ _ => true,
+ _ => false
+ ),
+ "video started playing"
+ );
+ });
+ return playbackStatePromise;
+}
+
+/**
+ * Pause the specific media and wait until it pauses successfully and the main
+ * controller has been updated.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would pause
+ * @param {string} elementId
+ * The element Id of the media which we would pause
+ * @return {Promise}
+ * Resolve when the media has been paused and the main controller has
+ * been updated.
+ */
+function pauseMedia(tab, elementId) {
+ const pausePromise = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [elementId],
+ Id => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ ok(!video.paused, `video is playing before calling pause`);
+ video.pause();
+ }
+ );
+ return Promise.all([pausePromise, waitUntilDisplayedPlaybackChanged()]);
+}
+
+/**
+ * Returns a promise that resolves when the specific media starts playing.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would check
+ * @param {string} elementId
+ * The element Id of the media which we would check
+ * @return {Promise}
+ * Resolve when the media has been starting playing.
+ */
+function checkOrWaitUntilMediaStartedPlaying(tab, elementId) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [elementId], Id => {
+ return new Promise(resolve => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ if (!video.paused) {
+ ok(true, `media started playing`);
+ resolve();
+ } else {
+ info(`wait until media starts playing`);
+ video.onplaying = () => {
+ video.onplaying = null;
+ ok(true, `media started playing`);
+ resolve();
+ };
+ }
+ });
+ });
+}
+
+/**
+ * Returns a promise that resolves when the specific media stops playing.
+ *
+ * @param {tab} tab
+ * The tab that contains the media which we would check
+ * @param {string} elementId
+ * The element Id of the media which we would check
+ * @return {Promise}
+ * Resolve when the media has been stopped playing.
+ */
+function checkOrWaitUntilMediaStoppedPlaying(tab, elementId) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [elementId], Id => {
+ return new Promise(resolve => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ if (video.paused) {
+ ok(true, `media stopped playing`);
+ resolve();
+ } else {
+ info(`wait until media stops playing`);
+ video.onpause = () => {
+ video.onpause = null;
+ ok(true, `media stopped playing`);
+ resolve();
+ };
+ }
+ });
+ });
+}
+
+/**
+ * Check if the active metadata is empty.
+ */
+function isCurrentMetadataEmpty() {
+ const current = MediaControlService.getCurrentActiveMediaMetadata();
+ is(current.title, "", `current title should be empty`);
+ is(current.artist, "", `current title should be empty`);
+ is(current.album, "", `current album should be empty`);
+ is(current.artwork.length, 0, `current artwork should be empty`);
+}
+
+/**
+ * Check if the active metadata is equal to the given metadata.artwork
+ *
+ * @param {object} metadata
+ * The metadata that would be compared with the active metadata
+ */
+function isCurrentMetadataEqualTo(metadata) {
+ const current = MediaControlService.getCurrentActiveMediaMetadata();
+ is(
+ current.title,
+ metadata.title,
+ `tile '${current.title}' is equal to ${metadata.title}`
+ );
+ is(
+ current.artist,
+ metadata.artist,
+ `artist '${current.artist}' is equal to ${metadata.artist}`
+ );
+ is(
+ current.album,
+ metadata.album,
+ `album '${current.album}' is equal to ${metadata.album}`
+ );
+ is(
+ current.artwork.length,
+ metadata.artwork.length,
+ `artwork length '${current.artwork.length}' is equal to ${metadata.artwork.length}`
+ );
+ for (let idx = 0; idx < metadata.artwork.length; idx++) {
+ // the current src we got would be a completed path of the image, so we do
+ // not check if they are equal, we check if the current src includes the
+ // metadata's file name. Eg. "http://foo/bar.jpg" v.s. "bar.jpg"
+ ok(
+ current.artwork[idx].src.includes(metadata.artwork[idx].src),
+ `artwork src '${current.artwork[idx].src}' includes ${metadata.artwork[idx].src}`
+ );
+ is(
+ current.artwork[idx].sizes,
+ metadata.artwork[idx].sizes,
+ `artwork sizes '${current.artwork[idx].sizes}' is equal to ${metadata.artwork[idx].sizes}`
+ );
+ is(
+ current.artwork[idx].type,
+ metadata.artwork[idx].type,
+ `artwork type '${current.artwork[idx].type}' is equal to ${metadata.artwork[idx].type}`
+ );
+ }
+}
+
+/**
+ * Check if the given tab is using the default metadata. If the tab is being
+ * used in the private browsing mode, `isPrivateBrowsing` should be definded in
+ * the `options`.
+ */
+async function isGivenTabUsingDefaultMetadata(tab, options = {}) {
+ const localization = new Localization([
+ "branding/brand.ftl",
+ "dom/media.ftl",
+ ]);
+ const fallbackTitle = await localization.formatValue(
+ "mediastatus-fallback-title"
+ );
+ ok(fallbackTitle.length, "l10n fallback title is not empty");
+
+ const metadata =
+ tab.linkedBrowser.browsingContext.mediaController.getMetadata();
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [metadata.title, fallbackTitle, options.isPrivateBrowsing],
+ (title, fallbackTitle, isPrivateBrowsing) => {
+ if (isPrivateBrowsing || !content.document.title.length) {
+ is(title, fallbackTitle, "Using a generic default fallback title");
+ } else {
+ is(
+ title,
+ content.document.title,
+ "Using website title as a default title"
+ );
+ }
+ }
+ );
+ is(metadata.artwork.length, 1, "Default metada contains one artwork");
+ ok(
+ metadata.artwork[0].src.includes("defaultFavicon.svg"),
+ "Using default favicon as a default art work"
+ );
+}
+
+/**
+ * Wait until the main media controller changes its playback state, we would
+ * observe that by listening for `media-displayed-playback-changed`
+ * notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `media-displayed-playback-changed`
+ */
+function waitUntilDisplayedPlaybackChanged() {
+ return BrowserUtils.promiseObserved("media-displayed-playback-changed");
+}
+
+/**
+ * Wait until the metadata that would be displayed on the virtual control
+ * interface changes. we would observe that by listening for
+ * `media-displayed-metadata-changed` notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `media-displayed-metadata-changed`
+ */
+function waitUntilDisplayedMetadataChanged() {
+ return BrowserUtils.promiseObserved("media-displayed-metadata-changed");
+}
+
+/**
+ * Wait until the main media controller has been changed, we would observe that
+ * by listening for the `main-media-controller-changed` notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `main-media-controller-changed`
+ */
+function waitUntilMainMediaControllerChanged() {
+ return BrowserUtils.promiseObserved("main-media-controller-changed");
+}
+
+/**
+ * Wait until any media controller updates its metadata even if it's not the
+ * main controller. The difference between this function and
+ * `waitUntilDisplayedMetadataChanged()` is that the changed metadata might come
+ * from non-main controller so it won't be show on the virtual control
+ * interface. we would observe that by listening for
+ * `media-session-controller-metadata-changed` notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `media-session-controller-metadata-changed`
+ */
+function waitUntilControllerMetadataChanged() {
+ return BrowserUtils.promiseObserved(
+ "media-session-controller-metadata-changed"
+ );
+}
+
+/**
+ * Wait until media controller amount changes, we would observe that by
+ * listening for `media-controller-amount-changed` notification.
+ *
+ * @return {Promise}
+ * Resolve when observing `media-controller-amount-changed`
+ */
+function waitUntilMediaControllerAmountChanged() {
+ return BrowserUtils.promiseObserved("media-controller-amount-changed");
+}
+
+/**
+ * check if the media controll from given tab is active. If not, return a
+ * promise and resolve it when controller become active.
+ */
+async function checkOrWaitUntilControllerBecomeActive(tab) {
+ const controller = tab.linkedBrowser.browsingContext.mediaController;
+ if (controller.isActive) {
+ return;
+ }
+ await new Promise(r => (controller.onactivated = r));
+}