diff options
Diffstat (limited to 'browser/extensions/webcompat/shims/spotify-embed.js')
-rw-r--r-- | browser/extensions/webcompat/shims/spotify-embed.js | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/shims/spotify-embed.js b/browser/extensions/webcompat/shims/spotify-embed.js new file mode 100644 index 0000000000..62ad05b725 --- /dev/null +++ b/browser/extensions/webcompat/shims/spotify-embed.js @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals exportFunction */ + +"use strict"; + +/** + * Spotify embeds default to "track preview mode". They require first-party + * storage access in order to detect the login status and allow the user to play + * the whole song or add it to their library. + * Upon clicking the "play" button in the preview view this shim attempts to get + * storage access and on success, reloads the frame and plays the full track. + * This only works if the user is already logged in to Spotify in the + * first-party context. + */ + +const AUTOPLAY_FLAG = "shimPlayAfterStorageAccess"; +const SELECTOR_PREVIEW_PLAY = 'div[data-testid="preview-play-pause"] > button'; +const SELECTOR_FULL_PLAY = 'button[data-testid="play-pause-button"]'; + +/** + * Promise-wrapper around DOMContentLoaded event. + */ +function waitForDOMContentLoaded() { + return new Promise(resolve => { + window.addEventListener("DOMContentLoaded", resolve, { once: true }); + }); +} + +/** + * Listener for the preview playback button which requests storage access and + * reloads the page. + */ +function previewPlayButtonListener(event) { + const { target, isTrusted } = event; + if (!isTrusted) { + return; + } + + const button = target.closest("button"); + if (!button) { + return; + } + + // Filter for the preview playback button. This won't match the full + // playback button that is shown when the user is logged in. + if (!button.matches(SELECTOR_PREVIEW_PLAY)) { + return; + } + + // The storage access request below runs async so playback won't start + // immediately. Mitigate this UX issue by updating the clicked element's + // style so the user gets some immediate feedback. + button.style.opacity = 0.5; + event.stopPropagation(); + event.preventDefault(); + + console.debug("Requesting storage access.", location.origin); + document + .requestStorageAccess() + // When storage access is granted, reload the frame for the embedded + // player to detect the login state and give us full playback + // capabilities. + .then(() => { + // Use a flag to indicate that we want to click play after reload. + // This is so the user does not have to click play twice. + sessionStorage.setItem(AUTOPLAY_FLAG, "true"); + console.debug("Reloading after storage access grant."); + location.reload(); + }) + // If the user denies the storage access prompt we can't use the login + // state. Attempt start preview playback instead. + .catch(() => { + button.click(); + }) + // Reset button style for both success and error case. + .finally(() => { + button.style.opacity = 1.0; + }); +} + +/** + * Attempt to start (full) playback. Waits for the play button to appear and + * become ready. + */ +async function startFullPlayback() { + // Wait for DOMContentLoaded before looking for the playback button. + await waitForDOMContentLoaded(); + + let numTries = 0; + let intervalId = setInterval(() => { + try { + document.querySelector(SELECTOR_FULL_PLAY).click(); + clearInterval(intervalId); + console.debug("Clicked play after storage access grant."); + } catch (e) {} + numTries++; + + if (numTries >= 50) { + console.debug("Can not start playback. Giving up."); + clearInterval(intervalId); + } + }, 200); +} + +(async () => { + // Only run the shim for embedded iframes. + if (window.top == window) { + return; + } + + console.warn( + `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.` + ); + + // Already requested storage access before the reload, trigger playback. + if (sessionStorage.getItem(AUTOPLAY_FLAG) == "true") { + sessionStorage.removeItem(AUTOPLAY_FLAG); + + await startFullPlayback(); + return; + } + + // Wait for the user to click the preview play button. If the player has + // already loaded the full version, this method will do nothing. + document.documentElement.addEventListener( + "click", + previewPlayButtonListener, + { capture: true } + ); +})(); |