From b61dbc4a577a3b79fcfb0b38e1352b975a2de23d Mon Sep 17 00:00:00 2001 From: Huey Date: Wed, 16 Nov 2022 23:03:15 +0800 Subject: [PATCH 1/7] feat: inject content script to handle follows from remote v4 Mastodon instances --- src/background/modules/AutoRemoteFollow.js | 13 +++ src/content_script/mastodonInject.js | 100 +++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/content_script/mastodonInject.js diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index 06bf1b9..bc5662a 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -144,6 +144,15 @@ function getInteractionType(url) { return [null, null]; } +/** + * Handles changes to the URL of a tab + */ +function onTabUpdate() { + browser.tabs.executeScript(null, { + file: `/content_script/mastodonInject.js` + }) +} + /** * Init AutoRemoteFollower module. * @@ -154,6 +163,10 @@ function init() { NetworkTools.webRequestListen(["http://*/*", "https://*/*"], "onBeforeRequest", (requestDetails) => { return handleWebRequest(requestDetails).catch(handleError).catch(console.error); }); + + browser.tabs.onUpdated.addListener(onTabUpdate, { + properties: ["url"] + }); } init(); diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js new file mode 100644 index 0000000..6d219d3 --- /dev/null +++ b/src/content_script/mastodonInject.js @@ -0,0 +1,100 @@ +/** + * @typedef {Object} VersionNumber + * @property {string} major + * @property {string} minor + * @property {string} patch + */ + +/** + * parses a versionNumber string + * @returns {VersionNumber} + */ +function parseVersionNumber() { + const versionElement = document.querySelector(`.link-footer`).textContent + const versionNumber = versionElement.match(/v?(?\d+)\.(?\d+)\.(?\d+)$/) + return versionNumber.groups +} + + +/** + * Replacement onClick handler for Follow button + * @param {Event} event + */ +function onClickFollow (event) { + event.stopPropagation() + event.preventDefault() + const username = window.location.pathname.split(`/`).slice(-1)[0] + // activate AutoRemoteFollow + window.open(`/users/${username}/remote_follow`, `_blank`) +} + +/** + * wait for element to appear + * @param {string} selector + * @param {number} timeout + */ +function waitForElement (selector, timeout) { + return new Promise((resolve, reject) => { + function getElement() { + return document.querySelector(selector) + } + + const element = getElement() + if(element){ + return resolve(element) + } + + const observer = new MutationObserver(() => { + const element = getElement() + if(element){ + resolve(element) + observer.disconnect() + } + }) + + observer.observe(document.body, { + childList: true, + subtree: true + }) + + window.setTimeout(() => { + reject() + }, timeout) + }) +} + +/** + * Inject replacement onClick handler for Follow button + */ +async function injectFollowButton () { + try { + const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000) + followButton.addEventListener(`click`, onClickFollow) + } catch { + // Follow button failed to appear + } +} + +async function init () { + let versionNumber + + try { + const initialStateObject = JSON.parse(document.getElementById(`initial-state`).innerHTML) + const version = initialStateObject?.meta?.version + if(!initialStateObject || !version){ + // not a Mastodon server + return + } + + versionNumber = parseVersionNumber(version) + } catch { + return + } + + + if(Number.parseInt(versionNumber.major) >= 4){ + await injectFollowButton() + } +} + +init() \ No newline at end of file From 9a1f476a0be93f87a87ce9032c1cd7de86cb76cc Mon Sep 17 00:00:00 2001 From: Huey Date: Thu, 17 Nov 2022 09:29:31 +0800 Subject: [PATCH 2/7] chore: fix stylistic issues --- src/background/modules/AutoRemoteFollow.js | 7 +-- src/content_script/mastodonInject.js | 59 +++++++++++----------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index bc5662a..f4ae2f3 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -145,12 +145,13 @@ function getInteractionType(url) { } /** - * Handles changes to the URL of a tab + * Handles changes to the URL of a tab. + * @returns {void} */ function onTabUpdate() { browser.tabs.executeScript(null, { - file: `/content_script/mastodonInject.js` - }) + file: "/content_script/mastodonInject.js" + }); } /** diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 6d219d3..2bcdabd 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -10,9 +10,9 @@ * @returns {VersionNumber} */ function parseVersionNumber() { - const versionElement = document.querySelector(`.link-footer`).textContent - const versionNumber = versionElement.match(/v?(?\d+)\.(?\d+)\.(?\d+)$/) - return versionNumber.groups + const versionElement = document.querySelector(`.link-footer`).textContent; + const versionNumber = versionElement.match(/v?(?\d+)\.(?\d+)\.(?\d+)$/); + return versionNumber.groups; } @@ -20,81 +20,82 @@ function parseVersionNumber() { * Replacement onClick handler for Follow button * @param {Event} event */ -function onClickFollow (event) { - event.stopPropagation() - event.preventDefault() - const username = window.location.pathname.split(`/`).slice(-1)[0] +function onClickFollow(event) { + event.stopPropagation(); + event.preventDefault(); + const username = window.location.pathname.split(`/`).slice(-1)[0]; // activate AutoRemoteFollow - window.open(`/users/${username}/remote_follow`, `_blank`) + window.open(`/users/${username}/remote_follow`, `_blank`); } /** * wait for element to appear * @param {string} selector * @param {number} timeout + * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} from which this was adapted */ -function waitForElement (selector, timeout) { +function waitForElement(selector, timeout) { return new Promise((resolve, reject) => { function getElement() { - return document.querySelector(selector) + return document.querySelector(selector); } - const element = getElement() + const element = getElement(); if(element){ - return resolve(element) + return resolve(element); } const observer = new MutationObserver(() => { - const element = getElement() + const element = getElement(); if(element){ - resolve(element) - observer.disconnect() + resolve(element); + observer.disconnect(); } }) observer.observe(document.body, { childList: true, subtree: true - }) + }); window.setTimeout(() => { - reject() - }, timeout) + reject(); + }, timeout); }) } /** * Inject replacement onClick handler for Follow button */ -async function injectFollowButton () { +async function injectFollowButton() { try { - const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000) - followButton.addEventListener(`click`, onClickFollow) + const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000); + followButton.addEventListener(`click`, onClickFollow); } catch { // Follow button failed to appear } } -async function init () { - let versionNumber +async function init() { + let versionNumber; try { - const initialStateObject = JSON.parse(document.getElementById(`initial-state`).innerHTML) - const version = initialStateObject?.meta?.version + const initialStateObject = JSON.parse(document.getElementById(`initial-state`).innerHTML); + const version = initialStateObject?.meta?.version; if(!initialStateObject || !version){ // not a Mastodon server - return + return; } - versionNumber = parseVersionNumber(version) + versionNumber = parseVersionNumber(version); } catch { return } if(Number.parseInt(versionNumber.major) >= 4){ - await injectFollowButton() + await injectFollowButton(); } } -init() \ No newline at end of file +init(); \ No newline at end of file From f41edcb7564df24cf05c200774d2c62aca11dd7b Mon Sep 17 00:00:00 2001 From: Huey Date: Thu, 17 Nov 2022 09:48:50 +0800 Subject: [PATCH 3/7] fix: remove version-checking code --- src/content_script/mastodonInject.js | 38 ++-------------------------- 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 2bcdabd..5ba390c 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -1,20 +1,4 @@ -/** - * @typedef {Object} VersionNumber - * @property {string} major - * @property {string} minor - * @property {string} patch - */ - -/** - * parses a versionNumber string - * @returns {VersionNumber} - */ -function parseVersionNumber() { - const versionElement = document.querySelector(`.link-footer`).textContent; - const versionNumber = versionElement.match(/v?(?\d+)\.(?\d+)\.(?\d+)$/); - return versionNumber.groups; -} - +"use strict"; /** * Replacement onClick handler for Follow button @@ -77,25 +61,7 @@ async function injectFollowButton() { } async function init() { - let versionNumber; - - try { - const initialStateObject = JSON.parse(document.getElementById(`initial-state`).innerHTML); - const version = initialStateObject?.meta?.version; - if(!initialStateObject || !version){ - // not a Mastodon server - return; - } - - versionNumber = parseVersionNumber(version); - } catch { - return - } - - - if(Number.parseInt(versionNumber.major) >= 4){ - await injectFollowButton(); - } + await injectFollowButton(); } init(); \ No newline at end of file From e9c16cad10711fe81e65b03958fce89a4cacb051 Mon Sep 17 00:00:00 2001 From: Huey Date: Thu, 17 Nov 2022 09:52:59 +0800 Subject: [PATCH 4/7] chore: additional stylistic fixes --- src/content_script/mastodonInject.js | 73 ++++++++++++++-------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 5ba390c..817c8ef 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -5,63 +5,64 @@ * @param {Event} event */ function onClickFollow(event) { - event.stopPropagation(); - event.preventDefault(); - const username = window.location.pathname.split(`/`).slice(-1)[0]; - // activate AutoRemoteFollow - window.open(`/users/${username}/remote_follow`, `_blank`); + event.stopPropagation(); + event.preventDefault(); + const username = window.location.pathname.split(`/`).slice(-1)[0]; + // activate AutoRemoteFollow + window.open(`/users/${username}/remote_follow`, `_blank`); } /** * wait for element to appear * @param {string} selector * @param {number} timeout - * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} from which this was adapted + * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} + * from which this was adapted */ function waitForElement(selector, timeout) { - return new Promise((resolve, reject) => { - function getElement() { - return document.querySelector(selector); - } + return new Promise((resolve, reject) => { + function getElement() { + return document.querySelector(selector); + } - const element = getElement(); - if(element){ - return resolve(element); - } + const element = getElement(); + if(element){ + return resolve(element); + } - const observer = new MutationObserver(() => { - const element = getElement(); - if(element){ - resolve(element); - observer.disconnect(); - } - }) + const observer = new MutationObserver(() => { + const element = getElement(); + if(element){ + resolve(element); + observer.disconnect(); + } + }); - observer.observe(document.body, { - childList: true, - subtree: true - }); + observer.observe(document.body, { + childList: true, + subtree: true + }); - window.setTimeout(() => { - reject(); - }, timeout); - }) + window.setTimeout(() => { + reject(); + }, timeout); + }); } /** * Inject replacement onClick handler for Follow button */ async function injectFollowButton() { - try { - const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000); - followButton.addEventListener(`click`, onClickFollow); - } catch { - // Follow button failed to appear - } + try { + const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000); + followButton.addEventListener(`click`, onClickFollow); + } catch { + // Follow button failed to appear + } } async function init() { - await injectFollowButton(); + await injectFollowButton(); } init(); \ No newline at end of file From 4d5c4a59044536a52924405b9bce71e2f1e6e0ed Mon Sep 17 00:00:00 2001 From: Huey Date: Fri, 18 Nov 2022 12:38:33 +0800 Subject: [PATCH 5/7] fix: formatting and address other review comments --- src/background/modules/AutoRemoteFollow.js | 2 +- src/content_script/mastodonInject.js | 51 +++++++++++++--------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index f4ae2f3..1921bca 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -149,7 +149,7 @@ function getInteractionType(url) { * @returns {void} */ function onTabUpdate() { - browser.tabs.executeScript(null, { + browser.tabs.executeScript({ file: "/content_script/mastodonInject.js" }); } diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 817c8ef..261871b 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -1,41 +1,47 @@ "use strict"; /** - * Replacement onClick handler for Follow button + * Replacement onClick handler for Follow button. * @param {Event} event + * @returns {void} */ function onClickFollow(event) { event.stopPropagation(); event.preventDefault(); - const username = window.location.pathname.split(`/`).slice(-1)[0]; + const username = window.location.pathname.split("/").slice(-1)[0]; // activate AutoRemoteFollow - window.open(`/users/${username}/remote_follow`, `_blank`); + window.open(`/users/${username}/remote_follow`, "_blank"); } /** - * wait for element to appear + * wait for element to appear. * @param {string} selector - * @param {number} timeout + * @param {number} timeoutDuration * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} * from which this was adapted + * @returns {Promise} */ -function waitForElement(selector, timeout) { +function waitForElement(selector, timeoutDuration) { return new Promise((resolve, reject) => { - function getElement() { - return document.querySelector(selector); - } + const getElement = () => document.querySelector(selector); + + const timeout = window.setTimeout(() => { + reject(new Error("waitForElement timed out")); + }, timeoutDuration); const element = getElement(); if(element){ + window.clearTimeout(timeout); return resolve(element); } const observer = new MutationObserver(() => { - const element = getElement(); - if(element){ - resolve(element); - observer.disconnect(); - } + const element = getElement(); + if(element){ + window.clearTimeout(timeout); + resolve(element); + observer.disconnect(); + } }); observer.observe(document.body, { @@ -43,24 +49,27 @@ function waitForElement(selector, timeout) { subtree: true }); - window.setTimeout(() => { - reject(); - }, timeout); + return null; }); } /** - * Inject replacement onClick handler for Follow button + * Inject replacement onClick handler for Follow button. + * @returns {void} */ async function injectFollowButton() { try { - const followButton = await waitForElement(`.account__header__tabs__buttons button.button`, 20000); - followButton.addEventListener(`click`, onClickFollow); - } catch { + const followButton = await waitForElement(".account__header__tabs__buttons button:first-of-type", 20000); + followButton.addEventListener("click", onClickFollow); + } catch (error) { // Follow button failed to appear } } +/** + * Initialise injection for Mastodon Follow button. + * @returns {void} + */ async function init() { await injectFollowButton(); } From ec7c7ad4672dca286ecb6908bb77bb1c72b8cb7c Mon Sep 17 00:00:00 2001 From: Huey Date: Fri, 18 Nov 2022 12:40:29 +0800 Subject: [PATCH 6/7] fix: capitalisation in jsdoc --- src/content_script/mastodonInject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 261871b..0d57129 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -14,7 +14,7 @@ function onClickFollow(event) { } /** - * wait for element to appear. + * Wait for element to appear. * @param {string} selector * @param {number} timeoutDuration * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} From f1f27f6286be3ee56d62bc23f7cca1452b1cae77 Mon Sep 17 00:00:00 2001 From: Nick Richards Date: Sat, 19 Nov 2022 20:44:07 +0000 Subject: [PATCH 7/7] Address comment-format findings by @rugk --- src/background/modules/AutoRemoteFollow.js | 1 + src/content_script/mastodonInject.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/background/modules/AutoRemoteFollow.js b/src/background/modules/AutoRemoteFollow.js index 1921bca..35d26c2 100644 --- a/src/background/modules/AutoRemoteFollow.js +++ b/src/background/modules/AutoRemoteFollow.js @@ -146,6 +146,7 @@ function getInteractionType(url) { /** * Handles changes to the URL of a tab. + * * @returns {void} */ function onTabUpdate() { diff --git a/src/content_script/mastodonInject.js b/src/content_script/mastodonInject.js index 0d57129..a754edf 100644 --- a/src/content_script/mastodonInject.js +++ b/src/content_script/mastodonInject.js @@ -2,6 +2,7 @@ /** * Replacement onClick handler for Follow button. + * * @param {Event} event * @returns {void} */ @@ -15,6 +16,7 @@ function onClickFollow(event) { /** * Wait for element to appear. + * * @param {string} selector * @param {number} timeoutDuration * @see {@link https://github.com/storybookjs/test-runner/blob/6d41927154e8dd1e4c9e7493122e24e2739a7a0f/src/setup-page.ts#L134} @@ -55,6 +57,7 @@ function waitForElement(selector, timeoutDuration) { /** * Inject replacement onClick handler for Follow button. + * * @returns {void} */ async function injectFollowButton() { @@ -68,6 +71,7 @@ async function injectFollowButton() { /** * Initialise injection for Mastodon Follow button. + * * @returns {void} */ async function init() {