diff --git a/src/background/content-script.js b/src/background/content-script.js new file mode 100644 index 0000000..8be3c26 --- /dev/null +++ b/src/background/content-script.js @@ -0,0 +1,12 @@ +import browser from "webextension-polyfill"; + +browser.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { + browser.tabs.executeScript(tabId, { + file: 'content_script/input_tools.js', + runAt: 'document_idle', + }); + + browser.tabs.insertCSS(tabId, { + file: 'content_script/input_tools.css', + }); +}); \ No newline at end of file diff --git a/src/background/index.js b/src/background/index.js index 1068bb4..622f804 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,10 +1,11 @@ +import browser from "webextension-polyfill"; import APIService from "../popup/APIService"; import SLStorage from "../popup/SLStorage"; +import "./content-script"; import { handleNewRandomAlias } from "./create-alias"; import { handleOnClickContextMenu } from "./context-menu"; -global.browser = require("webextension-polyfill"); global.isBackgroundJS = true; /** @@ -24,7 +25,7 @@ async function handleGetAppSettings() { /** * Register onMessage listener */ -global.browser.runtime.onMessage.addListener(async function (request, sender) { +browser.runtime.onMessage.addListener(async function (request, sender) { if (request.tag === "NEW_RANDOM_ALIAS") { return await handleNewRandomAlias(sender.tab); } else if (request.tag === "GET_APP_SETTINGS") { @@ -35,7 +36,7 @@ global.browser.runtime.onMessage.addListener(async function (request, sender) { /** * Register context menu */ -global.browser.contextMenus.create({ +browser.contextMenus.create({ title: "Create random email alias (copied)", contexts: ["all"], onclick: handleOnClickContextMenu, diff --git a/src/content_script/input_tools.js b/src/content_script/input_tools.js index 352e30f..5be266d 100644 --- a/src/content_script/input_tools.js +++ b/src/content_script/input_tools.js @@ -1,235 +1,237 @@ -let SLSettings = {}; +if (!typeof InputTools !== 'undefined') { + let SLSettings = {}; -const InputTools = { - isLoading: false, + const InputTools = { + isLoading: false, - // store tracked input elements - trackedElements: [], + // store tracked input elements + trackedElements: [], - init(target) { - InputTools.queryEmailInputAndApply(target, (element) => { - if (!InputTools.isValidEmailInput(element)) { + init(target) { + InputTools.queryEmailInputAndApply(target, (element) => { + if (!InputTools.isValidEmailInput(element)) { + return; + } + + // ignore if this elements has already been tracked + const i = InputTools.trackedElements.indexOf(element); + if (i === -1) { + InputTools.trackedElements.push(element); + InputTools.addSLButtonToInput(element); + } + }); + }, + + destroy(target) { + InputTools.queryEmailInputAndApply(target, (element) => { + // remove element from tracking list + const i = InputTools.trackedElements.indexOf(element); + if (i !== -1) { + InputTools.trackedElements.splice(i, 1); + } + }); + }, + + queryEmailInputAndApply(target, actionFunction) { + if (!target.querySelectorAll) return; + const elements = target.querySelectorAll( + "input[type='email'],input[name*='email'],input[id*='email']" + ); + for (const element of elements) { + actionFunction(element); + } + }, + + isValidEmailInput(element) { + const style = getComputedStyle(element); + return ( + // check if element is visible + style.visibility !== "hidden" && + style.display !== "none" && + style.opacity !== "0" && + style.pointerEvents === "auto" && + // check if element is not disabled + !element.disabled && + // for example, we must filter out a checkbox with name*=email + // check if element is text or email input + (element.type === "text" || element.type === "email") + ); + }, + + addSLButtonToInput(inputElem) { + // create wrapper for SL button + const btnWrapper = InputTools.newDiv("sl-button-wrapper"); + const inputSumHeight = inputElem.getBoundingClientRect().height + "px"; + btnWrapper.style.height = inputSumHeight; + btnWrapper.style.width = inputSumHeight; + document.body.appendChild(btnWrapper); + + // create the SL button + const slButton = InputTools.newDiv("sl-button"); + slButton.addEventListener("click", function () { + InputTools.handleOnClickSLButton(inputElem, slButton); + }); + slButton.style.height = inputSumHeight; + slButton.style.width = inputSumHeight; + btnWrapper.appendChild(slButton); + + InputTools.placeBtnToTheRightOfElement(inputElem, btnWrapper); + }, + + newDiv(...className) { + const div = document.createElement("div"); + div.classList.add(...className); + return div; + }, + + placeBtnToTheRightOfElement(inputElem, btnWrapper) { + let intervalId = 0; + + function updatePosition() { + // check is element is removed from trackedElements + const i = InputTools.trackedElements.indexOf(inputElem); + if (i === -1) { + btnWrapper.parentNode.removeChild(btnWrapper); + clearInterval(intervalId); + } + + // get dimension & position of input + const inputCoords = inputElem.getBoundingClientRect(); + const inputStyle = getComputedStyle(inputElem); + const elemWidth = InputTools.dimensionToInt(btnWrapper.style.width); + const pageXOffset = window.pageXOffset; + const pageYOffset = window.pageYOffset; + const buttonXOffset = + SLSettings.SLButtonPosition === "right-inside" + ? -elemWidth * 1.02 + : elemWidth * 0.02; + + // calculate elem position + const left = + InputTools.sumPixel([ + inputCoords.left, + pageXOffset, + inputElem.offsetWidth, + buttonXOffset, + -inputStyle.paddingRight, + ]) + "px"; + + const top = InputTools.sumPixel([inputCoords.top, pageYOffset]) + "px"; + + if (btnWrapper.style.left !== left) { + btnWrapper.style.left = left; + } + + if (btnWrapper.style.top !== top) { + btnWrapper.style.top = top; + } + } + + intervalId = setInterval(updatePosition, 200); + updatePosition(); + }, + + async handleOnClickSLButton(inputElem, slButton) { + if (InputTools.isLoading) { return; } + InputTools.isLoading = true; + slButton.classList.add("loading"); - // ignore if this elements has already been tracked - const i = InputTools.trackedElements.indexOf(element); - if (i === -1) { - InputTools.trackedElements.push(element); - InputTools.addSLButtonToInput(element); - } - }); - }, - - destroy(target) { - InputTools.queryEmailInputAndApply(target, (element) => { - // remove element from tracking list - const i = InputTools.trackedElements.indexOf(element); - if (i !== -1) { - InputTools.trackedElements.splice(i, 1); - } - }); - }, - - queryEmailInputAndApply(target, actionFunction) { - if (!target.querySelectorAll) return; - const elements = target.querySelectorAll( - "input[type='email'],input[name*='email'],input[id*='email']" - ); - for (const element of elements) { - actionFunction(element); - } - }, - - isValidEmailInput(element) { - const style = getComputedStyle(element); - return ( - // check if element is visible - style.visibility !== "hidden" && - style.display !== "none" && - style.opacity !== "0" && - style.pointerEvents === "auto" && - // check if element is not disabled - !element.disabled && - // for example, we must filter out a checkbox with name*=email - // check if element is text or email input - (element.type === "text" || element.type === "email") - ); - }, - - addSLButtonToInput(inputElem) { - // create wrapper for SL button - const btnWrapper = InputTools.newDiv("sl-button-wrapper"); - const inputSumHeight = inputElem.getBoundingClientRect().height + "px"; - btnWrapper.style.height = inputSumHeight; - btnWrapper.style.width = inputSumHeight; - document.body.appendChild(btnWrapper); - - // create the SL button - const slButton = InputTools.newDiv("sl-button"); - slButton.addEventListener("click", function () { - InputTools.handleOnClickSLButton(inputElem, slButton); - }); - slButton.style.height = inputSumHeight; - slButton.style.width = inputSumHeight; - btnWrapper.appendChild(slButton); - - InputTools.placeBtnToTheRightOfElement(inputElem, btnWrapper); - }, - - newDiv(...className) { - const div = document.createElement("div"); - div.classList.add(...className); - return div; - }, - - placeBtnToTheRightOfElement(inputElem, btnWrapper) { - let intervalId = 0; - - function updatePosition() { - // check is element is removed from trackedElements - const i = InputTools.trackedElements.indexOf(inputElem); - if (i === -1) { - btnWrapper.parentNode.removeChild(btnWrapper); - clearInterval(intervalId); + let res = await sendMessageToBackground("NEW_RANDOM_ALIAS"); + if (res.error) { + alert("SimpleLogin Error: " + res.error); + res = { alias: "" }; } - // get dimension & position of input - const inputCoords = inputElem.getBoundingClientRect(); - const inputStyle = getComputedStyle(inputElem); - const elemWidth = InputTools.dimensionToInt(btnWrapper.style.width); - const pageXOffset = window.pageXOffset; - const pageYOffset = window.pageYOffset; - const buttonXOffset = - SLSettings.SLButtonPosition === "right-inside" - ? -elemWidth * 1.02 - : elemWidth * 0.02; + InputTools.isLoading = false; + slButton.classList.remove("loading"); - // calculate elem position - const left = - InputTools.sumPixel([ - inputCoords.left, - pageXOffset, - inputElem.offsetWidth, - buttonXOffset, - -inputStyle.paddingRight, - ]) + "px"; + inputElem.value = res.alias; + }, - const top = InputTools.sumPixel([inputCoords.top, pageYOffset]) + "px"; - - if (btnWrapper.style.left !== left) { - btnWrapper.style.left = left; + sumPixel(dimensions) { + let sum = 0; + for (const dim of dimensions) { + sum += !isNaN(dim) ? dim : InputTools.dimensionToInt(dim); } + return sum; + }, - if (btnWrapper.style.top !== top) { - btnWrapper.style.top = top; + dimensionToInt(dim) { + try { + const pixel = parseFloat(dim.replace(/[^0-9.]+/g, "")); + return isNaN(pixel) ? 0 : pixel; + } catch (e) { + return 0; } - } + }, + }; - intervalId = setInterval(updatePosition, 200); - updatePosition(); - }, + const MutationObserver = + window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver; - async handleOnClickSLButton(inputElem, slButton) { - if (InputTools.isLoading) { - return; - } - InputTools.isLoading = true; - slButton.classList.add("loading"); - - let res = await sendMessageToBackground("NEW_RANDOM_ALIAS"); - if (res.error) { - alert("SimpleLogin Error: " + res.error); - res = { alias: "" }; - } - - InputTools.isLoading = false; - slButton.classList.remove("loading"); - - inputElem.value = res.alias; - }, - - sumPixel(dimensions) { - let sum = 0; - for (const dim of dimensions) { - sum += !isNaN(dim) ? dim : InputTools.dimensionToInt(dim); - } - return sum; - }, - - dimensionToInt(dim) { - try { - const pixel = parseFloat(dim.replace(/[^0-9.]+/g, "")); - return isNaN(pixel) ? 0 : pixel; - } catch (e) { - return 0; - } - }, -}; - -const MutationObserver = - window.MutationObserver || - window.WebKitMutationObserver || - window.MozMutationObserver; - -/** - * Add DOM mutations listener - */ -function addMutationObserver() { - const mutationObserver = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - for (const addedNode of mutation.addedNodes) { - // add SLButton for newly added nodes - InputTools.init(addedNode); - } - - for (const removedNode of mutation.removedNodes) { - // destroy SLButton for removed nodes - InputTools.destroy(removedNode); - } - }); - }); - - const target = document.body; - if (!target) return; - - mutationObserver.observe(target, { - childList: true, - subtree: true, - }); -} - -/** - * Send message to background.js and resolve with the response - * @param {string} tag - * @param {object} data - */ -function sendMessageToBackground(tag, data = null) { - const _browser = window.chrome || browser; - return new Promise((resolve) => { - try { - _browser.runtime.sendMessage( - { - tag, - data, - }, - function (response) { - resolve(response); + /** + * Add DOM mutations listener + */ + function addMutationObserver() { + const mutationObserver = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + for (const addedNode of mutation.addedNodes) { + // add SLButton for newly added nodes + InputTools.init(addedNode); } - ); - } catch (e) { - // Extension context invalidated. - console.error(e); - } - }); -} -async function initModule() { - SLSettings = await sendMessageToBackground("GET_APP_SETTINGS"); - if (SLSettings.showSLButton) { - InputTools.init(document); - addMutationObserver(); + for (const removedNode of mutation.removedNodes) { + // destroy SLButton for removed nodes + InputTools.destroy(removedNode); + } + }); + }); + + const target = document.body; + if (!target) return; + + mutationObserver.observe(target, { + childList: true, + subtree: true, + }); } -} -initModule(); + /** + * Send message to background.js and resolve with the response + * @param {string} tag + * @param {object} data + */ + function sendMessageToBackground(tag, data = null) { + const _browser = window.chrome || browser; + return new Promise((resolve) => { + try { + _browser.runtime.sendMessage( + { + tag, + data, + }, + function (response) { + resolve(response); + } + ); + } catch (e) { + // Extension context invalidated. + console.error(e); + } + }); + } + + async function initModule() { + SLSettings = await sendMessageToBackground("GET_APP_SETTINGS"); + if (SLSettings.showSLButton) { + InputTools.init(document); + addMutationObserver(); + } + } + + initModule(); +} \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index a5acd67..73494bd 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -8,11 +8,12 @@ "128": "icons/icon_128.png" }, "permissions": [ - "activeTab", "storage", "cookies", "contextMenus", - "https://*.simplelogin.io/*" + "tabs", + "http://*/*", + "https://*/*" ], "browser_action": { "default_title": "simplelogin-extension", @@ -27,15 +28,5 @@ "scripts": [ "background.js" ] - }, - "content_scripts": [ - { - "matches": [ - "" - ], - "js": ["content_script/input_tools.js"], - "css": ["content_script/input_tools.css"], - "all_frames": true - } - ] + } }