From 5b61a75c9b5715db7ce3c76c496edc2ac601d249 Mon Sep 17 00:00:00 2001 From: Fabian Foerg <3429782+faf0@users.noreply.github.com> Date: Sat, 20 Mar 2021 12:08:55 -0400 Subject: [PATCH 1/3] Fix issue #7 and clean up code Fixes https://github.com/intOrfloat/spotitySpeedExtension/issues/7 Cleans up code and only only sets playback speed on certain events: https://github.com/intOrfloat/spotitySpeedExtension/pull/6 --- content-script.js | 70 ++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/content-script.js b/content-script.js index 02e5ca9..0982f20 100644 --- a/content-script.js +++ b/content-script.js @@ -14,6 +14,10 @@ var code = ` var base = document.createElement; /* A backup reference to the browser's original document.createElement */ var VideoElementsMade = []; /* Array of video/audio elements made by spotify's scripts */ + + function debugLog(text) { + console.log(text) + } /* Replacing the DOM's original reference to the browser's createElement function */ document.createElement = function(message) { @@ -22,7 +26,7 @@ var code = ` /* we check the first argument sent to us Examp. document.createElement('video') would have message = 'video' */ /* ignores the many document.createElement('div'), document.createElement('nav'), ect... */ - if(message == 'video' || message == 'audio'){ /* Checking if spotify scripts are making a video or audio element */ + if(message === 'video' || message === 'audio'){ /* Checking if spotify scripts are making a video or audio element */ VideoElementsMade.push(element); /* Add a reference to the element in our array. Arrays hold references not copies by default in javascript. */ } return element /* return the element and complete the loop so the page is allowed to be made */ @@ -33,10 +37,13 @@ var code = ` function getStoredSpeed(){ /* Gets stored speed between refreshes*/ return localStorage.getItem('speed'); } + + // locally cache the speed, so getStoredSpeed does not need to be called repetitively var lastSpeed = getStoredSpeed() || 1.0; /* if stored speed is null make lastSpeed 1.0 */ function setStoredSpeed(value){ /* Sets variable in the site's cookie along-side spotify's stuff */ localStorage.setItem('speed',value); + lastSpeed = value; } /* Building our playback speed input element */ @@ -48,49 +55,62 @@ var code = ` + 'width: 45px;' + 'margin: 5px;'; input.value = lastSpeed * 100; - input.oninput = function(e){ /* What happens when we change the number in our input box element */ - validateAndChangeSpeed(); /* We call our function */ + input.onkeypress = function(e){ /* What happens when we change the number in our input box element */ + if (e.code === "Enter") { + validateAndChangeSpeed(); + } + }; + input.onblur = function() { + // "onfocusout" event not working + validateAndChangeSpeed(); }; function validateAndChangeSpeed(value){ - var val = parseFloat( value || (input.value / 100)); /* val must be in format 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */ - if(!isNaN(val)){ /* check if val is a number */ - changeSpeed(val); + var val = parseFloat( value || (input.value / 100)); + if (!isNaN(val) && (val !== lastSpeed)) { + /* only change if input is valid and it changed */ + setStoredSpeed(val); + videosChangeSpeed(val); } } - function changeSpeed(val) { + function videosChangeSpeed(val) { for(var i = 0; i < VideoElementsMade.length; i++){ /* change speed for all elements found (i havent seen this be more than 1 but you never know) */ - VideoElementsMade[i].playbackRate = val; /* set the playback rate here */ - if(val != lastSpeed){ /* update the lastSpeed if the speed actually changed */ - lastSpeed = val; - setStoredSpeed(val); + /* val is clamped to range 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */ + if (VideoElementsMade[i].playbackRate !== val) { + debugLog("changing playback rate from " + VideoElementsMade[i].playbackRate + " to " + val) } + VideoElementsMade[i].playbackRate = val; /* set the playback rate here */ + VideoElementsMade[i].defaultPlaybackRate = val; } } - function timeout() { /* This function is called by itself over and over */ - if(document.getElementById('speed-extension-input') == null) /* check if our input element doesnt exist */ - { - try { - document.getElementsByClassName('now-playing-bar__right')[0].appendChild (input); /* make our input exist on page */ - }catch{ - setTimeout(timeout, 100);/*now-playing-bar__right doesnt exist yet so lets try again in 100ms*/ - return; - } + function addSpeedInput() { /* adds speed input next to volume bar */ + debugLog("Adding speed input"); + try { + document.getElementsByClassName('volume-bar')[0].appendChild(input); /* make our input exist on page */ + debugLog("Added speed input"); + }catch{ + setTimeout(addSpeedInput, 100);/*volume-bar doesnt exist yet so lets try again in 100ms*/ + return; } + } + + addSpeedInput(); + + function ensureSpeedNotChanged() { + /* sometimes playbackRate is set back to 1.0 by spotify's code so timeout just ensures it goes the speed the user desires */ setTimeout(function () { /* setTimeout is a delayed call(500 milliseconds) to the code below */ try { - validateAndChangeSpeed(lastSpeed); /* this is in a try/catch because if an error happens timeout wouldnt be called again. */ + /* this is in a try/catch because if an error happens timeout wouldnt be called again. */ + validateAndChangeSpeed(lastSpeed); }catch{ } - timeout(); /* call timeout again which starts the loop and eventually it will come back here */ + ensureSpeedNotChanged(); /* call timeout again which starts the loop and eventually it will come back here */ }, 500); /* 500ms */ } - - timeout(); /* starts the loop to check and create our inputbox and to set the playback speed without having to mess with input box(by refreshing and having it load from cookie) */ - /* sometimes playbackRate is set back to 1.0 by spotify's code so timeout just ensures it goes the speed the user desires */ + ensureSpeedNotChanged(); };`; /* ======== End of code string literal ======== */ var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */ From ddb0470b90c96bb695b6e871ecf6be17b3e12e94 Mon Sep 17 00:00:00 2001 From: Fabian Foerg <3429782+faf0@users.noreply.github.com> Date: Sat, 20 Mar 2021 15:23:28 -0400 Subject: [PATCH 2/3] Update content-script.js Retry adding the input field only every second --- content-script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content-script.js b/content-script.js index 0982f20..70ab15f 100644 --- a/content-script.js +++ b/content-script.js @@ -91,7 +91,7 @@ var code = ` document.getElementsByClassName('volume-bar')[0].appendChild(input); /* make our input exist on page */ debugLog("Added speed input"); }catch{ - setTimeout(addSpeedInput, 100);/*volume-bar doesnt exist yet so lets try again in 100ms*/ + setTimeout(addSpeedInput, 1000);/*volume-bar doesnt exist yet so lets try again in 1 second*/ return; } } From 7ec2f825bb0522314aa400680845a5daea4cb8bf Mon Sep 17 00:00:00 2001 From: jamesEmerson112 <36806380+jamesEmerson112@users.noreply.github.com> Date: Sat, 28 May 2022 13:51:16 -0700 Subject: [PATCH 3/3] Upgraded Manifest, updated content-script Google Chrome v. 102.0.5002.63 (Official Build) (64-bit) OS: Window 10 Cause: Manifest 2 is (soon) deprecated => content security issues preventing code to be injected Solution: 1. Upraded Manifest 2 -> Manifest 3 with some adjustment 2. Split content-script into 2: content-script context and inject codes 3. Adjust inject codes to be compatible with content security protocol 4. Debugged and adjusted content-script for the inject code (inject.js) --- content-script.js | 186 +++++++++++++++++++++++----------------------- inject.js | 27 +++++++ manifest.json | 29 ++++++-- 3 files changed, 141 insertions(+), 101 deletions(-) create mode 100644 inject.js diff --git a/content-script.js b/content-script.js index 70ab15f..ac7f43d 100644 --- a/content-script.js +++ b/content-script.js @@ -11,110 +11,110 @@ 3. The timeout is just an added assurance that the playbackspeed input element is created and that the speed is changed to the stored speed from previous sessions */ /* ======== Start of code string literal ======== */ -var code = ` - var base = document.createElement; /* A backup reference to the browser's original document.createElement */ - var VideoElementsMade = []; /* Array of video/audio elements made by spotify's scripts */ +// console.log("outside of the code") +// var code = ` +// console.log("inside the code"); +var base = document.createElement; /* A backup reference to the browser's original document.createElement */ +var VideoElementsMade = []; /* Array of video/audio elements made by spotify's scripts */ +function debugLog(text) { + console.log(text) +} - function debugLog(text) { - console.log(text) - } +/* Replacing the DOM's original reference to the browser's createElement function */ +document.createElement = function(message) { + /* base.apply is calling the backup reference of createElement with the arguments sent to our function and assigning it to our variable named element */ + var element = base.apply(this, arguments); - /* Replacing the DOM's original reference to the browser's createElement function */ - document.createElement = function(message) { - /* base.apply is calling the backup reference of createElement with the arguments sent to our function and assigning it to our variable named element */ - var element = base.apply(this, arguments); - - /* we check the first argument sent to us Examp. document.createElement('video') would have message = 'video' */ - /* ignores the many document.createElement('div'), document.createElement('nav'), ect... */ - if(message === 'video' || message === 'audio'){ /* Checking if spotify scripts are making a video or audio element */ - VideoElementsMade.push(element); /* Add a reference to the element in our array. Arrays hold references not copies by default in javascript. */ + /* we check the first argument sent to us Examp. document.createElement('video') would have message = 'video' */ + /* ignores the many document.createElement('div'), document.createElement('nav'), ect... */ + if(message === 'video' || message === 'audio'){ /* Checking if spotify scripts are making a video or audio element */ + VideoElementsMade.push(element); /* Add a reference to the element in our array. Arrays hold references not copies by default in javascript. */ + } + return element /* return the element and complete the loop so the page is allowed to be made */ +}; + +/* When the page is loaded completely... */ +window.onload = function() { + function getStoredSpeed(){ /* Gets stored speed between refreshes*/ + return localStorage.getItem('speed'); + } + // locally cache the speed, so getStoredSpeed does not need to be called repetitively + var lastSpeed = getStoredSpeed() || 1.0; /* if stored speed is null make lastSpeed 1.0 */ + + function setStoredSpeed(value){ /* Sets variable in the site's cookie along-side spotify's stuff */ + localStorage.setItem('speed',value); + lastSpeed = value; + } + /* Building our playback speed input element */ + var input = document.createElement('input'); + input.type = 'number'; + input.id = 'speed-extension-input'; + input.style = 'background-color: #08080859;' + + 'border: #823333;' + + 'width: 45px;' + + 'margin: 5px;'; + input.value = lastSpeed * 100; + input.onkeypress = function(e){ /* What happens when we change the number in our input box element */ + if (e.code === "Enter") { + validateAndChangeSpeed(); } - return element /* return the element and complete the loop so the page is allowed to be made */ + }; + input.onblur = function() { + // "onfocusout" event not working + validateAndChangeSpeed(); }; - /* When the page is loaded completely... */ - window.onload = function() { - function getStoredSpeed(){ /* Gets stored speed between refreshes*/ - return localStorage.getItem('speed'); + function validateAndChangeSpeed(value){ + var val = parseFloat( value || (input.value / 100)); + if (!isNaN(val) && (val !== lastSpeed)) { + /* only change if input is valid and it changed */ + setStoredSpeed(val); + videosChangeSpeed(val); } - - // locally cache the speed, so getStoredSpeed does not need to be called repetitively - var lastSpeed = getStoredSpeed() || 1.0; /* if stored speed is null make lastSpeed 1.0 */ + } - function setStoredSpeed(value){ /* Sets variable in the site's cookie along-side spotify's stuff */ - localStorage.setItem('speed',value); - lastSpeed = value; - } - - /* Building our playback speed input element */ - var input = document.createElement('input'); - input.type = 'number'; - input.id = 'speed-extension-input'; - input.style = 'background-color: #08080859;' - + 'border: #823333;' - + 'width: 45px;' - + 'margin: 5px;'; - input.value = lastSpeed * 100; - input.onkeypress = function(e){ /* What happens when we change the number in our input box element */ - if (e.code === "Enter") { - validateAndChangeSpeed(); - } - }; - input.onblur = function() { - // "onfocusout" event not working - validateAndChangeSpeed(); - }; - - function validateAndChangeSpeed(value){ - var val = parseFloat( value || (input.value / 100)); - if (!isNaN(val) && (val !== lastSpeed)) { - /* only change if input is valid and it changed */ - setStoredSpeed(val); - videosChangeSpeed(val); + function videosChangeSpeed(val) { + for(var i = 0; i < VideoElementsMade.length; i++){ /* change speed for all elements found (i havent seen this be more than 1 but you never know) */ + /* val is clamped to range 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */ + if (VideoElementsMade[i].playbackRate !== val) { + debugLog("changing playback rate from " + VideoElementsMade[i].playbackRate + " to " + val) } + VideoElementsMade[i].playbackRate = val; /* set the playback rate here */ + VideoElementsMade[i].defaultPlaybackRate = val; } - - function videosChangeSpeed(val) { - for(var i = 0; i < VideoElementsMade.length; i++){ /* change speed for all elements found (i havent seen this be more than 1 but you never know) */ - /* val is clamped to range 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */ - if (VideoElementsMade[i].playbackRate !== val) { - debugLog("changing playback rate from " + VideoElementsMade[i].playbackRate + " to " + val) - } - VideoElementsMade[i].playbackRate = val; /* set the playback rate here */ - VideoElementsMade[i].defaultPlaybackRate = val; - } + } + + function addSpeedInput() { /* adds speed input next to volume bar */ + debugLog("Adding speed input"); + try { + document.getElementsByClassName('volume-bar')[0].appendChild(input); /* make our input exist on page */ + debugLog("Added speed input"); + }catch{ + setTimeout(addSpeedInput, 1000);/*volume-bar doesnt exist yet so lets try again in 1 second*/ + return; } - - function addSpeedInput() { /* adds speed input next to volume bar */ - debugLog("Adding speed input"); + } + + addSpeedInput(); + function ensureSpeedNotChanged() { + /* sometimes playbackRate is set back to 1.0 by spotify's code so timeout just ensures it goes the speed the user desires */ + setTimeout(function () { /* setTimeout is a delayed call(500 milliseconds) to the code below */ try { - document.getElementsByClassName('volume-bar')[0].appendChild(input); /* make our input exist on page */ - debugLog("Added speed input"); + /* this is in a try/catch because if an error happens timeout wouldnt be called again. */ + validateAndChangeSpeed(lastSpeed); }catch{ - setTimeout(addSpeedInput, 1000);/*volume-bar doesnt exist yet so lets try again in 1 second*/ - return; + } - } - - addSpeedInput(); - - function ensureSpeedNotChanged() { - /* sometimes playbackRate is set back to 1.0 by spotify's code so timeout just ensures it goes the speed the user desires */ - setTimeout(function () { /* setTimeout is a delayed call(500 milliseconds) to the code below */ - try { - /* this is in a try/catch because if an error happens timeout wouldnt be called again. */ - validateAndChangeSpeed(lastSpeed); - }catch{ - - } - ensureSpeedNotChanged(); /* call timeout again which starts the loop and eventually it will come back here */ - }, 500); /* 500ms */ - } - ensureSpeedNotChanged(); - };`; + ensureSpeedNotChanged(); /* call timeout again which starts the loop and eventually it will come back here */ + }, 500); /* 500ms */ + } + ensureSpeedNotChanged(); +} +// };`; /* ======== End of code string literal ======== */ -var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */ -script.textContent = code; /* insert our code as the contents of the script */ -document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */ -(document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */ -script.remove(); /* idk why i do this */ +// Moved to inject.js +// var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */ +// script.textContent = code; /* insert our code as the contents of the script */ +// document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */ +// (document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */ +// script.remove(); /* idk why i do this */ \ No newline at end of file diff --git a/inject.js b/inject.js new file mode 100644 index 0000000..1fd756f --- /dev/null +++ b/inject.js @@ -0,0 +1,27 @@ +// inject.js +// var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */ +// script.textContent = code; /* insert our code as the contents of the script */ +// document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */ +// (document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */ +// script.remove(); /* idk why i do this */ + +const nullthrows = (v) => { + if (v == null) throw new Error("it's a null"); + return v; +} + +function injectCode(src) { + const script = document.createElement('script'); + script.src = src; + script.onload = function() { + console.log("chrome extension injected"); + this.remove(); + }; + + // This script runs before the element is created, + // so we add the script to instead. + nullthrows(document.head || document.documentElement).appendChild(script); +} + + +injectCode(chrome.runtime.getURL('/content-script.js')); \ No newline at end of file diff --git a/manifest.json b/manifest.json index ef7e4b4..ceeba4a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,18 +1,31 @@ { "name": "Spotify Playback Speed Access", "description": "Adds ability to change song speed with an input next to the volume slider.", - "version": "1.6", - "permissions": [ - "storage" + "version": "1.7", + "manifest_version": 3, + "host_permissions":[ + "https://open.spotify.com/*" ], - "icons": { "16": "speed16.png", - "48": "speed48.png", - "128": "speed128.png" }, "content_scripts": [ { "matches": ["https://open.spotify.com/*"], - "js": ["content-script.js"] + "run_at": "document_start", + "js": ["inject.js"] + } + ], + "web_accessible_resources": [ + { + "resources": ["content-script.js"], + "matches": ["https://open.spotify.com/*"] } ], - "manifest_version": 2 + "permissions": [ + "storage", + "activeTab" + ], + + "icons": { "16": "speed16.png", + "48": "speed48.png", + "128": "speed128.png" } + }