The weirdest thing is happening;
With my extension enabled, certain web pages appear normally, but the non-visible parts have their background color changed to black. So, everything looks fine until you scroll. Here's an example with my extension enabled:
Here it is with my extension turned off:
Again, this only happens for some web pages (many are fine) and only for their non-visible (i.e., scrollable) parts.
The only DOM manipulation happening is in popup.js, which, anyway I believe doesn't fire unless you click the extension icon (right?). And by "DOM manipulation", I mean:
document.getElementById('getStartedBtn').innerHTML = "Sign out";
document.getElementById('getStartedBtn').style.color = '#fff';
... nothing more. There's nothing in my contentScript.js nor background.js (which, anyway, is the background service worker).
In terms of manifest.json permissions, this is what I have:
"permissions": [
"contextMenus",
"activeTab",
"identity"
]
-- EDIT --
As requested, here's the complete manifest.json:
{
"manifest_version": 3,
"name": "XXX",
"version": "1.0.3",
"description": "XXX",
"icons": {
"16": "icons/icon_16.png",
"32": "icons/icon_32.png",
"48": "icons/icon_48.png",
"128": "icons/icon_128.png"
},
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "XXX",
"default_popup": "popup.html"
},
"permissions": [
"contextMenus",
"activeTab",
"identity"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"run_at": "document_start",
"js": ["contentScript.js"],
"css": ["contentScript.css"]
}
],
"oauth2": {
"client_id": "XXX.apps.googleusercontent.com",
"scopes":["openid", "email", "profile"]
},
"key": "XXX"
}
Here's the complete popup.js:
'use strict';
import './popup.css';
import '#lottiefiles/lottie-player';
///////////////////////////////////////////////////
// Events
// Fire every time extension icon is clicked
document.addEventListener('DOMContentLoaded', updateButtonText);
document.getElementById('getStartedBtn').addEventListener('click', () => {
document.getElementById('getStartedBtn').style.color = '#1fa3ec';
document.getElementById('getStartedBtn').classList.add("button--loading");
getStarted();
});
///////////////////////////////////////////////////
// Event logic
const subscriptionHTML = 'Subscribe for $1.';
function updateButtonText() {
// Send msg to background
chrome.runtime.sendMessage({ type: 'isUserSignedIn' }, (response) => {
if (response.response === 'YES') {
document.getElementById('getStartedBtn').innerHTML = "Sign out";
document.getElementById('signedInAsLbl').innerHTML = `Signed in as ${response.email}`;
document.getElementById('getASubscriptionLbl').innerHTML = "";
} else {
document.getElementById('getStartedBtn').innerHTML = "Sign in";
document.getElementById('signedInAsLbl').innerHTML = "";
document.getElementById('getASubscriptionLbl').innerHTML = subscriptionHTML;
}
document.getElementById('getStartedBtn').style.color = '#fff';
document.getElementById('getStartedBtn').classList.remove("button--loading");
});
}
function getStarted() {
// Send msg to background
chrome.runtime.sendMessage({type: 'getStarted', payload: { message: "Let's get started!"}}, (response) => {
if (response.response === 'YES') {
document.getElementById('getStartedBtn').innerHTML = "Sign out";
document.getElementById('signedInAsLbl').innerHTML = `Signed in as ${response.email}`;
document.getElementById('getASubscriptionLbl').innerHTML = "";
} else if (response === 'NO') {
document.getElementById('getStartedBtn').innerHTML = "Sign in";
document.getElementById('signedInAsLbl').innerHTML = "";
document.getElementById('getASubscriptionLbl').innerHTML = subscriptionHTML;
}
document.getElementById('getStartedBtn').style.color = '#fff';
document.getElementById('getStartedBtn').classList.remove("button--loading");
});
}
And here's button--loading CSS class from popup.css, responsible for a loading spinning circle on the sign in/out button:
.button--loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
border: 4px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: button-loading-spinner 1s ease infinite;
}
#keyframes button-loading-spinner {
from {
transform: rotate(0turn);
}
to {
transform: rotate(1turn);
}
}
Any ideas? I can't see why this is happening.
Related
I am working on a Chrome extension that highlights text in the same was as CTRL+F and I found this code that highlights text.
However, upon trying to implement it, I've been running into some trouble where everything run perfectly except the document.execCommand("HiliteColor") and document.execCommand("BackColor") functions.
I've read from this post that execCommand does not work in content_script, so it must be sent to the background page.
However, background pages have been replaced by service_workers, which when trying to use it, does not recognize the window variable.
Is there a way to implement the following functions without downgrading to manifest_version 2 and refactoring most of the code.
Code
manifest.json:
{
"manifest_version": 3,
"name": "ext",
"version": "1.0",
"description": "Extension",
"icons": {
"48": "icon.png"
},
"permissions": [
"tabs",
"storage",
"activeTab"
],
"host_permissions": [
"https://api.com/*"
],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"scripts/Highlight.js"
]
}
]
}
scripts/Highlight.js
function highlightSelection(color) {
var sel = window.getSelection();
var range = sel.getRangeAt(0);
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
console.log("Design mode on");
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
}
console.log("Design mode off");
document.designMode = "off";
}
chrome.runtime.onMessage.addListener((request) => {
console.log(request);
for (let index = 0; index < request.textArray.length; index++) {
const element = request.textArray[index];
console.log(element)
window.find(element.text);
highlightSelection(element.color);
}
});
The Listener receives a message with the following code:
function sendHighlight(color, errors) {
chrome.tabs.query({ active: true, currentWindow: true}, function(activeTabs) {
chrome.tabs.sendMessage(activeTabs[0].id, { errors, color }, (response) => {
console.log(response);
});
});
}
document.getElementById("button").addEventListener("click", () => sendHighlight("blue", ["Hello", "apple", "arraycontent"]));
What I tried
So far, I tried to comment the window.find() and just hightlight the selected text on the window, however the code seems to completely ignore the execCommand or at least not work as intended.
I also tried to modify the manifest as such:
"background": {
"service_worker": "scripts/Highlight.js"
}
But it does not seem to recognize global var such as window and document.
I would first like to thank #wOxxOm for helping me find the answer.
As explained by them, we first need to add the following call before using any HiliteColor functiondocument.execCommand("styleWithCSS", false, true);
Next, The biggest mistake I've made was in the argument handling, where the color argument was undefined in the HighlightSelection function.
Here is the final version of the Highlight.js function where, upon call, highlights all the given arguments.
function highlightSelection(color) {
document.designMode = "on";
document.execCommand("styleWithCSS", false, true);
if (!document.execCommand("HiliteColor", false, color)) {
document.execCommand("BackColor", false, color);
}
document.designMode = "off";
}
chrome.runtime.onMessage.addListener((request) => {
for (let index = 0; index < request.search.length; index++) {
const element = request.search[index];
while(window.find(element)) {
highlightSelection(request.color);
}
}
});
I am quite ashamed of making such a rookie mistake and hope that my code will be able to help some of you.
I'm migrating an extension from manifest v2 to manifest v3 (and refactor and redesign...)
In the old version we were using an iframe within the tab, like this:
background.js
const open = () => {
const oldIframe = document.getElementById('cm-frame');
if (oldIframe) {
oldIframe.remove();
return;
}
const iframe = document.createElement('iframe');
iframe.setAttribute('id', 'cm-frame');
iframe.setAttribute('style', 'top: 10px;right: 10px;width: 450px;height: 100%;z-index: 2147483650;border: none; position:fixed;');
iframe.setAttribute('allow', '');
iframe.src = chrome.extension.getURL('index.html');
iframe.frameBorder = 0;
document.body.appendChild(iframe);
};
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.executeScript({
code: '(' + open.toString() + ')();'
}, () => { });
});
I've tried the same approach using chrome.actions.onClicked.addListener with chrome.scripting.executeScript and had no luck.
I know we could just use a popup ("default_popup": "popup.html",) but the iframe works better in terms of integration and design.
I was able to make it work.
background.js
chrome.action.onClicked.addListener(async function (tab) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
const oldIframe = document.getElementById('cm-frame');
if (oldIframe) {
oldIframe.remove();
return;
}
const iframe = document.createElement('iframe');
iframe.setAttribute('id', 'cm-frame');
iframe.setAttribute(
'style',
'top: 10px;right: 10px;width: 400px;height: calc(100% - 20px);z-index: 2147483650;border: none; position:fixed;'
);
iframe.setAttribute('allow', '');
iframe.src = chrome.runtime.getURL('popup.html');
document.body.appendChild(iframe);
},
});
});
This my manifest.json file in case someone also needs it (I had to add stuff to permissions and web_accessible_resources to make it work and load correctly):
{
"manifest_version": 3,
"name": "Contact Mapping Extension",
"background": { "service_worker": "background.bundle.js" },
"action": {
"default_icon": "icon-34.png"
},
"icons": {
"128": "icon-128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "<all_urls>"],
"js": ["contentScript.bundle.js"],
"css": ["content.styles.css"]
}
],
"web_accessible_resources": [
{
"resources": ["content.styles.css", "icon-128.png", "icon-34.png", "popup.html"],
"matches": ["<all_urls>"]
}
],
"permissions": [
"tabs",
"activeTab",
"scripting"
]
}
Here is the contents of background.js:
var toggle = false;
chrome.browserAction.onClicked.addListener(function(tab) {
toggle = !toggle;
if(toggle) {
var start_working = function() {
chrome.tabs.query({ url: ["http://*/*","https://*/*"], currentWindow: true }, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.executeScript(tabs[i].id, {code : "var enabled = 1;"});
chrome.tabs.executeScript(tabs[i].id, {file: 'js/new.js'});
chrome.browserAction.setIcon({path: "add48.png", tabId:tabs[i].id});
}
});
}
if (!chrome.tabs.onUpdated.hasListener(start_working)) {
chrome.tabs.onUpdated.addListener(start_working);
chrome.tabs.query({url: ["http://*/*","https://*/*"]}, function (tabs) {
console.log('1start_working='+chrome.tabs.onUpdated.hasListener(start_working));
console.log('1toggle='+toggle);
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.executeScript(tabs[i].id, {code : "var enabled = 1;"});
chrome.tabs.executeScript(tabs[i].id, {file: 'js/new.js'});
chrome.browserAction.setIcon({path: "add48.png", tabId:tabs[i].id});
}
}
);
}
} else {
chrome.tabs.onUpdated.removeListener(start_working);
console.log('2start_working='+chrome.tabs.onUpdated.hasListener(start_working));
console.log('2toggle='+toggle);
chrome.tabs.query({url: ["http://*/*","https://*/*"], currentWindow: true }, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.executeScript(tabs[i].id, {code : "var enabled = 0; $('#sort_g').remove();"});
chrome.browserAction.setIcon({path: "add48_off.png", tabId:tabs[i].id});
}
}
);
return false;
}
});
Here is the code of manifest.json:
{
"manifest_version": 2,
"name": "Ds",
"version": "0.2",
"background": {
"scripts": ["background.js"]
},
"icons": {
"48": "add48.png",
"128": "add128.png"
},
"browser_action": {
"default_icon": "add48_off.png"
},
"permissions": [
"tabs",
"notifications",
"*://*/*"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery-2.2.4.min.js"],
"run_at": "document_end",
"all_frames": true
}
],
"web_accessible_resources": ["tpl/panel.html"]
}
When we click on the application icon, we add an element to all the HTML tabs and make the icon "active", and when we click the icon again, we change the icon to gray and remove the html element from all pages.
Also, when updating the html tab, the element should again appear and the extension icon should be active.
All this is implemented, but after disabling the extension, an html element still appears, although the new.js script should not be connected and chrome.tabs.onUpdated.hasListener(start_working)=false
Here's the log after disabling the extension:
background.js:49 2start_working=false
background.js:50 2toggle=false
But when updating the tab anyway, the html element appears on the page
I can not understand why this happens? Moreover, the console shows that after disabling the extension, when updating the page, the new.js script is connected already 3 times.
Right now, I have an app that always runs the content.js script and replaces all images in the browser that it can find with a different image. I want to make it so that this only happens when you actually click the icon for the extension.
Here is my manifest.json:
{
"manifest_version": 2,
"name": "Unicorn",
"description": "changes images and words to unicorn related ones",
"version": "1.0",
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"browser_action": {
"default_title": "Unicorn"
}
}
Here is content.js:
chrome.browserAction.onClicked.addListener(function (tab) { //should be fired when User Clicks ICON
var elements = document.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if(isImage(element)){
element.src="http://67.media.tumblr.com/30b1b0d0a42bca3759610242a1ff0348/tumblr_nnjxy1GQAA1tpo3v2o1_540.jpg";
}
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
var replacedText = text.replace(/wild/gi, 'rainbow bright');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
});
function isImage(i) {
return i instanceof HTMLImageElement;
}
This works fine without the line chrome.browserAction.onClicked.addListener(function (tab) { and the closing brackets for it. What am I missing here?
The browser action click handler should be moved to Background context for it to work, then you can send a message to current tab.
manifest.json
{
"manifest_version": 2,
"name": "Unicorn",
"description": "changes images and words to unicorn related ones",
"version": "1.0",
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"background": {
"scripts": [
"background.js"
]
},
"browser_action": {
"default_title": "Unicorn"
}
}
background.js
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.sendMessage(tab.id, {
method: 'showImages'
});
});
content.js
function showImages() {
var elements = document.getElementsByTagName('*');
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (isImage(element)) {
element.src = "http://67.media.tumblr.com/30b1b0d0a42bca3759610242a1ff0348/tumblr_nnjxy1GQAA1tpo3v2o1_540.jpg";
}
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
var replacedText = text.replace(/wild/gi, 'rainbow bright');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
}
function isImage(i) {
return i instanceof HTMLImageElement;
}
// Message handler
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.method === 'showImages') {
showImages();
}
});
I am writing extension which will underline accent in English words on web pages.
I've got stuck after I click "Search" button on popup, nothing seems to happen.
Here's the scenario:
user double click a word on a web page.
the whole word is marked.
user clicks extension icon on chrome browser bar.
a popup is shown. Input field in popup is filled in with the marked word.
user adds accent. Ie. if marked word is 'boundary', in popup's input field there will be: 'boudary' displayed. Then user modify the input value to: 'boudary,bo' (without quotes).
user click "Search" button on popup.
letters "bo" in "boundary" word on page are being underlined.
manifest.json
{
"content_scripts": [ {
"js": [ "jquery.js", "jquery.highlight-4.js", "selection.js" ],
"matches": [ "\u003Call_urls\u003E" ]
} ],
"name": "Mark accent",
"version": "1.0",
"manifest_version": 2,
"options_page": "options.html",
"description": "Marks accent in english words for selected word on page",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"icons": {
"128": "icon.png"
},
"permissions": [ "tabs", "http://*/*", "https://*/*", "storage", "file:///*" ]
}
app.js
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendRequest(tab.id, {method: "getSelection"}, function (response) {
$("#t1").val(response.data);
console.log('input t1 value: ' + $("#t1").val(response.data));
});
});
$("#t1").keypress(function(event) {
if ( event.which == 13 ) {
$("#search_btn").click();
}
});
$("#t1").focus();
function search(that) {
var token = new String (t1.value);
chrome.tabs.executeScript(null,
{code:"$(document.body).highlight('"+token+"','text-decoration:underline')"});
window.close();
}
selection.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == "getSelection")
sendResponse({data: window.getSelection().toString()});
else
sendResponse({}); // snub them.
});
popup.html
<style>
body {
overflow: hidden; margin: 5px; padding: 0px; background: black;color: white;
width: 300px; font-family: 'Droid Sans', arial, sans-serif;
}
</style>
Please enter the word to highlight :
<input type="text" id="t1"/>
<button onclick="search(this)" id="search_btn">Search</button>
<button onclick="hl_clear(this)" id="clear_btn">Clear all highlights</button>
<script src="jquery.js"></script>
<script src="jquery.highlight-4.js"></script>
<script src="app.js"></script>
jquery.highlight-4.js
jQuery.fn.highlight = function(pat, fbgcolor) {
function innerHighlight(node, pat, fbgcolor) {
var skip = 0;
var array = pat.split(',');
var sWord = array[0];
var accent = array[1];
if (node.nodeType == 3) {
var pos = node.data.toUpperCase().indexOf(sWord);
if (pos >= 0) {
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(sWord.length);
var pos2 = middlebit.data.toUpperCase().indexOf(accent);
if (pos2 >= 0) {
var spannode = document.createElement('span');
spannode.className = 'highlight';
fbgcolor += ";padding: 0px; margin: 0px;";
spannode.setAttribute('style', fbgcolor);
var middlebit2 = middlebit.splitText(pos2);
var endbit2 = middlebit2.splitText(accent.length);
var middleclone2 = middlebit2.cloneNode(true);
spannode.appendChild(middleclone2);
middlebit2.parentNode.replaceChild(spannode, middlebit2);
}
skip = 1;
}
}
else if (node.nodeType == 1 && node.childNodes &&
!/(script|style)/i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += innerHighlight(node.childNodes[i], pat, fbgcolor);
}
}
return skip;
}
return this.each(function() {
innerHighlight(this, pat.toUpperCase(), fbgcolor);
});
};
It is working after so many modifications and eliminated non-chrome extension related content. You can add your content to this skeleton.
Do not add Inline scripts in html
<button onclick="search(this)" id="search_btn">Search</button>
Basic Skeleton of your code:
manifest.json
{
"content_scripts": [ {
"js": [ "jquery.js", "jquery.highlight-4.js", "selection.js" ],
"matches": [ "<all_urls>" ]
} ],
"name": "Mark accent",
"version": "1.0",
"manifest_version": 2,
"description": "Marks accent in english words for selected word on page",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"icons": {
"128": "icon.png"
},
"permissions": [ "tabs", "<all_urls>" ]
}
app.js
function search(that) {
console.log("Search is clicked");
var token = document.getElementById("t1").value;
console.log(token);
chrome.tabs.executeScript(null,
{code:"highlight();"});
//window.close();
}
window.onload=function (){
document.getElementById("search_btn").onclick=search;
};
selection.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == "getSelection")
sendResponse({data: window.getSelection().toString()});
else
sendResponse({}); // snub them.
});
popup.html
<html>
<head>
<style>
body {
overflow: hidden; margin: 5px; padding: 0px; background: black;color: white;
width: 300px; font-family: 'Droid Sans', arial, sans-serif;
}
</style>
<script src="jquery.js"></script>
<script src="app.js"></script>
<body>
Please enter the word to highlight :
<input type="text" id="t1"/>
<button id="search_btn">Search</button>
<button id="">Clear all highlights</button>
</body>
</html>
jquery.highlight-4.js
function highlight(){
console.log("Highlight is called");
}
Let me know if it is still failing.