Use document.execCommand() on a chrome extension with manifest_version 3 - google-chrome-extension

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.

Related

declarativeNetRequest Regex rules for redirecting YT shorts work when the url is typed out or opened in a new tab but not when clicked from YT itself

I'm making a Chrome extension from redirecting youtube.com/shorts/... to youtube.com/watch?v=...
Everything works fine when I open those shorts links in new tabs or when I type them out but when I click from the homepage itself, they don't get redirected.
Here is my rules.json file:
[
{
"id": 1,
"priority": 1,
"action": { "type": "redirect", "redirect": { "regexSubstitution":"https://youtube.com/watch?v=\\1" } },
"condition": { "regexFilter": "^.*youtube\\.com/shorts/(.*)", "resourceTypes": ["main_frame"] }
}
]
Here is my manifest.json file:
{
"manifest_version": 3,
"name": "No Shorts",
"version": "0.5",
"description": "Play YT shorts as regular videos instead of in a separate player",
"action": {
"default_icon": "images/no-shorts-ico.png"
},
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
},
"icons":{
"16": "images/16.png",
"48": "images/48.png",
"128": "images/128.png"
},
"permissions":[ "declarativeNetRequest"],
"host_permissions":["*://*.youtube.com/*"]
}
I clicked on a short video from the homepage and it did not get redirected. However, when I refreshed it, it did get redirected. It also got redirected when I clicked open in new tab or typed out the url myself.
If I had to guess, I think it is happening because of something that is similar to client-side navigation but I really can't say for sure. Is there a fix for this?
There is no network request to intercept in such inner navigation as it uses the history API in JS.
You can run a script on the entire youtube domain and intercept the click event:
// page.js:
addEventListener('click', e => {
const thumb = e.target.closest('ytd-thumbnail');
const cmd = thumb?.__data.data.navigationEndpoint.commandMetadata.webCommandMetadata;
if (cmd?.webPageType !== 'WEB_PAGE_TYPE_SHORTS') return;
cmd.webPageType = 'WEB_PAGE_TYPE_WATCH';
cmd.url = cmd.url.replace('/shorts/', '/watch?v=');
for (const a of thumb.querySelectorAll('a'))
a.href = a.href.replace('/shorts/', '/watch?v=');
}, true);
// manifest.json:
"background": { "service_worker": "bg.js" },
"permissions": ["scripting"],
"host_permissions": ["*://www.youtube.com/"],
// bg.js
chrome.runtime.onInstalled.addListener(async () => {
const scripts = [{
id: 'page',
world: 'MAIN',
matches: ['*://www.youtube.com/*'],
runAt: 'document_start',
js: ['page.js'],
}];
await chrome.scripting.unregisterContentScripts({ids: scripts.map(s => s.id)})
.catch(() => {});
await chrome.scripting.registerContentScripts(scripts);
for (const script of scripts) {
const execCfg = {
target: {},
files: script.js,
injectImmediately: true,
world: script.world,
};
for (const tab of await chrome.tabs.query({url: script.matches})) {
execCfg.target.tabId = tab.id;
chrome.scripting.executeScript(execCfg);
}
}
});

Change the color of Send button of Gmail Compose dialog using chrome extension

I am making a chrome extension in which I want to change the color of the "Send" button of Compose dialog.
What is the best way to do it?
Thanks in advance!
Update-
Here is the function of content.js I am currently using to change the color-
function modifySendButton(check, form) {
var send_button = $(form).siblings('table').find('div[role="button"][aria-label="Send ‪(Ctrl-Enter)‬"]');
if (0 === send_button.length) {
send_button = $(this).siblings('table').find('div[role="button"][aria-label="Send ‪(⌘Enter)‬"]');
}
if (true === check) {
send_button.addClass("active-send-button");
} else {
send_button.removeClass("active-send-button");
}
}
It is changing the Send button color, but I want to know is it the right way to do so?
You can try to use mutation observer inside the content-script.
Complete example:
manifest.json
{
"manifest_version": 2,
"name": "Btn Color",
"description": "",
"version": "1.0.0",
"content_scripts": [{
"js": ["content-script.js"],
"matches": [
"https://mail.google.com/*"
],
"run_at": "document_end"
}],
"permissions": [
"tabs"
]
}
content-script.js
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.addedNodes && mutation.addedNodes.length) {
// look for the button
const sendBtn = document.querySelector('.dw [aria-label*="Send"]');
if(sendBtn) {
sendBtn.style.background = 'red'; // set your styles
}
}
});
});
var targetNode = document.body;
observer.observe(targetNode, {childList: true});

