I am creating a chrome extension that I want to be able to enable/disable. I have successfully made a popup that does just that. The trouble is, if I reload the extension (or if the user downloads it initially) my content scripts default to being off. I could just inject the content script in the manifest.json but that results in the content script being injected for any new tab--which I do not want. The behavior should be that if you download/reload the extension, it is on by default, but then you can enable it/disable it and that applies to every new tab. I have tried to put an initialization in background.js but that does not get called at startup apparently.
manifest.json
{
"manifest_version": 2,
"name": "Rotten Tomatoes Search",
"description": "This extension searches rotten tomatoes with highlighted text",
"version": "1.0",
"browser_action": {
"default_icon": "./icons/icon_on.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"<all_urls>",
"background"
],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"content_scripts": [{
"js": ["jquery-1.12.3.min.js"],
"matches": ["<all_urls>"]
}]
}
background.js
var isExtensionOn = true;
chrome.tabs.executeScript({code: "console.log('backgournd hit...')"});
turnItOn();
chrome.extension.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.cmd == "setOnOffState") {
isExtensionOn = request.data.value;
}
if (request.cmd == "getOnOffState") {
sendResponse(isExtensionOn);
}
});
function turnItOn() {
chrome.browserAction.setIcon({path: "./icons/icon_on.png"});
chrome.tabs.executeScript({file:"openTooltipMenu.js"});
//$('#toggle').text('disable');
}
popup.js
document.addEventListener('DOMContentLoaded', function() {
// show different text depending on on/off state (for icon, handled by having default icon)
chrome.extension.sendMessage({ cmd: "getOnOffState" }, function(currentState){
if (currentState) $('#toggle').text('disable');
else $('#toggle').text('enable');
});
// allow user to toggle state of extension
var toggle = document.getElementById('toggle')
toggle.addEventListener('click', function() {
//chrome.tabs.executeScript({code: "console.log('toggled...')"});
chrome.extension.sendMessage({ cmd: "getOnOffState" }, function(currentState){
var newState = !currentState;
// toggle to the new state in background
chrome.extension.sendMessage({ cmd: "setOnOffState", data: { value: newState } }, function(){
// after toggling, do stuff based on new state
if (newState) turnOn();
else turnOff();
});
});
})
});
function turnOn() {
chrome.browserAction.setIcon({path: "./icons/icon_on.png"});
chrome.tabs.executeScript({file:"openTooltipMenu.js"});
$('#toggle').text('disable');
}
function turnOff() {
chrome.browserAction.setIcon({path: "./icons/icon_off.png"});
chrome.tabs.executeScript({code: "$('body').off();"});
$('#toggle').text('enable');
}
popup.html
<some code>
<script src="./jquery-1.12.3.min.js"></script>
<script src="./popup.js"></script><style type="text/css"></style>
</head>
<body>
<div class="popupMenu" style="list-style-type:none">
<div class="header">Rotten Tomatoes Search</div>
<hr>
<div class="menuEntry" id="toggle"></div>
</div>
</body>
</html>
I have figured out the issue. My architectural approach was wrong. One should inject the content_script globally but check with the script whether or not something should be done. To be clearer, in the script get the status from the background page and do something based on that. Previously, I was only injecting the script once the popup was loaded or once initially when the background was initialized. Additionally, one must loop through all tabs in all windows to update the state in all tabs (if that's what one wants).
Related
I'm working on Chrome extensions. I try to learn messaging between content and background. I develop simple project for this. But I have issue.
Basic idea is
User click button on extension popup
A function (bot.js) find image from content of tab then extension (background.js) will download it.
The issue is port.onMessage.addListener() in background.js fired twice.
When background.js sends message to contentscript.js there are two same messages in console or when I try to download in background.js (the code line "Do Something") it download the file twice.
How can I solve this problem?
popup.html
<!doctype html>
<html>
<head>
<title>Test Plugin</title>
<script src="background.js"></script>
<script src="popup.js"></script>
</head>
<body>
<h1>Test Plugin</h1>
<button id="btnStart">Button</button>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
var checkPageButton = document.getElementById('btnStart');
checkPageButton.addEventListener('click', function() {
GetImages("Some URL");
}, false);
}, false);
var tab_title = '';
function GetImages(pageURL){
// Tab match for pageURL and return index
chrome.tabs.query({}, function(tabs) {
var tab=null;
for(var i=0;i<tabs.length;i++){
if(tabs[i].url==undefined || tabs[i].url=="" || tabs[i]==null){}
else{
if(tabs[i].url.includes(pageURL)){
tab=tabs[i];
break;
}
}
}
if(tab!=null){
chrome.tabs.executeScript(tab.id, {
file: "bot.js"
}, function(results){
console.log(results);
});
}
});
}
bot.js
var thumbImagesCount = document.querySelectorAll('.classifiedDetailThumbList .thmbImg').length;
var megaImageURL=document.querySelectorAll('.mega-photo-img img')[0].src;
console.log(megaImageURL + " from bot.js");
port.postMessage({key:"download", text: megaImageURL});
background.js
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "content-script");
port.onMessage.addListener(function(message) {
console.log(message);
if(message.key=="download"){
// Do Something
// Event fires twice
port.postMessage({key:"download", text: "OK"});
}
})
});
contentscript.js
console.log("content script loaded!");
var port = chrome.runtime.connect({name: "content-script"});
port.onMessage.addListener(function(message){
console.log(message);
});
manifest.json
{
"manifest_version": 2,
"name": "Test Extension",
"description": "This extension will download images from gallery",
"version": "1.0",
"icons": {
"16": "bot16.png",
"48": "bot48.png",
"128": "bot128.png" },
"browser_action": {
"default_icon": "bot48.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"downloads",
"http://*/",
"https://*/"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
]
}
The background script declared in manifest.json already has its own page, a hidden background page where it runs, so you should not load it in the popup as it makes no sense in case there are listeners for API events, the background page is already listening for them. In this case the copy also creates the second listener while the popup is open.
Solution: don't load background.js in popup.
See also Accessing console and devtools of extension's background.js.
I wrote an extension for Chrome. I want when I click on button from my extension, the value 'abc' will be set into active input on active page.
Here are my codes:
1) manifest.json
{
"name": "Test",
"short_name": "Test",
"manifest_version": 2,
"version":"2.0.0.0",
"browser_action": {
"default_popup": "index.html",
"default_title": "Load EmojiSelector"
},
"background":{
"scripts":["background.js"],
"persistent": false
},
"content_scripts":[
{
"matches":["http://*/*", "https://*/*"],
"js":["content.js"]
}
]
,
"permissions": [
"activeTab"
]
}
2) index.html
<!DOCTYPE html>
<html>
<head>
<title>Test SendMessage</title>
<script src='content.js'></script>
</head>
<body>
<input id='btsend' type='button' value='Send abc to input'>
</body>
</html>
3) background.js
chrome.runtime.onMessage.addListener(function(response, sender, sendResponse){
var act = chrome.tabs.getSelected(null, function(tab){
//How to set the value of response to active input in page????
});
});
4) content.js
onload=function(e){
var btsend = document.getElementById('btsend');
btsend.onclick = function(){
chrome.runtime.sendMessage('abc');
}
}
How can I set value for active input in active page by using DOM.
Make sure to read about the extension architecture: your popup script should be different from the content script. Actually, you don't need an unconditionally injected content script at all, use chrome.tabs.executeScript like shown in the official sample extension Page Redder.
Note: It's not possible to insert text on the default new tab page because Chrome redirects keyboard input to its built-in omnibox (address bar) which accepts only trusted (real) key presses, not the ones sent from JavaScript.
The correct popup script should attach a click listener to the element
You don't need a background page script at all for this task
manifest.json:
{
"name": "Test",
"manifest_version": 2,
"version":"2.0.0.0",
"browser_action": {
"default_popup": "popup.html",
"default_title": "Load EmojiSelector"
},
"permissions": [
"activeTab"
]
}
popup.html:
<input id='btsend' type='button' value='Send abc to input'>
<script src='popup.js'></script>
popup.js:
document.getElementById('btsend').onclick = () => {
chrome.tabs.executeScript({file: 'content.js'});
};
content.js:
document.execCommand('insertText', false, 'abc');
im dealing with combination of popup, content script and background script. Unfotrunatelly any communication I chose among them failed to me to get desired result.
I need to develop webextension with icon/popup in browser menu. Once it is clicked, it opens dedicated menu with items in popup window.
When i click on popup certain item.. tab should be opened and it should go to certain URL to log in (via URL with parameters or POST) and go to subpage (specific to popup item text).
I was trying many combinations of messagings between popup & bg script, between bg script & content script etc. ... but it never worked to me. I had also problems with synchronizations of requests, because although i logged in (via windows.open) i could perform further subpage to load.
Do you have guys idea how this could work? the best with some shor example?
I used only standard javascript.
My FIRST attempt:
manifest.json
{
"description": "test",
"manifest_version": 2,
"name": "extensiontest",
"version": "1.0",
"homepage_url": "https://xxxx.com",
"icons": {
"32": "beasts-32.png"
},
"applications": {
"gecko": {
"id": "testid#sss.com",
"strict_min_version": "45.0"
}
},
"content_scripts":
[
{
"matches": ["<all_urls>"],
"run_at": "document_end",
"js": ["content.js"]
}
],
"browser_action":
{
"default_icon": "popup.png",
"default_popup":"popup.html"
},
"background":
{
"scripts": ["background.js"]
},
"permissions":
[
"tabs","storage"
]
}
popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="links.css"/>
</head>
<script src="popup.js"></script>
<body>
<ul>
<li><a id="bQuote" href="#">Quote</a></li>
<li><a id="bSend" href="#">Send</a></li>
</ul>
</body></html>
popup.js
function goQuote() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "Quote"});
});
// Generic Log In link
window.open("Login-link","targetname");
}
function goSend() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "Send"});
});
// Generic Log In link
window.open("Login-link","targetname");
}
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("bQuote").addEventListener("click", goQuote);
document.getElementById("bSend").addEventListener("click", goSend);
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "Quote" ) {
// Subpage link with same target name
window.open("supbpage-link1","targetname");
}
if( request.message === "Send" ) {
// Subpage link with same target name
window.open("supbpage-link2","targetname");
}
}
);
You need a background script to orchestrate things.
In popup.js use chrome.runtime.sendMessage() to send message to a background.js on click event.
In background.js:
chrome.runtime.onMessage() to catch a message from popup.js
chrome.tabs.create() to open login page and remember tab id;
chrome.webNavigation.onCompleted.addEventListener() to detect tab is loaded (you need an event with tabId that you remembered and frameId === 0);
chrome.tabs.executeScript() on that tab to inject content.js that will fill form and submit it;
chrome.webNavigation.onCompleted.addEventListener() again to detect tab is loaded;
chrome.tabs.update() to navigate to desired subpage;
cleanup.
And keep in mind that anytime anything can go wrong: user may interrupt page loading, credentials may be wrong, network connection may disappear, etc. :)
Here is my scenario: By clicking the browser icon, I create a sidebar (html and css) next to the whole page, thus creating two columns (one is my sidebar, the other one is the actual page).
What I to achieve is having the sidebar stay when I reload the page or navigate to another page WITHIN the same domain. What I have right now is just the creation of the sidebar, but I have to click the browser action every time I navigate or reload the web page.
Manifest:
{
"name": "apdrop",
"version": "0.1",
"manifest_version": 2,
"description": "first prototype for apdrop extension",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon": "icons/icon19.png",
"default_title": "apdrop"
},
"permissions": [
"background",
"tabs",
"http://*/*/",
"https://*/*/"
]
}
Background.js
function injectedScript(tab, method){
chrome.tabs.insertCSS(tab.id, {file:"style.css"});
//chrome.tabs.insertCSS(tab.id, {file:"bootstrap.css"});
chrome.tabs.executeScript(tab.id, { file: 'jquery-2.1.1.min.js'});
//chrome.tabs.executeScript(tab.id, { file: 'bootstrap.min.js'});
chrome.tabs.executeScript(tab.id, { file: 'inject.js'});
}
function click(tab){
console.log("browser action clicked");
injectedScript(tab, 'click');
//alert("action button was clicked");
}
chrome.browserAction.onClicked.addListener(click);
Inject.js
var ev = $("body > *");
if (!document.getElementById('contentxf343487d32'))
{
ev.wrapAll("<div id='insidecontent65675f526567'>");
$("#insidecontent65675f526567").wrapAll("<div id='contentxf343487d32'>");
$("<div id='sidebar343gf87897fh'><div id='insidesidebar87678bbbb'><p>this is my name</p></div></div>").insertBefore("#contentxf343487d32");
}
else
{
$("#sidebar343gf87897fh").remove();
$("#insidecontent65675f526567").unwrap();
$("#insidecontent65675f526567 > div").unwrap();
}
Hope this helps clarify a bit more.
The simplest strategy would be to save state in domain's sessionStorage and have a "detector" script that re-injects your UI.
Add setting the state in your content script:
// inject.js
if (!document.getElementById('contentxf343487d32'))
{
// ...
sessionStorage["contentxf343487d32"] = true;
}
else
{
// ...
sessionStorage["contentxf343487d32"] = false;
}
Add a "detector" script:
// detect.js
if(sessionStorage["contentxf343487d32"])
{
chrome.runtime.sendMessage({injectSidebar: true});
}
Always inject the script on page load, via the manifest (and change to a better permission):
"content_scripts" : [
{
"matches": ["<all_urls>"],
"js": ["detect.js"]
}
],
"permissions": [
"background",
"tabs",
"<all_urls>"
]
In the background, inject the script upon message:
// background.js
chrome.runtime.onMessage.addListener( function (message, sender, sendResponse){
if(message.injectSidebar)
{
click(sender.tab);
}
});
If you need more persistence than sessionStorage provides, use localStorage. If you need a different logic, you can still use this skeleton of a detector signalling the background.
I'm trying to access some DOM elements from a webpage:
<html>
<button id="mybutton">click me</button>
</html>
I want to access the innerHTML ("click me") through a chrome extension:
chrome.browserAction.onClicked.addListener(function(tab) {
var button = document.getElementById("mybutton");
if(button == null){
alert("null!");
}
else{
alert("found!");
}
});
When I click the extension, the popup says: "null".
My manifest.json:
{
"name": "HackExtension",
"description": "Hack all the things",
"version": "2.0",
"permissions": [
"tabs", "http://*/*"
],
"background": {
"scripts": ["contentscript.js"],
"persistent": false
},
"browser_action": {
"scripts": ["contentscript.js"],
"persistent": false
},
"manifest_version": 2
}
The solution:
You need a manifest file, a background script and a content script. This is not really clear in the documentation that you have to use it and also, how to use it. For alerting the full dom, see here. Because I have a hard time finding a complete solution that actually works and not just snippets that are useless for newbies, like me, I included a specific solution:
manifest.json
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["file:///*"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Test Extension"
},
"permissions": ["activeTab"]
}
content.js
/* Listen for messages */
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
/* If the received message has the expected format... */
if (msg.text && (msg.text == "report_back")) {
/* Call the specified callback, passing
the web-pages DOM content as argument */
sendResponse(document.getElementById("mybutton").innerHTML);
}
});
background.js
/* Regex-pattern to check URLs against.
It matches URLs like: http[s]://[...]stackoverflow.com[...] */
var urlRegex = /^file:\/\/\/:?/;
/* A function creator for callbacks */
function doStuffWithDOM(element) {
alert("I received the following DOM content:\n" + element);
}
/* When the browser-action button is clicked... */
chrome.browserAction.onClicked.addListener(function(tab) {
/*...check the URL of the active tab against our pattern and... */
if (urlRegex.test(tab.url)) {
/* ...if it matches, send a message specifying a callback too */
chrome.tabs.sendMessage(tab.id, { text: "report_back" },
doStuffWithDOM);
}
});
index.html
<html>
<button id="mybutton">click me</button>
</html>
Just save the index.html somewhere and load in the folder as an extension, containing the three other files. Open the index.html and push the extension button. It should show "click me".
Starting with Manifest V3, your content scripts won't be able to access anything generated by other loaded scripts and using a trick like inlining a your code inside <script> tag won't work due to stricter CSP rules. This caused me a lot of head ache since I couldn't figure out how to access library-generated DOM properties similar to React or Redux DevTools.
Instead, you have to now inject your script inside the service_worker with eg:
chrome.scripting.registerContentScripts([
{
id: 'inject',
matches: ['<all_urls>'],
js: ['inject.js'],
runAt: 'document_end',
world: 'MAIN'
}
])
Notice the 'MAIN' property, not the default 'ISOLATED'. Then inside my inject.js I do whatever, eg:
window.addEventListener('load', () => {
findReact()
})
Also you have to add the script to the manifest.json:
"web_accessible_resources": [
{
"resources": ["inject.js"],
"matches": ["<all_urls>"],
"extension_ids": []
}
],
"externally_connectable": {
"ids": ["*"]
},
Not sure is "externally_connectable" needed. And you need to add at least "scripting" permissions. I used the React DevTools migration as my source https://github.com/facebook/react/pull/25145