tor-browser

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

spotify-embed.js (4269B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* globals exportFunction */
      6 
      7 "use strict";
      8 
      9 /**
     10 * Spotify embeds default to "track preview mode". They require first-party
     11 * storage access in order to detect the login status and allow the user to play
     12 * the whole song or add it to their library.
     13 * Upon clicking the "play" button in the preview view this shim attempts to get
     14 * storage access and on success, reloads the frame and plays the full track.
     15 * This only works if the user is already logged in to Spotify in the
     16 * first-party context.
     17 */
     18 
     19 const AUTOPLAY_FLAG = "shimPlayAfterStorageAccess";
     20 const SELECTOR_PREVIEW_PLAY = 'div[data-testid="preview-play-pause"] > button';
     21 const SELECTOR_FULL_PLAY = 'button[data-testid="play-pause-button"]';
     22 
     23 /**
     24 * Promise-wrapper around DOMContentLoaded event.
     25 */
     26 function waitForDOMContentLoaded() {
     27  return new Promise(resolve => {
     28    window.addEventListener("DOMContentLoaded", resolve, { once: true });
     29  });
     30 }
     31 
     32 /**
     33 * Listener for the preview playback button which requests storage access and
     34 * reloads the page.
     35 */
     36 function previewPlayButtonListener(event) {
     37  const { target, isTrusted } = event;
     38  if (!isTrusted) {
     39    return;
     40  }
     41 
     42  const button = target.closest("button");
     43  if (!button) {
     44    return;
     45  }
     46 
     47  // Filter for the preview playback button. This won't match the full
     48  // playback button that is shown when the user is logged in.
     49  if (!button.matches(SELECTOR_PREVIEW_PLAY)) {
     50    return;
     51  }
     52 
     53  // The storage access request below runs async so playback won't start
     54  // immediately. Mitigate this UX issue by updating the clicked element's
     55  // style so the user gets some immediate feedback.
     56  button.style.opacity = 0.5;
     57  event.stopPropagation();
     58  event.preventDefault();
     59 
     60  console.debug("Requesting storage access.", location.origin);
     61  document
     62    .requestStorageAccess()
     63    // When storage access is granted, reload the frame for the embedded
     64    // player to detect the login state and give us full playback
     65    // capabilities.
     66    .then(() => {
     67      // Use a flag to indicate that we want to click play after reload.
     68      // This is so the user does not have to click play twice.
     69      sessionStorage.setItem(AUTOPLAY_FLAG, "true");
     70      console.debug("Reloading after storage access grant.");
     71      location.reload();
     72    })
     73    // If the user denies the storage access prompt we can't use the login
     74    // state. Attempt start preview playback instead.
     75    .catch(() => {
     76      button.click();
     77    })
     78    // Reset button style for both success and error case.
     79    .finally(() => {
     80      button.style.opacity = 1.0;
     81    });
     82 }
     83 
     84 /**
     85 * Attempt to start (full) playback. Waits for the play button to appear and
     86 * become ready.
     87 */
     88 async function startFullPlayback() {
     89  await document.requestStorageAccess();
     90 
     91  // Wait for DOMContentLoaded before looking for the playback button.
     92  await waitForDOMContentLoaded();
     93 
     94  let numTries = 0;
     95  let intervalId = setInterval(() => {
     96    try {
     97      document.querySelector(SELECTOR_FULL_PLAY).click();
     98      clearInterval(intervalId);
     99      console.debug("Clicked play after storage access grant.");
    100    } catch (e) {}
    101    numTries++;
    102 
    103    if (numTries >= 50) {
    104      console.debug("Can not start playback. Giving up.");
    105      clearInterval(intervalId);
    106    }
    107  }, 200);
    108 }
    109 
    110 (async () => {
    111  // Only run the shim for embedded iframes.
    112  if (window.top == window) {
    113    return;
    114  }
    115 
    116  console.warn(
    117    `When using the Spotify embedded player, Firefox calls the Storage Access API on behalf of the site. See https://bugzilla.mozilla.org/show_bug.cgi?id=1792395 for details.`
    118  );
    119 
    120  // Already requested storage access before the reload, trigger playback.
    121  if (sessionStorage.getItem(AUTOPLAY_FLAG) == "true") {
    122    sessionStorage.removeItem(AUTOPLAY_FLAG);
    123 
    124    await startFullPlayback();
    125    return;
    126  }
    127 
    128  // Wait for the user to click the preview play button. If the player has
    129  // already loaded the full version, this method will do nothing.
    130  document.documentElement.addEventListener(
    131    "click",
    132    previewPlayButtonListener,
    133    { capture: true }
    134  );
    135 })();