Content script not being allowed to access extension resources - google-chrome-extension

In my extension I dynamically inject a content script. The content script is
let txt = fetch(chrome.runtime.getURL('example.txt')).then(r=>r.text());
The error I get is GET chrome-extension://invalid/ net::ERR_FAILED (anonymous) # content-script.js:36.
In my research I have found the page https://developer.chrome.com/docs/extensions/mv3/manifest/web_accessible_resources/ that talks about how some scripts get denied resources. But it says that content scripts are not effected.
What is the issue?
Are the docs wrong?

I assume you're referring to this sentence from Manifest - Web Accessible Resources:
"Content scripts themselves do not need to be allowed."
That means you don't need to declare content-script.js as a web-accessible resource in order to inject content-script.js into a web page.
But example.txt is not a content script. It's just a file that's part of your extension. Because content-script.js runs in the context of a web page, content-script.js can only fetch() example.txt if you declare example.txt as a web-accessible resource.
If you don't want to declare example.txt as a web-accessible resource, you can have content-script.js send a message to the extension's service worker. The service worker can then fetch() example.txt and send the result back to content-script.js.
Another problem:
fetch() returns a Promise, and so does r.text()
let txt = fetch(chrome.runtime.getURL('example.txt')).then(r=>r.text());
This code doesn't put the contents of example.txt into the variable txt.
Instead, the variable txt contains a Promise that evaluates to the contents of example.txt.
You either need to chain another then-handler to your code, or use async/await. See await Error at Top Level Module in Chrome Extension Service_worker
Example with web-accessible resource
manifest.json
{
"manifest_version": 3,
"name": "Fetch in Content Script",
"version": "1.0",
"action": {
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content_script.js"]
}
],
"web_accessible_resources": [
{
"resources": [ "example.txt" ],
"matches": [ "*://*/*" ]
}
]
}
content_script.js
fetch(chrome.runtime.getURL('example.txt'))
.then(response => response.text())
.then(text => {
console.log(text);
});
Example with message passing
manifest.json
{
"manifest_version": 3,
"name": "Fetch in Content Script",
"version": "1.0",
"action": {
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content_script.js"]
}
]
}
background.js
function on_message(message, sender, sendResponse) {
fetch(chrome.runtime.getURL(message))
.then(response => response.text())
.then(text => sendResponse(text));
/*
https://developer.chrome.com/docs/extensions/mv3/messaging/#simple
Note: The sendResponse callback is only valid if used synchronously,
or if the event handler returns true to indicate that it will respond asynchronously.
The sendMessage function's callback will be invoked automatically
if no handlers return true or if the sendResponse callback is garbage-collected.
*/
return true;
}
chrome.runtime.onMessage.addListener(on_message);
content_script.js
chrome.runtime.sendMessage("example.txt")
.then(response => {
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError);
}
else {
console.log(response);
}
});

Related

How to access iframes using scripting API?

I have a chrome extension I need to access all the iframes on the attached page using the scripting API
{
"manifest_version": 3,
"name": "My example extension",
"version": "0.0.1",
"permissions": ["tabs", "scripting"],
"devtools_page": "devtools.html",
"host_permissions": [
"<all_urls>"
]
}
I can run the script on the inspectedWindow easily enough
// devtools.html -> js
chrome.devtools.panels.create("Sample Panel", "icon.png", "/panel.html", panel => {
// code invoked on panel creation
});
// panel.html -> js
chrome.scripting.executeScript({
target: { tabId: chrome.devtools.inspectedWindow.tabId },
injectImmediately: true,
func: () => {
console.log('foo')
}
})
I want this script to run on the page and all children iframes immediately (before anything else is run, just like a content_script).
It seems this only runs on the host page. When I try to query for child iframes, it doesn't seem to find anything
> await chrome.tabs.query({ windowId: chrome.devtools.inspectedWindow.tabId })
// []
Did I miss a permission?

Load content script into all tabs, but only if the content script is not already loaded

Problem
My chrome extension needs to inject the content script into all tabs when switched on and then when any new tabs are loaded etc.
By adding the content script to the manifest file I can satisfy the second requirement of having it loaded in newly loaded tabs.
To make it also inject the content script into tabs as soon as the extension is loaded or refreshed (there is no popup), I am using chrome.scripting.executeScript.
Problem is each time the extension is turned off/on or refreshed, the content script is loaded in again and any DOM manipulation from content script occurs multiple times.
So from reading another post, I liked the idea of sending a message to the content script, if I don't get a response (undefined) then I inject the script into this tab, if I do get a response then do nothing.
In my background logs I get this error Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. which is what I would expect if the content script did not exist, but even when the content script is clearly being loaded, I still get the same error.
Any help greatly appreciated. And maybe this is not even the best approach for this, I am not sure. Ideally I would also want the extension to immediately stop working when it's turned off, but I haven't got around to this problem yet.
Manifest
{
"name": "Text highlighter",
"description": "Text highlighter",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"options_ui": {
"page": "options.html",
"open_in_tab": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": [ "styles.css" ]
}
],
"permissions": ["storage", "activeTab", "scripting", "tabs"],
"host_permissions": ["<all_urls>"]
}
self.oninstall = () => {
chrome.tabs.query({}, function(tabs) {
tabs.forEach((tab) => {
chrome.tabs.sendMessage(tab.id, true, (response) => {
console.log('response', response);
if(!response) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content.js']
});
chrome.scripting.insertCSS({
target: { tabId: tab.id },
files: ['styles.css']
});
}
});
});
});
};
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('request', request);
sendResponse(true);
return true;
});