removeListener в Chrome extension not working

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.

chrome.browserAction.onClicked cannot be triggered

I am working on some experiments that requires to auto generate IDs for elements, I have decided to go with chrome extension.
However, the content script only got triggered when I land on the homepage of the site (e.g. https://secure.checkout.visa.com/). Once I navigate to other pages (e.g. tap on "Create Account"), the script didn't get triggered, I tried to set a break point on the content script but it only got hit on the home page, but not on create account page.
Any idea what went wrong here? Below is the code:
// manifest.json
{
"manifest_version": 2,
"name": "Chrome Auto Gen ID",
"description": "Generate IDs",
"version": "1.0",
"permissions": ["activeTab"],
"background": {
"scripts": ["event_page.js"],
"persistent": false
},
"browser_action": {
"default_title": "Auto generate ID"
},
"content_scripts": [
{
"matches": ["https://secure.checkout.visa.com/*"],
"js": ["jquery-3.2.1.min.js", "auto_gen_id.js"],
"all_frames": true,
"run_at": "document_idle"
}
]
}
The content script:
// auto_gen_id.js
var divs = document.getElementsByTagName("div");
var inputs = document.getElementsByTagName("input");
var sections = document.getElementsByTagName("section");
var count = 0;
function genId(list) {
for (var i = 0; i < list.length; ++i) {
var element = list[i];
if (element.hasAttribute("id")) {
continue;
}
element.id = "gen-id-" + count++;
}
}
genId(divs);
genId(inputs);
The background script:
// event_page.js
chrome.browserAction.onClicked.addListener(function(tab) {
console.log('Generating IDs on ' + tab.url);
chrome.tabs.executeScript(null, {file: "auto_gen_id.js"});
});
Thanks #makyen and #wOxxOm, followed the suggestions below to solve the problem:
Is there a JavaScript/jQuery DOM change listener

Google Chrome Extension Not Working As Expected

So I'm trying my hand at a google chrome extension which in theory should be extremely straight forward. I know there are tons of stack queries and I've tried a few, I've even copied and pasted direct solutions for testing and I have been unsuccessful. So here is the project. When a page loads, check the html content, and change the icon programmatically.
This is my content.js
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete' && tab.active) {
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.browserAction.setIcon({path: "check.png"});
} else {
chrome.browserAction.setIcon({path: "cross.png"});
}
}
})
This is my manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "Check if tag exist on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
Right now I'm running this item as a content script because as a content script I can use the logic
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {} else {}
However as a content script the chrome API stuff doesn't work. When I run this script as a background script the script works, except that
var markup = document.documentElement.innerHTML;
doesn't return the pages html, it returns the injected script html.
I've read this stack which was informative as to what the difference was and I've read and tested many stacks like here this, without much success. So obviously i'm missing something and doing something wrong. Thank in advanced for any help.
UPDATES:
So I've made some modifications, but it's still not working, though I think that it's closer to working.
As per the comments below I am now using a content script and background script. No errors are being thrown, however the background script isn't doesn't anything.
content.js
var markup = document.documentElement.innerHTML;
var m = markup.indexOf("candy");
if (m > -1) {
chrome.runtime.sendMessage({"found" : true});
} else {
chrome.runtime.sendMessage({"found": false});
}
background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if request.found {
alert("HERE");
chrome.browserAction.setIcon({
path: "check.png",
tabId: sender.tab.id
});
} else {
alert("HERE2");
chrome.browserAction.setIcon({
path: "cross.png",
tabId: sender.tab.id
});
}
});
manifest.json
{
"manifest_version": 2,
"name": "Tag Analyzer Plugin",
"description": "find tag on page",
"version": "1.0",
"browser_action": {
"default_icon": "cross.png"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"permissions": ["<all_urls>"]
}
I've added some alerts on the background.js to see if it was being trigger and nothing, not does the icon change.

Resources