tor-browser

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

head.js (15190B)


      1 /**
      2 * This function would create a new foreround tab and load the url for it. In
      3 * addition, instead of returning a tab element, we return a tab wrapper that
      4 * helps us to automatically detect if the media controller of that tab
      5 * dispatches the first (activated) and the last event (deactivated) correctly.
      6 * @ param url
      7 *         the page url which tab would load
      8 * @ param input window (optional)
      9 *         if it exists, the tab would be created from the input window. If not,
     10 *         then the tab would be created in current window.
     11 * @ param needCheck (optional)
     12 *         it decides if we would perform the check for the first and last event
     13 *         on the media controller. It's default true.
     14 */
     15 async function createLoadedTabWrapper(
     16  url,
     17  { inputWindow = window, needCheck = true } = {}
     18 ) {
     19  class tabWrapper {
     20    constructor(tab, needCheck) {
     21      this._tab = tab;
     22      this._controller = tab.linkedBrowser.browsingContext.mediaController;
     23      this._firstEvent = "";
     24      this._lastEvent = "";
     25      this._events = [
     26        "activated",
     27        "deactivated",
     28        "metadatachange",
     29        "playbackstatechange",
     30        "positionstatechange",
     31        "supportedkeyschange",
     32      ];
     33      this._needCheck = needCheck;
     34      if (this._needCheck) {
     35        this._registerAllEvents();
     36      }
     37    }
     38    _registerAllEvents() {
     39      for (let event of this._events) {
     40        this._controller.addEventListener(event, this._handleEvent.bind(this));
     41      }
     42    }
     43    _unregisterAllEvents() {
     44      for (let event of this._events) {
     45        this._controller.removeEventListener(
     46          event,
     47          this._handleEvent.bind(this)
     48        );
     49      }
     50    }
     51    _handleEvent(event) {
     52      info(`handle event=${event.type}`);
     53      if (this._firstEvent === "") {
     54        this._firstEvent = event.type;
     55      }
     56      this._lastEvent = event.type;
     57    }
     58    get linkedBrowser() {
     59      return this._tab.linkedBrowser;
     60    }
     61    get controller() {
     62      return this._controller;
     63    }
     64    get tabElement() {
     65      return this._tab;
     66    }
     67    async close() {
     68      info(`wait until finishing close tab wrapper`);
     69      const deactivationPromise = this._controller.isActive
     70        ? new Promise(r => (this._controller.ondeactivated = r))
     71        : Promise.resolve();
     72      BrowserTestUtils.removeTab(this._tab);
     73      await deactivationPromise;
     74      if (this._needCheck) {
     75        is(this._firstEvent, "activated", "First event should be 'activated'");
     76        is(
     77          this._lastEvent,
     78          "deactivated",
     79          "Last event should be 'deactivated'"
     80        );
     81        this._unregisterAllEvents();
     82      }
     83    }
     84  }
     85  const browser = inputWindow ? inputWindow.gBrowser : window.gBrowser;
     86  let tab = await BrowserTestUtils.openNewForegroundTab(browser, url);
     87  return new tabWrapper(tab, needCheck);
     88 }
     89 
     90 /**
     91 * Returns a promise that resolves when generated media control keys has
     92 * triggered the main media controller's corresponding method and changes its
     93 * playback state.
     94 *
     95 * @param {string} event
     96 *        The event name of the media control key
     97 * @return {Promise}
     98 *         Resolve when the main controller receives the media control key event
     99 *         and change its playback state.
    100 */
    101 function generateMediaControlKeyEvent(event) {
    102  const playbackStateChanged = waitUntilDisplayedPlaybackChanged();
    103  MediaControlService.generateMediaControlKey(event);
    104  return playbackStateChanged;
    105 }
    106 
    107 /**
    108 * Play the specific media and wait until it plays successfully and the main
    109 * controller has been updated.
    110 *
    111 * @param {tab} tab
    112 *        The tab that contains the media which we would play
    113 * @param {string} elementId
    114 *        The element Id of the media which we would play
    115 * @return {Promise}
    116 *         Resolve when the media has been starting playing and the main
    117 *         controller has been updated.
    118 */
    119 async function playMedia(tab, elementId) {
    120  const playbackStatePromise = waitUntilDisplayedPlaybackChanged();
    121  await SpecialPowers.spawn(tab.linkedBrowser, [elementId], async Id => {
    122    const video = content.document.getElementById(Id);
    123    if (!video) {
    124      ok(false, `can't get the media element!`);
    125    }
    126    ok(
    127      await video.play().then(
    128        _ => true,
    129        _ => false
    130      ),
    131      "video started playing"
    132    );
    133  });
    134  return playbackStatePromise;
    135 }
    136 
    137 /**
    138 * Pause the specific media and wait until it pauses successfully and the main
    139 * controller has been updated.
    140 *
    141 * @param {tab} tab
    142 *        The tab that contains the media which we would pause
    143 * @param {string} elementId
    144 *        The element Id of the media which we would pause
    145 * @return {Promise}
    146 *         Resolve when the media has been paused and the main controller has
    147 *         been updated.
    148 */
    149 function pauseMedia(tab, elementId) {
    150  const pausePromise = SpecialPowers.spawn(
    151    tab.linkedBrowser,
    152    [elementId],
    153    Id => {
    154      const video = content.document.getElementById(Id);
    155      if (!video) {
    156        ok(false, `can't get the media element!`);
    157      }
    158      ok(!video.paused, `video is playing before calling pause`);
    159      video.pause();
    160    }
    161  );
    162  return Promise.all([pausePromise, waitUntilDisplayedPlaybackChanged()]);
    163 }
    164 
    165 /**
    166 * Returns a promise that resolves when the specific media starts playing.
    167 *
    168 * @param {tab} tab
    169 *        The tab that contains the media which we would check
    170 * @param {string} elementId
    171 *        The element Id of the media which we would check
    172 * @return {Promise}
    173 *         Resolve when the media has been starting playing.
    174 */
    175 function checkOrWaitUntilMediaStartedPlaying(tab, elementId) {
    176  return SpecialPowers.spawn(tab.linkedBrowser, [elementId], Id => {
    177    return new Promise(resolve => {
    178      const video = content.document.getElementById(Id);
    179      if (!video) {
    180        ok(false, `can't get the media element!`);
    181      }
    182      if (!video.paused) {
    183        ok(true, `media started playing`);
    184        resolve();
    185      } else {
    186        info(`wait until media starts playing`);
    187        video.onplaying = () => {
    188          video.onplaying = null;
    189          ok(true, `media started playing`);
    190          resolve();
    191        };
    192      }
    193    });
    194  });
    195 }
    196 
    197 /**
    198 * Set the playback rate on a media element.
    199 *
    200 * @param {tab} tab
    201 *        The tab that contains the media which we would check
    202 * @param {string} elementId
    203 *        The element Id of the media which we would check
    204 * @param {number} rate
    205 *        The playback rate to set
    206 * @return {Promise}
    207 *         Resolve when the playback rate has been set
    208 */
    209 function setPlaybackRate(tab, elementId, rate) {
    210  return SpecialPowers.spawn(
    211    tab.linkedBrowser,
    212    [elementId, rate],
    213    (Id, rate) => {
    214      const video = content.document.getElementById(Id);
    215      if (!video) {
    216        ok(false, `can't get the media element!`);
    217      }
    218      video.playbackRate = rate;
    219    }
    220  );
    221 }
    222 
    223 /**
    224 * Set the time on a media element.
    225 *
    226 * @param {tab} tab
    227 *        The tab that contains the media which we would check
    228 * @param {string} elementId
    229 *        The element Id of the media which we would check
    230 * @param {number} currentTime
    231 *        The time to set
    232 * @return {Promise}
    233 *         Resolve when the time has been set
    234 */
    235 function setCurrentTime(tab, elementId, currentTime) {
    236  return SpecialPowers.spawn(
    237    tab.linkedBrowser,
    238    [elementId, currentTime],
    239    (Id, currentTime) => {
    240      const video = content.document.getElementById(Id);
    241      if (!video) {
    242        ok(false, `can't get the media element!`);
    243      }
    244      video.currentTime = currentTime;
    245    }
    246  );
    247 }
    248 
    249 /**
    250 * Returns a promise that resolves when the specific media stops playing.
    251 *
    252 * @param {tab} tab
    253 *        The tab that contains the media which we would check
    254 * @param {string} elementId
    255 *        The element Id of the media which we would check
    256 * @return {Promise}
    257 *         Resolve when the media has been stopped playing.
    258 */
    259 function checkOrWaitUntilMediaStoppedPlaying(tab, elementId) {
    260  return SpecialPowers.spawn(tab.linkedBrowser, [elementId], Id => {
    261    return new Promise(resolve => {
    262      const video = content.document.getElementById(Id);
    263      if (!video) {
    264        ok(false, `can't get the media element!`);
    265      }
    266      if (video.paused) {
    267        ok(true, `media stopped playing`);
    268        resolve();
    269      } else {
    270        info(`wait until media stops playing`);
    271        video.onpause = () => {
    272          video.onpause = null;
    273          ok(true, `media stopped playing`);
    274          resolve();
    275        };
    276      }
    277    });
    278  });
    279 }
    280 
    281 /**
    282 * Check if the active metadata is empty.
    283 */
    284 function isCurrentMetadataEmpty() {
    285  const current = MediaControlService.getCurrentActiveMediaMetadata();
    286  is(current.title, "", `current title should be empty`);
    287  is(current.artist, "", `current title should be empty`);
    288  is(current.album, "", `current album should be empty`);
    289  is(current.artwork.length, 0, `current artwork should be empty`);
    290 }
    291 
    292 /**
    293 * Check if the active metadata is equal to the given metadata.artwork
    294 *
    295 * @param {object} metadata
    296 *        The metadata that would be compared with the active metadata
    297 */
    298 function isCurrentMetadataEqualTo(metadata) {
    299  const current = MediaControlService.getCurrentActiveMediaMetadata();
    300  is(
    301    current.title,
    302    metadata.title,
    303    `tile '${current.title}' is equal to ${metadata.title}`
    304  );
    305  is(
    306    current.artist,
    307    metadata.artist,
    308    `artist '${current.artist}' is equal to ${metadata.artist}`
    309  );
    310  is(
    311    current.album,
    312    metadata.album,
    313    `album '${current.album}' is equal to ${metadata.album}`
    314  );
    315  is(
    316    current.artwork.length,
    317    metadata.artwork.length,
    318    `artwork length '${current.artwork.length}' is equal to ${metadata.artwork.length}`
    319  );
    320  for (let idx = 0; idx < metadata.artwork.length; idx++) {
    321    // the current src we got would be a completed path of the image, so we do
    322    // not check if they are equal, we check if the current src includes the
    323    // metadata's file name. Eg. "http://foo/bar.jpg" v.s. "bar.jpg"
    324    ok(
    325      current.artwork[idx].src.includes(metadata.artwork[idx].src),
    326      `artwork src '${current.artwork[idx].src}' includes ${metadata.artwork[idx].src}`
    327    );
    328    is(
    329      current.artwork[idx].sizes,
    330      metadata.artwork[idx].sizes,
    331      `artwork sizes '${current.artwork[idx].sizes}' is equal to ${metadata.artwork[idx].sizes}`
    332    );
    333    is(
    334      current.artwork[idx].type,
    335      metadata.artwork[idx].type,
    336      `artwork type '${current.artwork[idx].type}' is equal to ${metadata.artwork[idx].type}`
    337    );
    338  }
    339 }
    340 
    341 /**
    342 * Check if the given tab is using the default metadata. If the tab is being
    343 * used in the private browsing mode, `isPrivateBrowsing` should be definded in
    344 * the `options`.
    345 */
    346 async function isGivenTabUsingDefaultMetadata(tab, options = {}) {
    347  const localization = new Localization([
    348    "branding/brand.ftl",
    349    "dom/media.ftl",
    350  ]);
    351  const fallbackTitle = await localization.formatValue(
    352    "mediastatus-fallback-title"
    353  );
    354  ok(fallbackTitle.length, "l10n fallback title is not empty");
    355 
    356  const metadata =
    357    tab.linkedBrowser.browsingContext.mediaController.getMetadata();
    358 
    359  await SpecialPowers.spawn(
    360    tab.linkedBrowser,
    361    [metadata.title, fallbackTitle, options.isPrivateBrowsing],
    362    (title, fallbackTitle, isPrivateBrowsing) => {
    363      if (isPrivateBrowsing || !content.document.title.length) {
    364        is(title, fallbackTitle, "Using a generic default fallback title");
    365      } else {
    366        is(
    367          title,
    368          content.document.title,
    369          "Using website title as a default title"
    370        );
    371      }
    372    }
    373  );
    374  is(metadata.artwork.length, 1, "Default metada contains one artwork");
    375  ok(
    376    metadata.artwork[0].src.includes("defaultFavicon.svg"),
    377    "Using default favicon as a default art work"
    378  );
    379 }
    380 
    381 /**
    382 * Wait until the main media controller changes its playback state, we would
    383 * observe that by listening for `media-displayed-playback-changed`
    384 * notification.
    385 *
    386 * @return {Promise}
    387 *         Resolve when observing `media-displayed-playback-changed`
    388 */
    389 function waitUntilDisplayedPlaybackChanged() {
    390  return BrowserUtils.promiseObserved("media-displayed-playback-changed");
    391 }
    392 
    393 /**
    394 * Wait until the metadata that would be displayed on the virtual control
    395 * interface changes. we would observe that by listening for
    396 * `media-displayed-metadata-changed` notification.
    397 *
    398 * @return {Promise}
    399 *         Resolve when observing `media-displayed-metadata-changed`
    400 */
    401 function waitUntilDisplayedMetadataChanged() {
    402  return BrowserUtils.promiseObserved("media-displayed-metadata-changed");
    403 }
    404 
    405 /**
    406 * Wait until the main media controller has been changed, we would observe that
    407 * by listening for the `main-media-controller-changed` notification.
    408 *
    409 * @return {Promise}
    410 *         Resolve when observing `main-media-controller-changed`
    411 */
    412 function waitUntilMainMediaControllerChanged() {
    413  return BrowserUtils.promiseObserved("main-media-controller-changed");
    414 }
    415 
    416 /**
    417 * Wait until any media controller updates its metadata even if it's not the
    418 * main controller. The difference between this function and
    419 * `waitUntilDisplayedMetadataChanged()` is that the changed metadata might come
    420 * from non-main controller so it won't be show on the virtual control
    421 * interface. we would observe that by listening for
    422 * `media-session-controller-metadata-changed` notification.
    423 *
    424 * @return {Promise}
    425 *         Resolve when observing `media-session-controller-metadata-changed`
    426 */
    427 function waitUntilControllerMetadataChanged() {
    428  return BrowserUtils.promiseObserved(
    429    "media-session-controller-metadata-changed"
    430  );
    431 }
    432 
    433 /**
    434 * Wait until media controller amount changes, we would observe that by
    435 * listening for `media-controller-amount-changed` notification.
    436 *
    437 * @return {Promise}
    438 *         Resolve when observing `media-controller-amount-changed`
    439 */
    440 function waitUntilMediaControllerAmountChanged() {
    441  return BrowserUtils.promiseObserved("media-controller-amount-changed");
    442 }
    443 
    444 /**
    445 * Wait until the position state that would be displayed on the virtual control
    446 * interface changes. we would observe that by listening for
    447 * `media-position-state-changed` notification.
    448 *
    449 * @return {Promise}
    450 *         Resolve when observing `media-position-state-changed`
    451 */
    452 function waitUntilPositionStateChanged() {
    453  return BrowserUtils.promiseObserved("media-position-state-changed");
    454 }
    455 
    456 /**
    457 * check if the media controll from given tab is active. If not, return a
    458 * promise and resolve it when controller become active.
    459 */
    460 async function checkOrWaitUntilControllerBecomeActive(tab) {
    461  const controller = tab.linkedBrowser.browsingContext.mediaController;
    462  if (controller.isActive) {
    463    return;
    464  }
    465  await new Promise(r => (controller.onactivated = r));
    466 }
    467 
    468 /**
    469 * Logs all `positionstatechange` events in a tab.
    470 */
    471 function logPositionStateChangeEvents(tab) {
    472  tab.linkedBrowser.browsingContext.mediaController.addEventListener(
    473    "positionstatechange",
    474    event =>
    475      info(
    476        `got position state: ${JSON.stringify({
    477          duration: event.duration,
    478          playbackRate: event.playbackRate,
    479          position: event.position,
    480        })}`
    481      )
    482  );
    483 }