save REQUEST body(payload) of POST REQUEST through chrome extension

we are looking to read/save a POST REQUEST's body/payload through chrome extension.
Scenario: a target webpage will submit a post request to server once in a while.
Requirement: chrome extension's popup.html has a button with id='startIntercept' and upon clicked we need to save the next firing post request's all details(headers, payload) to a session variable.
we only need to save what post request is been sent from current tab. we are not bothered what response we received for the post request.
through a source we came to know a way to achieve it by "Overwriting window.XMLHttpRequest.prototype.open method" by saving old open method to a variable and create a new open method to intercept any request and save it then pass parameters to old open method to get usual expected page functionality.
code used:
//popup.js
let startIntercept = document.getElementById("startIntercept");
startIntercept.addEventListener("click", async () => {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["startWork.js"],
});
});
//startWork.js
let oldXHROpen = window.XMLHttpRequest.prototype.open;
console.log('script injected');
window.XMLHttpRequest.prototype.open = function() {
this.addEventListener("load", function() {
const responseBody = this.responseText;
sessionStorage.setItem('payload', responseBody );
});
return oldXHROpen.apply(this, arguments);
};
//manifest.json
"name": "extTracker",
"description": "want to save post request details",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"host_permissions": ["<all_urls>"],
"permissions": ["storage", "activeTab", "scripting"],
"web_accessible_resources": [{
"resources": ["/assets/media/blinkSd.mp3"],
"matches": ["<all_urls>"],
"use_dynamic_url": true
}],
"action": {
"default_popup": "popup.html",
},
*once we have a working model we will enhance manifest file/filter requests to intercept accordingly
the above code fails to save/intercept any request made from current tab even through script 'startWork.js' is successfully injected.
why it fails?? any better approach to achieve this functionality?
Your Expertise and Time are very much appreciated. Thank You

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.

How to allow an extension for two domains only?

I've written a google chrome extension. It's okay and works now but I want the extension to be usebale only on two domains because it's written for these two websites only and is useless for others. There is a context menu only. For now it hasn't even popup, or action button in the top right corner (hidden by default). How can achieve this?
My current manifest.json:
{
"manifest_version": 2,
"background": {
"scripts": ["scripts/jquery.min.js", "scripts/background.js"]
},
"name": "Export Entries",
"description": "some descriptions here",
"version": "1.0",
"icons": {
"16": "images/logo.png",
"48": "images/logo.png",
"128": "images/logo.png"
},
"permissions": ["downloads", "tabs", "contextMenus", "http://my-own-domain-accessed-via-ajax-for-creating-some-files-there.com/*"],
"content_scripts": [{
"matches": ["*://allowed-domain1.com/*", "*://allowed-domain2.com/*"],
"css": ["styles/style.css"],
"js": ["scripts/jquery.min.js", "scripts/popup.js", "scripts/background.js"],
"run_at": "document_end"
}],
"web_accessible_resources": [
"images/logo.png"
]
}
As I understand the extension cannot be disabled absolutely, its process will run in background again. But it's not a problem. I just want to not display the context menu item on other websites.
background.js creates the context menu item and handles its click event:
function exportEntries(info, tab) {
if (info['linkUrl'].indexOf('domain1.com/user/') > -1) {
var user = info['linkUrl'].substr('27');
} else {
var user = null; // export all entries from this topic
}
$.ajax({
url: 'http://my-own-domain-which-creates-the-file.eu/exportEntries/create.php',
method: 'POST',
data: {
topic: tab.url,
user: user
}
}).done(function(url) {
forceDownload(url);
});
}
function forceDownload(url) {
var filename = url.replace(/^.*\/|\.[^.]*$/g, '');
chrome.downloads.download({
url: url,
saveAs: true
}, // options array
function(id) {
// callback function
}
);
};
document.addEventListener('DOMContentLoaded', function() {
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
}
});
});
create.php is on my own domain. It just gets the current page's URL and the user's nickname. Then export all entries from the given topic (i.e. page URL) for the given user, creates a file (.txt, .pdf etc.) and sends back url for downloading the file.
popup.html, popup.js, css file and other stuff is not used for now.
Remove all of your content scripts, they're useless, because the chrome.contextMenus API can only be used on the background page.
To limit the context menu entry to certain domains, pecify the documentUrlPatterns key when you create the context menu using the chrome.contextMenus.create:
chrome.contextMenus.create({
'title': 'Export Entries',
'contexts': ['link'],
'onclick': function(info, tab) {
exportEntries(info, tab);
},
'documentUrlPatterns': [
'*://allowed-domain1.com/*',
'*://allowed-domain2.com/*'
]
});
According to the content scripts documentation:
"If you want to inject the code only sometimes, use the permissions field instead, as described in Programmatic injection."
So instead of
"content_scripts": [
{
"matches": [ "http://allowed-domain.com" ]
}
],
use
permissions: [
"tabs", "http://allowed-domain.com"
],

Resources