Forgive me for any glaring mistakes as I am new to chrome extensions, but this error with Chrome's message passing API has been discussed here, here, and here in the past and the common response is along the lines of 'disable existing Chrome extensions, one of them is causing the error'. Is this the best that can be accomplished? Are we supposed to just roll over and accept the fact that our extensions will conflict with others? Returning true or returning a Promise for the listener callback function and using sendResponse does not solve the problem for me.
Currently, I can only get the new value stored in chrome.storage.local (no errors) by disabling all other chrome extensions, removing the extension and loading back up the unpacked extension. The code interestingly only seems to work on developer.chrome.com, it doesn't work at all on the other "matches" URLs in manifest.json.
I think that there is some significance in the await and async operators in solving this issue but I am unsure how to properly implement it.
manifest.json:
{
"manifest_version": 2,
"name": "my extension",
"version": "1.0",
"description": "its my extension",
"permissions": [
"declarativeContent",
"storage",
"activeTab"
],
"content_scripts": [
{
"matches": [
"*://developer.chrome.com/*",
"*://bbc.co.uk/*",
"*://theguardian.com/*",
"*://dailymail.co.uk/*"
],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_security_policy": "script-src 'self' https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js; object-src 'self'",
"page_action": {
"default_popup": "popup.html"
},
"icons": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
}
popup.html:
<!DOCTYPE html>
<html>
<head>
<title>my extension</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="popup.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h1>my extension</h1>
<h2>Article: <span id="article-headline"></span></h2>
<button id="detect-article">Detect Article</button>
</body>
</html>
popup.js:
$(document).ready(function() {
$("#detect-article").click(function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id, {request: "Requesting headline"}, function(response) {
console.log("Requesting headline")
});
});
});
})
function getHeadline(changes) {
let changedValues = Object.keys(changes);
//console.log(changedValues);
for (var item of changedValues) {
console.log("new value: " + changes[item].newValue);
$("#article-headline").text(changes[item].newValue)
}
}
chrome.storage.onChanged.addListener(getHeadline);
content.js:
function handleRequest(message, sender, sendResponse) {
console.log("Request recieved");
let headlineList = document.getElementsByTagName("h1");
chrome.storage.local.set({headline: headlineList[0].innerText}, function() {
console.log("'" + headlineList[0].innerText + "' stored in local storage");
});
return true;
}
chrome.runtime.onMessage.addListener(handleRequest);
background.js:
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostContains: 'developer.chrome.com' },
}),
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostContains: 'bbc.co.uk' },
}),
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostContains: 'theguardian.com' },
}),
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostContains: 'dailymail.co.uk' },
}),
],
actions: [new chrome.declarativeContent.ShowPageAction()]
}]);
});
});
Many thanks for taking the time to look/re-look at this issue, solutions pertaining to the aforementioned 'disable existing extensions' are not what I am looking for.
When you specify a callback for sendMessage you're telling the API that you NEED a response so when your content script doesn't respond using sendResponse the API thinks something terrible happened and reports it as such!
Reminder: when editing content scripts make sure to reload both the extension on chrome://extensions page and the tabs that should have this content script.
If you need a response from asynchronously running code such as chrome API callback:
Keep return true
Call sendResponse(someImportantData) inside the callback
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
chrome.storage.local.set({foo: 'bar'}, () => {
sendResponse('whatever');
});
return true;
});
Same for Promise, but don't use async for the onMessage listener, more info.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
fetch(message.url).then(r => r.text())
.then(t => sendResponse({ok: t}))
.catch(e => sendResponse({err: e.message}));
return true;
});
If you need a response and it can be sent immediately:
Replace return true with sendResponse
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse('whatever');
});
If you don't need any response:
Remove the callback in sendMessage
chrome.tabs.sendMessage(tabs[0].id, {request: "Requesting headline"});
Remove return true - all it does currently is telling the API to keep the messaging port open indefinitely, which will never be used by you, so it's just a memory leak source.
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// do something
// don't return true
// ManifestV2: don't call sendResponse
// ManifestV3 bug: uncomment the next line
// sendResponse();
});
For ManifestV3 in Chrome 99, 100, 101 you need a dummy sendResponse() call.
Related
In manifest v2, I am trying to force the onBeforeRequest listener to wait for an asynchronous call before the evaluation of a test condition that decides whether to block the request or let it go.
The documentation and topics i came accross seems conflicting, some say that this feature is not supported within webRequest, while others suggest there could be workarounds.
The most promising one i found so far but unfortunately didn't quite work in my case, came from this post : Use asynchronous calls in a blocking webRequest handler
Is there a possible idea to circumvent this issue ?
chrome.webRequest.onBeforeRequest.addListener(
function() {
chrome.tabs.query({active:true,windowType:"normal", currentWindow: true},function(tab){
for(const [key, value] of Object.entries(inputMaps[tab[0].id][Object.keys(inputMaps[tab[0].id])[0]])){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
// Async call to content_script will update the testMap[curTabId]
chrome.tabs.sendMessage(tabs[0].id,
{
text: "aadAuth",
usr: Object.keys(inputMaps[tabs[0].id])[0],
pass: value
})
});
}
});
// Need to make sure this executes after the reception of async call response
// Impossible to block the request from within the async response callback method.
if(testMap[curTabId]){
return {cancel: true};
}else{
return {}
}
},
{urls: ["<all_urls>"]},
["blocking"]
);
Extract of the manifest :
{
"manifest_version": 2,
"background": {
"service_worker": "background.js"
},
"permissions": [
"storage",
"activeTab",
"tabs",
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
"browser_action": {
"default_popup": "popup.html",
}
}
I want to extract the HTML of a webpage so that I can analyze it and send a notification to my chrome extension. Sort of like how an adblocker does it when analyzing a web page for ads and then tell the extension how many possible ads there are.
I am trying to use the document object in content-scripts to get the HTML, however, I always seem to get the HTML of my popup file instead. Can anybody help?
content-script.js
chrome.tabs.onActivated.addListener(function(activeInfo) {
chrome.tabs.get(activeInfo.tabId, function(tab) {
console.log("[content.js] onActivated");
chrome.tabs.sendMessage(
activeInfo.tabId,
{
content: document.all[0].innerText,
type: "from_content_script",
url: tab.url
},
{},
function(response) {
console.log("[content.js]" + window.document.all[0].innerText);
}
);
});
});
chrome.tabs.onUpdated.addListener((tabId, change, tab) => {
if (tab.active && change.url) {
console.log("[content.js] onUpdated");
chrome.tabs.sendMessage(
tabId,
{
content: document.all[0].innerText,
type: "from_content_script",
url: change.url
},
{},
function(response) {
console.log("[content.js]" + window.document.all[0].innerText);
}
);
}
});
background.js
let messageObj = {};
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Arbitrary string allowing the background to distinguish
// message types. You might also be able to determine this
// from the `sender`.
if (message.type === "from_content_script") {
messageObj = message;
} else if (message.type === "from_popup") {
sendResponse(messageObj);
}
});
manifest.json
{
"short_name": "Extension",
"version": "1.0.0",
"manifest_version": 3,
"name": "My Extension",
"description": "My Extension Description",
"permissions": ["identity", "activeTab", "tabs"],
"icons": {
"16": "logo-16.png",
"48": "logo-48.png",
"128": "logo-128.png"
},
"action": {
"default_icon": "ogo_alt-16.png",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["./static/js/content-script.js"],
"run_at": "document_end"
}
],
"background": {
"service_worker": "./static/js/background.js"
}
}
Your current content script is nonfunctional because content scripts cannot access chrome.tabs API. If it kinda worked for you, the only explanation is that you loaded it in the popup, which is wrong because the popup is not a web page, it's a separate page with a chrome-extension:// URL.
For your current goal, there's no need for the background script at all because you can simply send a message from the popup to the content script directly to get the data. Since you're showing the info on demand there's also no need to run the content scripts all the time in all the sites i.e. you can remove content_scripts from manifest.json and inject the code on demand from the popup.
TL;DR. Remove content_scripts and background from manifest.json, remove background.js and content-script.js files.
manifest.json:
"permissions": ["activeTab", "scripting"],
popup.html:
<body>
your UI
<script src=popup.js></script>
</body>
popup.js:
(async () => {
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
let result;
try {
[{result}] = await chrome.scripting.executeScript({
target: {tabId: tab.id},
func: () => document.documentElement.innerText,
});
} catch (e) {
document.body.textContent = 'Cannot access page';
return;
}
// process the result
document.body.textContent = result;
})();
If you want to to analyze the page automatically and display some number on the icon then you will need the background script and possibly content_scripts in manifest.json, but that's a different task.
Hey guys so I'm building an extension for chrome with VueJS and now I need to be able to when the user clicks a button in the extension it redirects the current tab to the new URL.
I tried the approach by this guy in StackOverflow: https://stackoverflow.com/a/35523438
Unfortunately, it didn't work and I don't know if it's because of a bad implementation or simply wouldn't work anyways.
Here goes my code:
ProductList.vue
Ill only include the script part since its the important part. Its running BTW cuz when I click the button it prints out the url.
<script>
export default {
name: "ProductList",
props: {
items: Array
},
methods: {
shopProduct(url) {
console.log(url);
chrome.runtime.sendMessage('open-product-url')
}
}
}
</script>
Manifest.json
{
"manifest_version": 2,
"name": "__MSG_extName__",
"homepage_url": "http://localhost:8080/",
"description": "A Vue Browser Extension",
"default_locale": "en",
"permissions": [
"tabs",
"<all_urls>",
"*://*/*"
],
"icons": {
"16": "icons/16.png",
"48": "icons/48.png",
"128": "icons/128.png"
},
"browser_action": {
"default_popup": "/app.html",
"default_title": "__MSG_extName__",
"default_icon": {
"19": "icons/19.png",
"38": "icons/38.png"
}
}
}
Background.js
chrome.runtime.onMessage.addListener(
(message, sender, sendResponse) => {
console.log(sender.id);
console.log(sender.tab.id);
sendResponse(true);
}
)
vue.config.js
module.exports = {
pages: {
app: {
template: 'public/app.html',
entry: './src/main.js',
title: 'App'
}
},
pluginOptions: {
browserExtension: {
componentOptions: {
background: {
entry: './src/assets/js/background.js'
},
contentScripts: {
entries: {
'content-script': [
'./src/assets/js/contents.js'
]
}
}
}
}
}
}
For now, I only tried to print out the sender id since I want to later update the URL, and Ill need the sender tab id.
Fixed the problem my self.
Docs I used to solve the issue: https://www.streaver.com/blog/posts/create-web-extension-vue.html
I was writing the listner in background.js when I should've wrote it in contents.js and in the vue component I needed to use a query and the send the message like the following:
browser.tabs.query({ active: true, currentWindow: true }).then(tabs => {
browser.tabs.sendMessage(tabs[0].id, {
msg: { action: "change_body_color", value: 'hey' }
});
});
Try This API of chrome, Chrome.tabs.update
Background.js
chrome.runtime.onMessage.addListener(
(message, sender, sendResponse) => {
console.log(sender.id);
console.log(sender.tab.id);
chrome.tabs.update(sender.tab.id, {
url: '<new_url>',
});
sendResponse(true);
}
)
Check this solution: Update and run pre-defined link on current tab chrome extension
Reference
https://developer.chrome.com/docs/extensions/reference/tabs/#method-update
Suggestion
You don't need "*://*/*" permission if you are using <all_urls> permission.
I am trying to load a website inside an iFrame but the server is sending the X-Frame-Options: SAMEORIGIN header so I've tried to use onHeadersReceived to modify the headers though I cannot get it to work.
manifest.json
{
"manifest_version": 2,
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__",
"default_locale": "en",
"version": "0.1",
"author": "author",
"homepage_url": "https://github.com/",
"icons": {
"48": "assets/icons/logo.png"
},
"background": {
"page": "../../background.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["scripts/dist/bundle.js"],
"css": ["assets/css/main.css"]
}
],
"permissions": [
"tabs",
"webRequest",
"contextMenus",
"webNavigation",
"webRequestBlocking"
],
"web_accessible_resources": [
"assets/icons/logo.png"
]
}
background.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="module" src="scripts/dist/contextMenu.js"></script>
<script type="module" src="scripts/dist/modifyHeaders.js"></script>
</head>
</html>
contextMenu.js
browser.contextMenus.create( {
id: "customsearch",
title: "Search",
contexts: ["selection"]
} );
// Context menu onClicked listener
browser.contextMenus.onClicked.addListener( (info, tab) => {
if (info.menuItemId = "custom-search") {
sendMessageToTab(tab, info);
}
} );
function sendMessageToTab(tab, info) {
browser.tabs.sendMessage(
tab.id,
{ query: info.selectionText }
);
}
background.js
var extraInfoSpec = ['blocking', 'responseHeaders'];
var filter = {
urls: ['<all_urls>'],
tabId: -1
};
// Bypass X-Frame-Options
browser.webRequest.onHeadersReceived.addListener(
modifyHeadersCallback,
filter,
extraInfoSpec
);
// onHeadersReceived Callback
function modifyHeadersCallback(details) {
let modifiedResponseHeaders = details.responseHeaders.filter(
header => !(header.name.toLowerCase() == 'x-frame-options' || header.name.toLowerCase() == 'content-security-policy')
);
return {responseHeaders: modifiedResponseHeaders};
};
Context menu works as expected, the problem is with the browser.webRequest.onHeadersReceived listener which seems to not get fired at all as I don't get any errors or logs to the console.
I did any extensive search and tried most of the solutions I've found but nothing worked for my case. Can you spot anything wrong in my approach?
Firefox
All you need is to remove tabId: -1 from your filter object:
browser.webRequest.onHeadersReceived.addListener(
modifyHeadersCallback,
{ urls: ['<all_urls>'] },
['blocking', 'responseHeaders']
);
Chrome
Modern Chrome requires the extraHeaders mode in extraInfoSpec parameter so the universal code for iframes would look like this:
browser.webRequest.onHeadersReceived.addListener(
modifyHeadersCallback,
{ urls: ['<all_urls>'], types: ['sub_frame'] },
// Modern Chrome needs 'extraHeaders' to see and change this header,
// so the following code evaluates to 'extraHeaders' only in modern Chrome.
['blocking', 'responseHeaders', chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS]
.filter(Boolean)
);
And of course "permissions" in manifest.json should contain the URLs you want to process e.g. in this case it's "<all_urls>".
So, this is either a bug or an intentional change, which wasn't documented yet, so if someone wants to report it please open a new issue on https://crbug.com. I guess it's intentional because the extraHeaders mode means this header is handled in the internal network process, which is separate from the browser process.
I'm trying to create a Chrome extension that displays the current page's DOM in a popup.
As a warmup, I tried putting a string in getBackgroundPage().dummy, and this is visible to the popup.js script. But, when I try saving the DOM in getBackgroundPage().domContent, popup.js just sees this as undefined.
Any idea what might be going wrong here?
I looked at this related post, but I couldn't quite figure out how to use the lessons from that post for my code.
Code:
background.js
chrome.extension.getBackgroundPage().dummy = "yo dummy";
function doStuffWithDOM(domContent) {
//console.log("I received the following DOM content:\n" + domContent);
alert("I received the following DOM content:\n" + domContent);
//theDomContent = domContent;
chrome.extension.getBackgroundPage().domContent = domContent;
}
chrome.tabs.onUpdated.addListener(function (tab) {
//communicate with content.js, get the DOM back...
chrome.tabs.sendMessage(tab.id, { text: "report_back" }, doStuffWithDOM); //FIXME (doesnt seem to get into doStuffWithDOM)
});
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 */
//alert("hi from content script"); //DOESN'T WORK ... do we ever get in here?
sendResponse(document.all[0].outerHTML);
}
});
popup.js
document.write(chrome.extension.getBackgroundPage().dummy); //WORKS.
document.write(chrome.extension.getBackgroundPage().domContent); //FIXME (shows "undefined")
popup.html
<!doctype html>
<html>
<head>
<title>My popup that should display the DOM</title>
<script src="popup.js"></script>
</head>
</html>
manifest.json
{
"manifest_version": 2,
"name": "Get HTML example w/ popup",
"version": "0.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Get HTML example",
"default_popup": "popup.html"
},
"permissions": ["tabs"]
}
You got the syntax of chrome.tabs.onUpdated wrong.
In background.js
chrome.tabs.onUpdated.addListener(function(id,changeInfo,tab){
if(changeInfo.status=='complete'){ //To send message after the webpage has loaded
chrome.tabs.sendMessage(tab.id, { text: "report_back" },function(response){
doStuffWithDOM(response);
});
}
})