my aim is to override the default setInterval JS function in all pages with a browser extension (by injecting my javascript code just at the beginning of pages). This script should run before any other script, jQuery included. My latest attempt (not working) is this:
manifest.js
...
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start",
"all_frames": true
}
],
"background": {
"service_worker": "background.js"
},
"permissions": [
"scripting"
],
"host_permissions": ["<all_urls>"],
...
content.js
function do_newInterval() {
window.setInterval = function setInterval() {console.log('hello')}
};
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.type === 'newInterval') { do_newInterval(); }
});
background.js
chrome.tabs.onUpdated.addListener(async () => {
let activeTab=await chrome.tabs.query({ active: true,
lastFocusedWindow: true })
chrome.tabs.sendMessage(activeTab[0].id, { type: "newInterval"
});
})
Unfortunately the code to replace the setInterval function is run after other page scripts are executed!
Could you please show me the right way to make my JS run as first?
Thank you!
Related
I know there are similar questions and I've tried almost everything in them. I'm trying to build a chrome extension and it needs to pass a message from content.js to background.js.
The code:
background.js
var xstext;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
xstext=request.stext;
});
var wtf = "https://www.google.com/search?q="+xstext;
chrome.commands.onCommand.addListener(function(command) {
if(command=="ntab"){
chrome.tabs.create({ url: wtf});
}
});
content.js
var text = window.getSelection();
var stext=text.toString();
chrome.runtime.sendMessage({stext: stext});
manifest.json
"manifest_version": 2,
"name": "vind",
"version": "1.0",
"description": "Search stuff easily!",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": true,
"run_at": "document_start",
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png"
},
"default_popup": "popup.html"
},
"commands": {
"ntab": {
"suggested_key": {
"default": "Alt+G",
"windows": "Alt+G",
"mac": "Alt+G",
"chromeos": "Alt+G",
"linux": "Alt+G"
},
"description": "New tab for the query"
}
}
}
I want to pass the selected text from content.js to background.js, I have tried adding return: true; in the listener to no avail. I'm getting 'undefined' added to the main string, so nothing seems to get passed. what should I do?
This approach won't work because 1) your content script is saving the text at the moment it runs which happens just one time at page load and 2) since the background script is not persistent it'll unload after receiving a message and xstext will disappear.
Do the reverse: ask the content script when the hotkey is pressed.
background.js, entire contents:
chrome.commands.onCommand.addListener(command => {
if (command === 'ntab') {
chrome.tabs.query({active: true, lastFocusedWindow: true}, ([tab]) => {
if (!tab) return;
chrome.tabs.sendMessage(tab.id, 'getText', text => {
chrome.tabs.create({
url: 'https://www.google.com/search?q=' + encodeURIComponent(text),
});
});
});
}
});
content.js, entire contents:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getText' && document.hasFocus()
&& (!document.activeElement || !/^I?FRAME$/.test(document.activeElement.tagName))) {
sendResponse(getSelection().toString());
}
});
P.S. An advanced solution would be to run the content script on demand (using chrome.tabs.executeScript in background.js onCommand listener) so you can remove content_scripts section from manifest.json and use activeTab permission instead.
I am trying to receive some info from the content page to the popup page in chrome extension.
Here is my manifest.json:
{
"name": " Downloader",
"description": "history ",
"version": "1.0",
"permissions": [
"activeTab",
"notifications"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": false,
"matches": ["<all_urls>"],
"exclude_matches": [],
"js": [
"/src/jquery.js",
"/src/sheet-min.js",
"/src/file-saver-min.js"
]
// "css": [
// "js/content/page.css"
// ]
}
],
"content_scripts": [{
"matches": ["*://*.ebay.com/*"],
"js": ["content.js"],
"run_at": "document_idle",
"all_frames": false
}],
"browser_action": {
"default_title": "Download History.",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"manifest_version": 2
}
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
// First, validate the message's structure.
if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
// Enable the page-action for the requesting tab.
chrome.browserAction.show(sender.tab.id);
}
});
content.js
// Inform the background page that
// this tab should have a page-action.
function ping() {
chrome.runtime.sendMessage('ping', response => {
if(chrome.runtime.lastError) {
setTimeout(ping, 1000);
} else {
chrome.runtime.sendMessage({
from: 'content',
subject: 'showPageAction',
});
}
});
}
ping();
// Listen for messages from the popup.
chrome.runtime.onMessage.addListener((msg, sender, response) => {
// First, validate the message's structure.
if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
// Collect the necessary data.
// (For your specific requirements `document.querySelectorAll(...)`
// should be equivalent to jquery's `$(...)`.)
var domInfo = {
total: document.querySelectorAll('*').length,
inputs: document.querySelectorAll('input').length,
buttons: document.querySelectorAll('button').length,
};
// Directly respond to the sender (popup),
// through the specified callback.
response(domInfo);
}
});
popup.js
const setDOMInfo = info => {
console.log(info)
};
window.addEventListener('DOMContentLoaded', () => {
// ...query for the active tab...
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
// ...and send a request for the DOM info...
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', subject: 'DOMInfo'},
// ...also specifying a callback to be called
// from the receiving end (content script).
setDOMInfo);
});
});
I know that this error occurs when the content script sends message to the background script but the background script is not ready to receive the message. After looking for a solution on stackoverflow I decided to use the ping function but as you can see above but it still gives me the same error message.
There's no chrome.browserAction.show as you can see in the documentation so the listener in background.js throws and aborts the execution. The messaging cycle never completes so to the sender it looks just like an absence of any receiver.
Each part of an extension has its own devtools.
Open devtools for the background script and you'll see the error.
There's no need for the background script here.
No need for showPageAction message either because browser_action is enabled by default.
P.S. the entire code can be simplified by switching to programmatic injection (example) so you can remove content_scripts, background script, and messaging.
This question already has an answer here:
Pass a variable from content script to popup
(1 answer)
Closed 4 years ago.
I want to send data from content.js to popup.js,
The content.js is just grabbing the document title and then passing it to the popup.js.
So then popup.js will change the popup.html DOM.
manifest.json:
{
"browser_action": {
"default_icon": {
"64": "icons/icon64.png"
},
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
],
"permissions": [
"tabs",
"activeTab",
"*://*/*"
]
}
popup.html:
<html>
<body>
<span class="info">TAB TITLE</span>
<script src="popup.js"></script>
</body>
</html>
content.js:
console.log('CONTENT IS RUNNING')
var getTitle = function() {
return document.title
}
chrome.runtime.sendMessage(getTitle());
popup.js:
console.log('POPUP IS RUNNING')
chrome.runtime.onMessage.addListener(
function(response, sender, sendResponse) {
var title = response;
return title;
}
);
document.querySelector('.info').innerHTML = title; // error: title is not defind
In popup.js the response parameter is not giving the document title.
The popup runs (and exists) only when it's shown.
The declared content script runs whenever the web page is loaded.
These two events may happen at any time so there's no guarantee they coincide.
Solution 1: don't declare the content script, instead run it manually
popup.js:
chrome.tabs.executeScript({code: 'document.title'}, ([title]) => {
document.querySelector('.info').textContent = title;
});
Note how we use textContent here which is safe, unlike innerHTML which can lead to XSS (it'll be blocked by default but not if you've relaxed the default CSP).
Solution 2: read the tab title directly via chrome.tabs API
popup.js:
chrome.tabs.query({active: true, currentWindow: true}, ([tab]) => {
document.querySelector('.info').textContent = tab.title;
});
manifest.js for both solutions:
{
"browser_action": {
"default_icon": {
"64": "icons/icon64.png"
},
"default_popup": "popup.html"
},
"permissions": ["activeTab"]
}
Note we only need "activeTab" permission so no permissions warning is displayed at installation.
I don't want to include jQuery on every page by listing it in the manifest
In the console, this works fine, but I can't dynamically include jQuery in a content script
No idea why
Put these two files in a folder (content.js and manifest.json)
Go to chrome:extensions in omnibox (url bar)
Load Unpacked Extension
Select Folder
Go to any page and CMD+Shift+R reload without cache
Check out the console and see jQuery is undefined
content.js
if (document.readyState === "complete") {
appendJQuery();
} else {
document.addEventListener("DOMContentLoaded", appendJQuery);
} function appendJQuery () {
var jq = document.createElement("script");
window.document.querySelector("head").appendChild(jq);
jq.onload = function () {
console.log(typeof $); // $ is not defined ?????
}
jq.src = "https://code.jquery.com/jquery-2.1.1.min.js";
}
manifest.json
{
"manifest_version": 2,
"name": "Sample",
"short_name": "Sample",
"version": "1.1",
"permissions": ["tabs", "http://*/*, https://*/*", "*://*/*", "<all_urls>"],
"content_scripts": [{
"matches": ["*://*/*", "http://*/*", "https://*/*", "file://*/*"],
"js": ["content.js"],
"run_at": "document_start"
}]
}
then jQuery is undefined......... wtf??? anyone know why??
How to get access to variable app from content script app.js in background script background.js?
Here is how I try it (background.js):
chrome.tabs.executeScript(null, { file: "app.js" }, function() {
app.getSettings('authorizeInProgress'); //...
});
Here is what I get:
Here is manifest.json:
{
"name": "ctrl-vk",
"version": "0.1.3",
"manifest_version": 2,
"description": "Chrome extension for ctrl+v insertion of images to vk.com",
"content_scripts": [{
"matches": [
"http://*/*",
"https://*/*"
],
"js": ["jquery-1.9.1.min.js"
],
"run_at": "document_end"
}],
"web_accessible_resources": [
"jquery-1.9.1.min.js"
],
"permissions" : [
"tabs",
"http://*/*",
"https://*/*"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
}
}
Full code for instance, at github
https://github.com/MaxLord/ctrl-vk/tree/with_bug
To avoid above error use following code
if (tab.url.indexOf("chrome-devtools://") == -1) {
chrome.tabs.executeScript(tabId, {
file: "app.js"
}, function () {
if (app.getSettings('authorizeInProgress')) {
alert('my tab');
REDIRECT_URI = app.getSettings('REDIRECT_URI');
if (tab.url.indexOf(REDIRECT_URI + "#access_token") >= 0) {
app.setSettings('authorize_in_progress', false);
chrome.tabs.remove(tabId);
return app.finishAuthorize(tab.url);
}
} else {
alert('not my');
}
});
}
instead of
chrome.tabs.executeScript(null, {
file: "app.js"
}, function () {
if (app.getSettings('authorizeInProgress')) {
alert('my tab');
REDIRECT_URI = app.getSettings('REDIRECT_URI');
if (tab.url.indexOf(REDIRECT_URI + "#access_token") >= 0) {
app.setSettings('authorize_in_progress', false);
chrome.tabs.remove(tabId);
return app.finishAuthorize(tab.url);
}
} else {
alert('not my');
}
});
Explanation
chrome://extensions/ page also fires chrome.tabs.onUpdated event, to avoid it we have to add a filter to skip all dev-tool pages.
(Would've submitted this as comment to the accepted answer but still lack the required reputation)
You should also give the tabId to chrome.tabs.executeScript as first argument when you have it. Otherwise you risk user switching windows/tabs right after requesting a URL and background.js doing executeScript against wrong page.
While fairly obvious on hindsight it threw me for a loop when I got that same error message "Cannot access contents of url "chrome-devtools://.." even though my chrome.tabs.onUpdated eventhandler was checking that the page user requested had some specific domain name just before doing the executeScript call.
So keep in mind, chrome.tabs.executeScript(null,..) runs the script in active window, even if the active window might be developer tools inspector.
We should notice that, in the manifest cofigļ¼
"content_scripts": [{
"matches": [
"http://*/*",
"https://*/*"
],
"js": ["jquery-1.9.1.min.js"
],
in the "matches" part, only http, https are matched, so if you load your extension in page like: 'chrome://extensions/', or 'file:///D:xxx', that error will occur.
You may load your extension in the page with the url 'http://'; or add more rules in your 'matches' array.