Is there any way to get remote JS file to chrome extension?
My manifest.json looks like this:
{
"name": "My Extension",
"version": "0.1",
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["jquery.js", "main.js"],
"run_at": "document_end",
"all_frames": true
}
]
}
I want use one JavaScript API, which is limited by usage on selected domain, so I can't insert it simply to loaded page in Chrome like this:
$('head').append('<script src="http://example.com/myApi.js?key=%key%"></script>');
because the JS API alerted me, that I'm using it on URL, which I haven't gave them.
I want just use some functions from this remote JS. The API key can be fortunately registered and used on localhost.
But I don't have any clue, how to solve this problem - where to put the remote JS file to be able to use it.
I've got an idea to create a "virtual DOM", but perhaps it's not a solution, because the JS API code can't be executed.
Where to insert the code? Some background page??
Did you try :
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://example.com/myApi.js?key=%key%';
document.getElementsByTagName('head')[0].appendChild(script);
I use it in my Google Chrome extension and works nicely.
Try this script :
if(window.top == window && !document.getElementById('molseek'))
{
if (document && document.doctype && document.doctype.name && document.doctype.name.toLowerCase() == 'html') {
loadToolTip();
}
// Weird guest... but if we got an head element, try to validate even if no doctype...
else if (document && document.getElementsByTagName('head').length) {
loadToolTip();
}
// Weird guest #2... but if we got title element, try to validate even if no doctype nor head...
else if (document && document.getElementsByTagName('title').length) {
loadToolTip();
}
}
function loadToolTip(){
(function(s){
if(!window.molseek){
s.src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';
(document.getElementsByTagName("head").item(0)||document.body).appendChild(s)
}
})(document.createElement('script'));
(function(s){
if(!window.apture){
s.src='https://location';
(document.getElementsByTagName("head").item(0)||document.body).appendChild(s)
}
})(document.createElement('script'));
}
Currently can't find a way to host code for an extension remotely, but still allow that code to use chrome.* javascript calls.
Related
After the Chrome extension I'm working on is installed, or upgraded, the content scripts (specified in the manifest) are not re-injected so a page refresh is required to make the extension work. Is there a way to force the scripts to be injected again?
I believe I could inject them again programmatically by removing them from the manifest and then handling which pages to inject in the background page, but this is not a good solution.
I don't want to automatically refresh the user's tabs because that could lose some of their data. Safari automatically refreshes all pages when you install or upgrade an extension.
There's a way to allow a content script heavy extension to continue functioning after an upgrade, and to make it work immediately upon installation.
Install/upgrade
The install method is to simply iterate through all tabs in all windows, and inject some scripts programmatically into tabs with matching URLs.
ManifestV3
manifest.json:
"background": {"service_worker": "background.js"},
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"],
These host_permissions should be the same as the content script's matches.
background.js:
chrome.runtime.onInstalled.addListener(async () => {
for (const cs of chrome.runtime.getManifest().content_scripts) {
for (const tab of await chrome.tabs.query({url: cs.matches})) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: cs.js,
});
}
}
});
This is a simplified example that doesn't handle frames. You can use getAllFrames API and match the URLs yourself, see the documentation for matching patterns.
ManifestV2
Obviously, you have to do it in a background page or event page script declared in manifest.json:
"background": {
"scripts": ["background.js"]
},
background.js:
// Add a `manifest` property to the `chrome` object.
chrome.manifest = chrome.runtime.getManifest();
var injectIntoTab = function (tab) {
// You could iterate through the content scripts here
var scripts = chrome.manifest.content_scripts[0].js;
var i = 0, s = scripts.length;
for( ; i < s; i++ ) {
chrome.tabs.executeScript(tab.id, {
file: scripts[i]
});
}
}
// Get all windows
chrome.windows.getAll({
populate: true
}, function (windows) {
var i = 0, w = windows.length, currentWindow;
for( ; i < w; i++ ) {
currentWindow = windows[i];
var j = 0, t = currentWindow.tabs.length, currentTab;
for( ; j < t; j++ ) {
currentTab = currentWindow.tabs[j];
// Skip chrome:// and https:// pages
if( ! currentTab.url.match(/(chrome|https):\/\//gi) ) {
injectIntoTab(currentTab);
}
}
}
});
Historical trivia
In ancient Chrome 26 and earlier content scripts could restore connection to the background script. It was fixed http://crbug.com/168263 in 2013. You can see an example of this trick in the earlier revisions of this answer.
The only way to force a content script to be injected without refreshing the page is via programatic injection.
You can get all tabs and inject code into them using the chrome tabs API.
For example you can store a manifest version in local storage and every time check if the manifest version is old one (in background page), if so you can get all active tabs and inject your code programmatically, or any other solution that will make you sure that the extension is updated.
Get all tabs using:
chrome.tabs.query
and inject your code into all pages
chrome.tabs.executeScript(tabId, {file: "content_script.js"});
Try this in your background script. Many of the old methods have been deprecated now, so I have refactored the code. For my use I'm only installing single content_script file. If need you can iterate over
chrome.runtime.getManifest().content_scripts array to get all .js files.
chrome.runtime.onInstalled.addListener(installScript);
function installScript(details){
// console.log('Installing content script in all tabs.');
let params = {
currentWindow: true
};
chrome.tabs.query(params, function gotTabs(tabs){
let contentjsFile = chrome.runtime.getManifest().content_scripts[0].js[0];
for (let index = 0; index < tabs.length; index++) {
chrome.tabs.executeScript(tabs[index].id, {
file: contentjsFile
},
result => {
const lastErr = chrome.runtime.lastError;
if (lastErr) {
console.error('tab: ' + tabs[index].id + ' lastError: ' + JSON.stringify(lastErr));
}
})
}
});
}
Chrome has added a method to listen for the install or upgrade event of the extension. One can re-inject the content script when such an event occur.
https://developers.chrome.com/extensions/runtime#event-onInstalled
Due to https://bugs.chromium.org/p/chromium/issues/detail?id=168263, the connection between your content script and background script is severed. As others have mentioned, one way to get around this issue is by reinjecting a content script. A rough overview is detailed in this StackOverflow answer.
The main tricky part is that it's necessary to "destruct" your current content script before injecting a new content script. Destructing can be really tricky, so one way to reduce the amount of state you must destruct is by making a small reinjectable script, that talks to your main content script over the DOM.
can't you add ?ver=2.10 at the end of css or js you upgraded?
"content_scripts": [ {
"css": [ "css/cs.css?ver=2.10" ],
"js": [ "js/contentScript.js?ver=2.10" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_end"
} ],
I am trying to build a Chrome plugin (76.0.3809.132 version of Chrome) that:
Injects a JavaScript block in a web page (done)
Get the injected block to pass back message to the chrome plugin using the latest Chrome runtime functionality (problem)
I initially referred to the Chrome developer reference at: https://developer.chrome.com/extensions/messaging#external-webpage
Accordingly, my Manifest is:
{
"manifest_version": 2,
"name": "Message Passing",
"description": "Simple messaging plugin",
"version": "1.1",
"externally_connectable": {
"matches": ["*://*.myexample.com/*"]
},
"permissions": [
"activeTab",
"storage",
"tabs",
"*://*.myexample.com/*"
],
"content_scripts": [
{
"matches": ["*://*.myexample.com/*"],
"run_at": "document_start",
"js": ["myContent.js"]
}
],
"web_accessible_resources": ["injected.js"]
}
My content script:
var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
//injecting correctly till this point
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
console.log("In plugin");//never gets called
});
JavaScript within the web page that makes the call to plugin:
//the function called by my web page, sending a JSON or text message
async function sendMessageToPlugin(dataToSend){
console.log('Send to plugin');//this is getting printed
var extID = 'abcdefghijklmnoabcdefhijklmnoabc';
chrome.runtime.sendMessage(extID, dataToSend);
}
No error, no result. I have tried different things, trying to create a background script to listen instead of listening in content script, trying to create a port for persistent messages instead, using "onMessage" instead of "onMessageExternal" per various older posts on the forum, but nothing works :(. What am I doing wrong? Can somebody at least point me towards any actually working example of a web page successfully communicating with the Chrome plugin? Would be good to know what Manifest was used and which part of the extension listened to the message successfully (content script or background). Thank you for your help!
Update 1:
I had some success with using the method described at https://developer.chrome.com/extensions/content_scripts#host-page-communication. Is this the only way of doing this? I would prefer to do it using externally connected functionality, but happy to keep testing this method and run with it if it works.
Update 2:
This code finally works for me:
In my content script-
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
//if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: ");
console.log(event.data);
//}
}, false);
And in my injected script:
async function sendMessageToPlugin(dataToSend) {
console.log('Send to plugin');
window.postMessage(dataToSend, "*");
}
I am sure Jafar's solution will work as well (I will try it and update!), but for now I am sticking with this one. Thank you Jafar for taking out time to reply!
I'm making a WebExtension for Chrome and Firefox that adds more information to GitHub. It's supposed to be faster than existing extensions.
I have my manifest set up like Mozilla's documentation recommends.
{
"manifest_version": 2,
"name": "GitHub Extended",
"version": "0.0.1",
"description": "Adds information to GitHub normally accessible only by the API.",
"permissions": [
"https://github.com/*"
],
"content_scripts": [
{
"all_frames": true,
"run_at": "document_start",
"matches": [
"https://github.com/*"
],
"js": [
"source/github.js",
"source/repository.js"
]
}
]
}
When the page is loaded, the content scripts are injected. The file github.js is a light wrapper around GitHub's API, and repository.js is the code to modfy the DOM of the main repository root page.
The most important code in there is this the preloader, which makes an API request while the page is loading and waits for both events to complete before adding to the DOM.
While this current code works fine in Chrome, in Firefox it simply does nothing. I tried testing it by putting console.log("I'm loaded!"); in repository.js. Nothing is printed. Why is this code not working in Firefox?
function beginPreload() {
console.log("Test from preload scope!");
let urlMatch = window.location.pathname.match(/\/([\w-]+)\/([\w-]+)/);
console.log(urlMatch);
Promise.all([
getSortedReleases(urlMatch[1], urlMatch[2]),
documentReady()
]).then((values) => {
let releaseJson = values[0];
let actionsEl = document.getElementsByClassName("pagehead-actions")[0];
let dlCount = 0;
for (release of releaseJson)
for (asset of release.assets)
dlCount += asset.download_count;
let buttonEl = createDownloadButton(
releaseJson[0].html_url,
window.location.pathname + "/releases",
formatNum(dlCount)
);
actionsEl.appendChild(buttonEl);
});
}
beginPreload();
console.log("Test from global scope!");
This was the solution.
"permissions": [
"https://api.github.com/*"
]
All that needed to happen was add permission for the extension to use GitHub's API. AFAIK, this is only required for content scripts using XHR.
You need to go step by step and first ask yourself if script is really injected in the FF github page: remove everything thing from your contentScript, reload extension and check your FF console. If you see the log then start adding code progressively until it breaks, if not you have a problem with your build content.
I am in the process of building a Chrome extension, and for the whole thing to work the way I would like it to, I need an external JavaScript script to be able to detect if a user has my extension installed.
For example: A user installs my plugin, then goes to a website with my script on it. The website detects that my extension is installed and updates the page accordingly.
Is this possible?
Chrome now has the ability to send messages from the website to the extension.
So in the extension background.js (content.js will not work) add something like:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request) {
if (request.message) {
if (request.message == "version") {
sendResponse({version: 1.0});
}
}
}
return true;
});
This will then let you make a call from the website:
var hasExtension = false;
chrome.runtime.sendMessage(extensionId, { message: "version" },
function (reply) {
if (reply) {
if (reply.version) {
if (reply.version >= requiredVersion) {
hasExtension = true;
}
}
}
else {
hasExtension = false;
}
});
You can then check the hasExtension variable. The only drawback is the call is asynchronous, so you have to work around that somehow.
Edit:
As mentioned below, you'll need to add an entry to the manifest.json listing the domains that can message your addon. Eg:
"externally_connectable": {
"matches": ["*://localhost/*", "*://your.domain.com/*"]
},
2021 Update:
chrome.runtime.sendMessage will throw the following exception in console if the extension isn't installed or it's disabled.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist
To fix this, add this validation inside the sendMessage callback
if (chrome.runtime.lastError) {
// handle error
}
I am sure there is a direct way (calling functions on your extension directly, or by using the JS classes for extensions), but an indirect method (until something better comes along):
Have your Chrome extension look for a specific DIV or other element on your page, with a very specific ID.
For example:
<div id="ExtensionCheck_JamesEggersAwesomeExtension"></div>
Do a getElementById and set the innerHTML to the version number of your extension or something. You can then read the contents of that client-side.
Again though, you should use a direct method if there is one available.
EDIT: Direct method found!!
Use the connection methods found here: https://developer.chrome.com/extensions/extension#global-events
Untested, but you should be able to do...
var myPort=chrome.extension.connect('yourextensionid_qwerqweroijwefoijwef', some_object_to_send_on_connect);
Another method is to expose a web-accessible resource, though this will allow any website to test if your extension is installed.
Suppose your extension's ID is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and you add a file (say, a transparent pixel image) as test.png in your extension's files.
Then, you expose this file to the web pages with web_accessible_resources manifest key:
"web_accessible_resources": [
"test.png"
],
In your web page, you can try to load this file by its full URL (in an <img> tag, via XHR, or in any other way):
chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/test.png
If the file loads, then the extension is installed. If there's an error while loading this file, then the extension is not installed.
// Code from https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/8ArcsWMBaM4/2GKwVOZm1qMJ
function detectExtension(extensionId, callback) {
var img;
img = new Image();
img.src = "chrome-extension://" + extensionId + "/test.png";
img.onload = function() {
callback(true);
};
img.onerror = function() {
callback(false);
};
}
Of note: if there is an error while loading this file, said network stack error will appear in the console with no possibility to silence it. When Chromecast used this method, it caused quite a bit of controversy because of this; with the eventual very ugly solution of simply blacklisting very specific errors from Dev Tools altogether by the Chrome team.
Important note: this method will not work in Firefox WebExtensions. Web-accessible resources inherently expose the extension to fingerprinting, since the URL is predictable by knowing the ID. Firefox decided to close that hole by assigning an instance-specific random URL to web accessible resources:
The files will then be available using a URL like:
moz-extension://<random-UUID>/<path/to/resource>
This UUID is randomly generated for every browser instance and is not your extension's ID. This prevents websites from fingerprinting the extensions a user has installed.
However, while the extension can use runtime.getURL() to obtain this address, you can't hard-code it in your website.
I thought I would share my research on this.
I needed to be able to detect if a specific extension was installed for some file:/// links to work.
I came across this article here
This explained a method of getting the manifest.json of an extension.
I adjusted the code a bit and came up with:
function Ext_Detect_NotInstalled(ExtName, ExtID) {
console.log(ExtName + ' Not Installed');
if (divAnnounce.innerHTML != '')
divAnnounce.innerHTML = divAnnounce.innerHTML + "<BR>"
divAnnounce.innerHTML = divAnnounce.innerHTML + 'Page needs ' + ExtName + ' Extension -- to intall the LocalLinks extension click here';
}
function Ext_Detect_Installed(ExtName, ExtID) {
console.log(ExtName + ' Installed');
}
var Ext_Detect = function (ExtName, ExtID) {
var s = document.createElement('script');
s.onload = function () { Ext_Detect_Installed(ExtName, ExtID); };
s.onerror = function () { Ext_Detect_NotInstalled(ExtName, ExtID); };
s.src = 'chrome-extension://' + ExtID + '/manifest.json';
document.body.appendChild(s);
}
var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
if (is_chrome == true) {
window.onload = function () { Ext_Detect('LocalLinks', 'jllpkdkcdjndhggodimiphkghogcpida'); };
}
With this you should be able to use Ext_Detect(ExtensionName,ExtensionID) to detect the installation of any number of extensions.
Another possible solution if you own the website is to use inline installation.
if (chrome.app.isInstalled) {
// extension is installed.
}
I know this an old question but this way was introduced in Chrome 15 and so I thought Id list it for anyone only now looking for an answer.
Here is an other modern approach:
const checkExtension = (id, src, callback) => {
let e = new Image()
e.src = 'chrome-extension://'+ id +'/'+ src
e.onload = () => callback(1), e.onerror = () => callback(0)
}
// "src" must be included to "web_accessible_resources" in manifest.json
checkExtension('gighmmpiobklfepjocnamgkkbiglidom', 'icons/icon24.png', (ok) => {
console.log('AdBlock: %s', ok ? 'installed' : 'not installed')
})
checkExtension('bhlhnicpbhignbdhedgjhgdocnmhomnp', 'images/checkmark-icon.png', (ok) => {
console.log('ColorZilla: %s', ok ? 'installed' : 'not installed')
})
I used the cookie method:
In my manifest.js file I included a content script that only runs on my site:
"content_scripts": [
{
"matches": [
"*://*.mysite.co/*"
],
"js": ["js/mysite.js"],
"run_at": "document_idle"
}
],
in my js/mysite.js I have one line:
document.cookie = "extension_downloaded=True";
and in my index.html page I look for that cookie.
if (document.cookie.indexOf('extension_downloaded') != -1){
document.getElementById('install-btn').style.display = 'none';
}
You could have the extension set a cookie and have your websites JavaScript check if that cookie is present and update accordingly. This and probably most other methods mentioned here could of course be cirvumvented by the user, unless you try and have the extension create custom cookies depending on timestamps etc, and have your application analyze them server side to see if it really is a user with the extension or someone pretending to have it by modifying his cookies.
There's another method shown at this Google Groups post. In short, you could try detecting whether the extension icon loads successfully. This may be helpful if the extension you're checking for isn't your own.
Webpage interacts with extension through background script.
manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": true
},
"externally_connectable": {
"matches": ["*://(domain.ext)/*"]
},
background.js:
chrome.runtime.onMessageExternal.addListener(function(msg, sender, sendResponse) {
if ((msg.action == "id") && (msg.value == id))
{
sendResponse({id : id});
}
});
page.html:
<script>
var id = "some_ext_id";
chrome.runtime.sendMessage(id, {action: "id", value : id}, function(response) {
if(response && (response.id == id)) //extension installed
{
console.log(response);
}
else //extension not installed
{
console.log("Please consider installig extension");
}
});
</script>
Your extension could interact with the website (e.g. changing variables) and your website could detect this.
But there should be a better way to do this. I wonder how Google is doing it on their extension gallery (already installed applications are marked).
Edit:
The gallery use the chrome.management.get function. Example:
chrome.management.get("mblbciejcodpealifnhfjbdlkedplodp", function(a){console.log(a);});
But you can only access the method from pages with the right permissions.
A lot of the answers here so far are Chrome only or incur an HTTP overhead penalty. The solution that we are using is a little different:
1. Add a new object to the manifest content_scripts list like so:
{
"matches": ["https://www.yoursite.com/*"],
"js": [
"install_notifier.js"
],
"run_at": "document_idle"
}
This will allow the code in install_notifier.js to run on that site (if you didn't already have permissions there).
2. Send a message to every site in the manifest key above.
Add something like this to install_notifier.js (note that this is using a closure to keep the variables from being global, but that's not strictly necessary):
// Dispatch a message to every URL that's in the manifest to say that the extension is
// installed. This allows webpages to take action based on the presence of the
// extension and its version. This is only allowed for a small whitelist of
// domains defined in the manifest.
(function () {
let currentVersion = chrome.runtime.getManifest().version;
window.postMessage({
sender: "my-extension",
message_name: "version",
message: currentVersion
}, "*");
})();
Your message could say anything, but it's useful to send the version so you know what you're dealing with. Then...
3. On your website, listen for that message.
Add this to your website somewhere:
window.addEventListener("message", function (event) {
if (event.source == window &&
event.data.sender &&
event.data.sender === "my-extension" &&
event.data.message_name &&
event.data.message_name === "version") {
console.log("Got the message");
}
});
This works in Firefox and Chrome, and doesn't incur HTTP overhead or manipulate the page.
You could also use a cross-browser method what I have used.
Uses the concept of adding a div.
in your content script (whenever the script loads, it should do this)
if ((window.location.href).includes('*myurl/urlregex*')) {
$('html').addClass('ifextension');
}
in your website you assert something like,
if (!($('html').hasClass('ifextension')){}
And throw appropriate message.
If you have control over the Chrome extension, you can try what I did:
// Inside Chrome extension
var div = document.createElement('div');
div.setAttribute('id', 'myapp-extension-installed-div');
document.getElementsByTagName('body')[0].appendChild(div);
And then:
// On web page that needs to detect extension
if ($('#myapp-extension-installed-div').length) {
}
It feels a little hacky, but I couldn't get the other methods to work, and I worry about Chrome changing its API here. It's doubtful this method will stop working any time soon.
If you're trying to detect any extension from any website,
This post helped: https://ide.hey.network/post/5c3b6c7aa7af38479accc0c7
Basically, the solution would be to simply try to get a specific file (manifest.json or an image) from the extension by specifying its path. Here's what I used. Definitely working:
const imgExists = function(_f, _cb) {
const __i = new Image();
__i.onload = function() {
if (typeof _cb === 'function') {
_cb(true);
}
}
__i.onerror = function() {
if (typeof _cb === 'function') {
_cb(false);
}
}
__i.src = _f;
__i = null;
});
try {
imgExists("chrome-extension://${CHROME_XT_ID}/xt_content/assets/logo.png", function(_test) {
console.log(_test ? 'chrome extension installed !' : 'chrome extension not installed..');
ifrm.xt_chrome = _test;
// use that information
});
} catch (e) {
console.log('ERROR', e)
}
Here is how you can detect a specific Extension installed and show a warning message.
First you need to open the manifest file of the extension by going to chrome-extension://extension_id_here_hkdppipefbchgpohn/manifest.json and look for any file name within "web_accessible_resources" section.
<div class="chromewarning" style="display:none">
<script type="text/javascript">
$.get("chrome-extension://extension_id_here_hkdppipefbchgpohn/filename_found_in_ web_accessible_resources.png").done(function () {
$(".chromewarning").show();
}).fail(function () {
// alert("failed.");
});
</script>
<p>We have detected a browser extension that conflicts with learning modules in this course.</p>
</div>
Chrome Extension Manifest v3:
const isFirefox = chrome.runtime.OnInstalledReason.CHROME_UPDATE != "chrome_update";
For FireFox, I believe chrome.runtime.OnInstalledReason.BROWSER_UPDATE will be "browser_update": https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/OnInstalledReason
Is there a way to access the list of resources that the browser requested (the ones found in this Chrome inspector's network panel)?
I would like to be able to iterate through these fetched resources to show the domains that have been accessed, something like:
for (var i = 0; i < window.navigator.resources.length; i++) {
var resource = window.navigator.resources[i];
console.log(resource); //=> e.g. `{domain: "www.google-analytics.com", name: "ga.js"}`
}
Or, maybe there is some event to write a handler for, such as:
window.navigator.onrequest = function(resource) {
console.log(resource); //=> e.g. `{domain: "www.google-analytics.com", name: "ga.js"}`
}
It doesn't need to work cross browser, or even be possible using client-side JavaScript. Just being able to access this information in any way would work (maybe there's some way to do this using phantomjs or watching network traffic from a shell/node script). Any ideas?
You can do this, but you will need to use Chrome extensions.
Chrome extensions have a lot of sandbox-style security. Communication between the Chrome extension and the web page is a multi-step process. Here's the most concise explanation I can offer with a full working example at the end:
A Chrome extension has full access to the chrome.* APIs, but a Chrome extension cannot communicate directly with the web page JS nor can the web page JS communicate directly with the Chrome extension.
To bridge the gap between the Chrome extension and the web page, you need to use a content script . A content script is essentially JavaScript that is injected at the window scope of the targeted web page. The content script cannot invoke functions nor access variables that are created by the web page JS, but they do share access to the same DOM and therefore events as well.
Because directly accessing variables and invoking functions is not allowed, the only way the web page and the content script can communicate is through firing custom events.
For example, if I wanted to pass a message from the Chrome extension to the page I could do this:
content_script.js
document.getElementById("theButton").addEventListener("click", function() {
window.postMessage({ type: "TO_PAGE", text: "Hello from the extension!" }, "*");
}, false);
web_page.js
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "TO_PAGE")) {
alert("Received from the content script: " + event.data.text);
}
}, false);
`4. Now that you can send a message from the content script to the web page, you now need the Chrome extension gather up all the network info you want. You can accomplish this through a couple different modules, but the most simple option is the webRequest module (see background.js below).
`5. Use message passing to relay the info on the web requests to the content script and then on to the web page JavaScript.
Visually, you can think of it like this:
Full working example:
The first three files comprise your Google Chrome Extension and the last file is the HTML file you should upload to http:// web space somewhere.
icon.png
Use any 16x16 PNG file.
manifest.json
{
"name": "webRequest Logging",
"description": "Displays the network log on the web page",
"version": "0.1",
"permissions": [
"tabs",
"debugger",
"webRequest",
"http://*/*"
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_title": "webRequest Logging"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content_script.js"]
}
],
"manifest_version": 2
}
background.js
var aNetworkLog = [];
chrome.webRequest.onCompleted.addListener(function(oCompleted) {
var sCompleted = JSON.stringify(oCompleted);
aNetworkLog.push(sCompleted);
}
,{urls: ["http://*/*"]}
);
chrome.extension.onConnect.addListener(function (port) {
port.onMessage.addListener(function (message) {
if (message.action == "getNetworkLog") {
port.postMessage(aNetworkLog);
}
});
});
content_script.js
var port = chrome.extension.connect({name:'test'});
document.getElementById("theButton").addEventListener("click", function() {
port.postMessage({action:"getNetworkLog"});
}, false);
port.onMessage.addListener(function(msg) {
document.getElementById('outputDiv').innerHTML = JSON.stringify(msg);
});
And use the following for the web page (named whatever you want):
<!doctype html>
<html>
<head>
<title>webRequest Log</title>
</head>
<body>
<input type="button" value="Retrieve webRequest Log" id="theButton">
<div id="outputDiv"></div>
</head>
</html>
Big shoutout to #Elliot B.
I essentially used what he did but I wanted events to trigger in the content script rather than listeners triggering in the background. For whatever reason, I was unable to connect to the port from the background script so this is what I came up with.
PS: you need jquery.js in the extension folder to make this work.
manifest.json
{
"manifest_version": 2,
"name": "MNC",
"version": "0.0.1",
"description": "Monitor Network Comms",
"permissions":["webRequest","*://*/"],
"content_scripts": [{
"matches": ["<all_urls>"],
"run_at": "document_start",
"js": ["content.js",
"jquery.js"]
}],
"background": {
"scripts": ["background.js"]
}
}
background.js
var aNetworkLog = [];
chrome.webRequest.onResponseStarted.addListener(
function(oCompleted) {
var sCompleted = JSON.stringify(oCompleted);
aNetworkLog.push(sCompleted);
},{urls: ["https://*/*"]}
);
chrome.extension.onConnect.addListener(function (port) {
chrome.webRequest.onResponseStarted.addListener(
function(){
port.postMessage({networkLog:JSON.stringify(aNetworkLog)});
},{urls: ["https://*/*"]}
);
port.onMessage.addListener(function (message) {
if (message.disconnect==true) {
port.disconnect();
}
});
});
content.js
div = $('<div id="outputDiv" style="float:left;max-width:fit-content;position:fixed;display:none;"></div>').appendTo(document.body);
var port = chrome.extension.connect({name:'networkLogging'});
port.onMessage.addListener(function (message) {
if (message.networkLog) {
div[0].innerHTML = message.networkLog;
}
});
observer = new WebKitMutationObserver(function(mutation,observer){
JSON.parse(mutation[0]['target'].innerHTML).forEach(function(item){
JSON.parse(item);
})
});
observer.observe(div[0],{childList:true});
This is definitely not the most efficient way of doing things but it works for me. Thought that I would add it in here just in case someone is needing it.