tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_default_action_handler.js (14615B)


      1 const PAGE_URL =
      2  "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html";
      3 const PAGE2_URL =
      4  "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_main_frame_with_multiple_child_session_frames.html";
      5 const IFRAME_URL =
      6  "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html";
      7 const CORS_IFRAME_URL =
      8  "https://example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html";
      9 const CORS_IFRAME2_URL =
     10  "https://test1.example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html";
     11 const videoId = "video";
     12 
     13 /**
     14 * This test is used to check the scenario when we should use the customized
     15 * action handler and the the default action handler (play/pause/stop/seekXXX).
     16 * If a frame (DOM Window, it could be main frame or an iframe) has active media
     17 * session, then it should use the customized action handler it it has one.
     18 * Otherwise, the default action handler should be used.
     19 */
     20 add_task(async function setupTestingPref() {
     21  await SpecialPowers.pushPrefEnv({
     22    set: [["media.mediacontrol.testingevents.enabled", true]],
     23  });
     24 });
     25 
     26 add_task(async function triggerDefaultActionHandler() {
     27  // Default handler should be triggered no matter if media session exists or not.
     28  const kCreateMediaSession = [true, false];
     29  for (const shouldCreateSession of kCreateMediaSession) {
     30    info(`open page and start media`);
     31    const tab = await createLoadedTabWrapper(PAGE_URL);
     32    await playMedia(tab, videoId);
     33 
     34    if (shouldCreateSession) {
     35      info(
     36        `media has started, so created session should become active session`
     37      );
     38      await Promise.all([
     39        waitUntilActiveMediaSessionChanged(),
     40        createMediaSession(tab),
     41      ]);
     42    }
     43 
     44    info(`test 'pause' action`);
     45    await simulateMediaAction(tab, "pause");
     46 
     47    info(`default action handler should pause media`);
     48    await checkOrWaitUntilMediaPauses(tab, { videoId });
     49 
     50    info(`test 'seekto' action`);
     51    await simulateMediaAction(tab, "seekto", 2.0);
     52 
     53    info(`default action handler should set currentTime`);
     54    await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0);
     55 
     56    info(`test 'seekforward' action`);
     57    await simulateMediaAction(tab, "seekforward", 1.0);
     58 
     59    info(`default action handler should set currentTime`);
     60    await checkOrWaitUntilMediaSeek(tab, { videoId }, 3.0);
     61 
     62    info(`test 'seekbackward' action`);
     63    await simulateMediaAction(tab, "seekbackward", 1.0);
     64 
     65    info(`default action handler should set currentTime`);
     66    await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0);
     67 
     68    info(`test 'play' action`);
     69    await simulateMediaAction(tab, "play");
     70 
     71    info(`default action handler should resume media`);
     72    await checkOrWaitUntilMediaPlays(tab, { videoId });
     73 
     74    info(`test 'stop' action`);
     75    await simulateMediaAction(tab, "stop");
     76 
     77    info(`default action handler should pause media`);
     78    await checkOrWaitUntilMediaPauses(tab, { videoId });
     79 
     80    const controller = tab.linkedBrowser.browsingContext.mediaController;
     81    ok(
     82      !controller.isActive,
     83      `controller should be deactivated after receiving stop`
     84    );
     85 
     86    info(`remove tab`);
     87    await tab.close();
     88  }
     89 });
     90 
     91 add_task(async function triggerNonDefaultHandlerWhenSetCustomizedHandler() {
     92  info(`open page and start media`);
     93  const tab = await createLoadedTabWrapper(PAGE_URL);
     94  await Promise.all([
     95    new Promise(r => (tab.controller.onactivated = r)),
     96    startMedia(tab, { videoId }),
     97  ]);
     98 
     99  const kActions = ["play", "pause", "stop"];
    100  for (const action of kActions) {
    101    info(`set action handler for '${action}'`);
    102    await setActionHandler(tab, action);
    103 
    104    info(`press '${action}' should trigger action handler (not a default one)`);
    105    await simulateMediaAction(tab, action);
    106    await waitUntilActionHandlerIsTriggered(tab, action);
    107 
    108    info(`action handler doesn't pause media, media should keep playing`);
    109    await checkOrWaitUntilMediaPlays(tab, { videoId });
    110  }
    111 
    112  info(`remove tab`);
    113  await tab.close();
    114 });
    115 
    116 add_task(
    117  async function triggerDefaultHandlerToPausePlaybackOnInactiveSession() {
    118    const kIframeUrls = [IFRAME_URL, CORS_IFRAME_URL];
    119    for (const url of kIframeUrls) {
    120      const kActions = ["play", "pause", "stop"];
    121      for (const action of kActions) {
    122        info(`open page and load iframe`);
    123        const tab = await createLoadedTabWrapper(PAGE_URL);
    124        const frameId = "iframe";
    125        await loadIframe(tab, frameId, url);
    126 
    127        info(`start media from iframe would make it become active session`);
    128        await Promise.all([
    129          new Promise(r => (tab.controller.onactivated = r)),
    130          startMedia(tab, { frameId }),
    131        ]);
    132 
    133        info(`press '${action}' should trigger iframe's action handler`);
    134        await setActionHandler(tab, action, frameId);
    135        await simulateMediaAction(tab, action);
    136        await waitUntilActionHandlerIsTriggered(tab, action, frameId);
    137 
    138        info(`start media from main frame so iframe would become inactive`);
    139        // When action is `play`, controller is already playing, because above
    140        // code won't pause media. So we need to wait for the active session
    141        // changed to ensure the following tests can be executed on the right
    142        // browsing context.
    143        let waitForControllerStatusChanged =
    144          action == "play"
    145            ? waitUntilActiveMediaSessionChanged()
    146            : ensureControllerIsPlaying(tab.controller);
    147        await Promise.all([
    148          waitForControllerStatusChanged,
    149          startMedia(tab, { videoId }),
    150        ]);
    151 
    152        if (action == "play") {
    153          info(`pause media first in order to test 'play'`);
    154          await pauseAllMedia(tab);
    155 
    156          info(
    157            `press '${action}' would trigger default handler on main frame because it doesn't set action handler`
    158          );
    159          await simulateMediaAction(tab, action);
    160          await checkOrWaitUntilMediaPlays(tab, { videoId });
    161 
    162          info(
    163            `default handler should also be triggered on inactive iframe, which would resume media`
    164          );
    165          await checkOrWaitUntilMediaPlays(tab, { frameId });
    166        } else {
    167          info(
    168            `press '${action}' would trigger default handler on main frame because it doesn't set action handler`
    169          );
    170          await simulateMediaAction(tab, action);
    171          await checkOrWaitUntilMediaPauses(tab, { videoId });
    172 
    173          info(
    174            `default handler should also be triggered on inactive iframe, which would pause media`
    175          );
    176          await checkOrWaitUntilMediaPauses(tab, { frameId });
    177        }
    178 
    179        info(`remove tab`);
    180        await tab.close();
    181      }
    182    }
    183  }
    184 );
    185 
    186 add_task(async function onlyResumeActiveMediaSession() {
    187  info(`open page and load iframes`);
    188  const tab = await createLoadedTabWrapper(PAGE2_URL);
    189  const frame1Id = "frame1";
    190  const frame2Id = "frame2";
    191  await loadIframe(tab, frame1Id, CORS_IFRAME_URL);
    192  await loadIframe(tab, frame2Id, CORS_IFRAME2_URL);
    193 
    194  info(`start media from iframe1 would make it become active session`);
    195  await createMediaSession(tab, frame1Id);
    196  await Promise.all([
    197    waitUntilActiveMediaSessionChanged(),
    198    startMedia(tab, { frameId: frame1Id }),
    199  ]);
    200 
    201  info(`start media from iframe2 would make it become active session`);
    202  await createMediaSession(tab, frame2Id);
    203  await Promise.all([
    204    waitUntilActiveMediaSessionChanged(),
    205    startMedia(tab, { frameId: frame2Id }),
    206  ]);
    207 
    208  info(`press 'pause' should pause both iframes`);
    209  await simulateMediaAction(tab, "pause");
    210  await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id });
    211  await checkOrWaitUntilMediaPauses(tab, { frameId: frame2Id });
    212 
    213  info(
    214    `press 'play' should only resume iframe2 which has active media session`
    215  );
    216  await simulateMediaAction(tab, "play");
    217  await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id });
    218  await checkOrWaitUntilMediaPlays(tab, { frameId: frame2Id });
    219 
    220  info(`remove tab`);
    221  await tab.close();
    222 });
    223 
    224 /**
    225 * The following are helper functions.
    226 */
    227 function startMedia(tab, { videoId, frameId }) {
    228  return SpecialPowers.spawn(
    229    tab.linkedBrowser,
    230    [videoId, frameId],
    231    (videoId, frameId) => {
    232      if (frameId) {
    233        return content.messageHelper(
    234          content.document.getElementById(frameId),
    235          "play",
    236          "played"
    237        );
    238      }
    239      return content.document.getElementById(videoId).play();
    240    }
    241  );
    242 }
    243 
    244 function pauseAllMedia(tab) {
    245  return SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
    246    await content.messageHelper(
    247      content.document.getElementById("iframe"),
    248      "pause",
    249      "paused"
    250    );
    251    const videos = content.document.getElementsByTagName("video");
    252    for (let video of videos) {
    253      video.pause();
    254    }
    255  });
    256 }
    257 
    258 function createMediaSession(tab, frameId = null) {
    259  info(`create media session`);
    260  return SpecialPowers.spawn(tab.linkedBrowser, [frameId], async frameId => {
    261    if (frameId) {
    262      await content.messageHelper(
    263        content.document.getElementById(frameId),
    264        "create-media-session",
    265        "created-media-session"
    266      );
    267      return;
    268    }
    269    // simply calling a media session would create an instance.
    270    content.navigator.mediaSession;
    271  });
    272 }
    273 
    274 function checkOrWaitUntilMediaPauses(tab, { videoId, frameId }) {
    275  return SpecialPowers.spawn(
    276    tab.linkedBrowser,
    277    [videoId, frameId],
    278    (videoId, frameId) => {
    279      if (frameId) {
    280        return content.messageHelper(
    281          content.document.getElementById(frameId),
    282          "check-pause",
    283          "checked-pause"
    284        );
    285      }
    286      return new Promise(r => {
    287        const video = content.document.getElementById(videoId);
    288        if (video.paused) {
    289          ok(true, `media stopped playing`);
    290          r();
    291        } else {
    292          info(`wait until media stops playing`);
    293          video.onpause = () => {
    294            video.onpause = null;
    295            ok(true, `media stopped playing`);
    296            r();
    297          };
    298        }
    299      });
    300    }
    301  );
    302 }
    303 
    304 function checkOrWaitUntilMediaPlays(tab, { videoId, frameId }) {
    305  return SpecialPowers.spawn(
    306    tab.linkedBrowser,
    307    [videoId, frameId],
    308    (videoId, frameId) => {
    309      if (frameId) {
    310        return content.messageHelper(
    311          content.document.getElementById(frameId),
    312          "check-playing",
    313          "checked-playing"
    314        );
    315      }
    316      return new Promise(r => {
    317        const video = content.document.getElementById(videoId);
    318        if (!video.paused) {
    319          ok(true, `media is playing`);
    320          r();
    321        } else {
    322          info(`wait until media starts playing`);
    323          video.onplay = () => {
    324            video.onplay = null;
    325            ok(true, `media starts playing`);
    326            r();
    327          };
    328        }
    329      });
    330    }
    331  );
    332 }
    333 
    334 function checkOrWaitUntilMediaSeek(tab, { videoId }, expectedTime) {
    335  return SpecialPowers.spawn(
    336    tab.linkedBrowser,
    337    [videoId, expectedTime],
    338    (videoId, expectedTime) => {
    339      return new Promise(r => {
    340        const video = content.document.getElementById(videoId);
    341        ok(video.paused, "video is paused");
    342        if (video.currentTime == expectedTime) {
    343          ok(true, `media has been seeked`);
    344          r();
    345        } else {
    346          info(`wait until media seeked`);
    347          video.ontimeupdate = () => {
    348            video.ontimeupdate = null;
    349            is(video.currentTime, expectedTime, `correct time set`);
    350            r();
    351          };
    352        }
    353      });
    354    }
    355  );
    356 }
    357 
    358 function setActionHandler(tab, action, frameId = null) {
    359  return SpecialPowers.spawn(
    360    tab.linkedBrowser,
    361    [action, frameId],
    362    async (action, frameId) => {
    363      if (frameId) {
    364        await content.messageHelper(
    365          content.document.getElementById(frameId),
    366          {
    367            cmd: "setActionHandler",
    368            action,
    369          },
    370          "setActionHandler-done"
    371        );
    372        return;
    373      }
    374      // Create this on the first function call
    375      if (content.actionHandlerPromises === undefined) {
    376        content.actionHandlerPromises = {};
    377      }
    378      content.actionHandlerPromises[action] = new Promise(r => {
    379        content.navigator.mediaSession.setActionHandler(action, () => {
    380          info(`receive ${action}`);
    381          r();
    382        });
    383      });
    384    }
    385  );
    386 }
    387 
    388 async function waitUntilActionHandlerIsTriggered(tab, action, frameId = null) {
    389  info(`wait until '${action}' action handler is triggered`);
    390  return SpecialPowers.spawn(
    391    tab.linkedBrowser,
    392    [action, frameId],
    393    (action, frameId) => {
    394      if (frameId) {
    395        return content.messageHelper(
    396          content.document.getElementById(frameId),
    397          {
    398            cmd: "checkActionHandler",
    399            action,
    400          },
    401          "checkActionHandler-done"
    402        );
    403      }
    404      const actionTriggerPromise = content.actionHandlerPromises[action];
    405      ok(actionTriggerPromise, `Has created promise for ${action}`);
    406      return actionTriggerPromise;
    407    }
    408  );
    409 }
    410 
    411 async function simulateMediaAction(tab, action, seekValue = 0.0) {
    412  const controller = tab.linkedBrowser.browsingContext.mediaController;
    413  if (!controller.isActive) {
    414    await new Promise(r => (controller.onactivated = r));
    415  }
    416  MediaControlService.generateMediaControlKey(action, seekValue);
    417 }
    418 
    419 function loadIframe(tab, iframeId, url) {
    420  return SpecialPowers.spawn(
    421    tab.linkedBrowser,
    422    [iframeId, url],
    423    async (iframeId, url) => {
    424      const iframe = content.document.getElementById(iframeId);
    425      info(`load iframe with url '${url}'`);
    426      iframe.src = url;
    427      await new Promise(r => (iframe.onload = r));
    428      // create a helper to simplify communication process with iframe
    429      content.messageHelper = (target, sentMessage, expectedResponse) => {
    430        target.contentWindow.postMessage(sentMessage, "*");
    431        return new Promise(r => {
    432          content.onmessage = event => {
    433            if (event.data == expectedResponse) {
    434              ok(true, `Received response ${expectedResponse}`);
    435              content.onmessage = null;
    436              r();
    437            }
    438          };
    439        });
    440      };
    441    }
    442  );
    443 }
    444 
    445 function waitUntilActiveMediaSessionChanged() {
    446  return BrowserUtils.promiseObserved("active-media-session-changed");
    447 }
    448 
    449 function ensureControllerIsPlaying(controller) {
    450  return new Promise(r => {
    451    if (controller.isPlaying) {
    452      r();
    453      return;
    454    }
    455    controller.onplaybackstatechange = () => {
    456      if (controller.isPlaying) {
    457        r();
    458      }
    459    };
    460  });
    461 }