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.
Related
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!
My chrome extension spawns a temp .html page. I want to manipulate the DOM of the sample.html page that was created, but can't. I can manipulate the DOM for any other page without issue. The problem seems to be with the fact my temp .html page resides within chrome-extension://
Error Message:
Unchecked runtime.lastError while running tabs.executeScript: Cannot access contents of url "chrome-extension://123/sample.html?id=100". Extension manifest must request permission to access this host.
Note: for simplicity sake I provided sample code that exhibits the same Error. Once loaded I can use the key combo to inject a div and some text into any webpage (Mac-> Cmd+Shift+P or PC Ctrl+Shift+P)
I've tried adding all possible permissions and even web_accessible_resources to the manifest.json. (I don't believe this to be the issue). I've tried different ways to inject the code into the sample.html by calling out the specific tabId, activeTab or even setting the tabId to null within the background file. I've read through stackoverflow, googled and looked around but came up short.
manifest.json
{
"manifest_version": 2,
"name": "sample1",
"description": "sample1",
"version": "0.0.1",
"browser_action":
{
"default_title": "sample"
},
"commands":
{
"saveImageCommand":
{
"suggested_key":
{
"default": "Ctrl+Shift+Z",
"mac": "Command+Shift+Z"
},
"description": "Toggle Save Image"
},
"playback":
{
"suggested_key":
{
"default": "Ctrl+Shift+P",
"mac": "Command+Shift+P"
},
"description": "load player Image"
}
},
"permissions": [
"tabs",
"activeTab",
"storage",
"<all_urls>",
"*://*/*"
],
"background":
{
"persistent": false,
"scripts": ["background.js"]
}
,
"web_accessible_resources": [
"chrome-extension://*/sample.html?id=*"
]
}
background.js
chrome.commands.onCommand.addListener(function(command) {
if (command === 'saveImageCommand') {
capturecurrent();
}
if (command === 'playback') {
chrome.tabs.executeScript(null, {
code: 'var divNode = document.createElement("div");divNode.setAttribute("id", "video1Div");var instructions = document.createTextNode("testing");divNode.appendChild(instructions);document.body.appendChild(divNode)'
});
}
});
chrome.browserAction.onClicked.addListener(function() {
chrome.tabs.captureVisibleTab(function(screenshotUrl) {
var viewTabUrl = chrome.extension.getURL('sample.html')
chrome.tabs.create({ url: viewTabUrl });
});
});
sample.html
<html>
<head></head>
<body>
<div id="firstDiv">firstDiv</div>
</body>
</html>
Expected Results:
For me to interact directly with the DOM on the temp sample.html page.
Note:
I don't want to build out buttons for DOM manipulation directly within the sample.html page. That defeats the purpose of this exercise. Esp since I want to use shortcut key combos to call this DOM manipulation (Mac-> Cmd+Shift+P or PC Ctrl+Shift+P)
Actual Results:
I am able to interact with the DOM on any normal website using the shortcut key combo but not the sample.html that URL starts with chrome-extension://
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.
Disclaimer: I am new to JavaScript and I have never developed a Chrome extension before.
I am trying to develop a Chrome extension that runs some JavaScript when the user selects some text on a page, right-clicks, then clicks a context menu button. I have determined (based on running it from the Chrome console) that the JavaScript I've written runs as expected. Now all is left is to make an extension.
I can get the extension to load, and I can get it to appear on the page and to appear to run. However, it doesn't seem to do anything, and the console doesn't return any output. (I read that I can't run inline JavaScript with event pages, hence using addListener.) Have I set up the context menu incorrectly? Is there an error (or several) in my script?
manifest.json
{
"name": "My Extension",
"description": "sample",
"version": "0.0.1",
"permissions": ["contextMenus"],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"manifest_version": 2
}
background.js
chrome.runtime.onInstalled.addListener(function() {
var context = "selection";
var title = "My Extension";
var id = chrome.contextMenus.create({"title": title, "contexts":[context],
"id": "context" + context});
});
chrome.contextMenus.onClicked.addListener(getSHA);
// Get file path of file to be staged
// Get SHA
function getSHA(){
stagedFile = window.getSelection().toString()
console.log(stagedFile)
baseURL = window.location.href.slice(0, -6);
prNumber = baseURL.slice(-4);
xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.github.com/repos/kubernetes/kubernetes.github.io/pulls/"+prNumber, false);
xhr.send();
json_data = JSON.parse(xhr.responseText);
shaValue = (json_data.head.sha)
console.log("SHA: "+shaValue)
getNetlify;
};
// Get Netlify URL
function getNetlify(){
xhr2 = new XMLHttpRequest();
xhr2.open("GET", "https://api.github.com/repos/kubernetes/kubernetes.github.io/commits/"+shaValue+"/status", false);
xhr2.send();
json_data2 = JSON.parse(xhr2.responseText, function(key, value) { if (key == "target_url" && value.includes("netlify")) { netlifyURL = value; }});
openStaging
};
// Stage file
function openStaging(){
window.open(netlifyURL+"/"+stagedFile)
};
You need to add a "content script" to your manifest.json. That is the kind of code that gets injected into the page to run. The background script didn't have access to the page at all. So, check out the documentation on content scripts. https://developer.chrome.com/extensions/content_scripts
You need to add the following piece to your code to your manifest.json:
"content_scripts": [
{
"matches": ["http://www.google.com/*"],
"css": ["mystyles.css"],
"js": ["myscript.js"]
}
]
With this code, any time the user goes to a site that "matches" the url I provided, then the extension will inject into that page mystyles.css and myscript.js. So... your pattern would be something like http*://*/* . That will inject the script onto any page that the user will go to.
Next, to accomplish what you are trying to accomplish, you don't need a background script. So you can remove that from your manifest.json.
So your manifest.json would look like this:
{
"name": "My Extension",
"description": "sample",
"version": "0.0.1",
"permissions": ["contextMenus"],
"content_scripts": [
{
"matches": ["http://www.google.com/*"],
"css": ["mystyles.css"],
"js": ["myscript.js"]
}
],
"manifest_version": 2
}
Then put your code into the myscript.js file (or whatever you want to call yours), and you should see this start to run on the page.
My extension's content script injects a script into gmail page via <script> element (main.js). The injected script needs some data from the settings stored in the extension's localStorage by options.js script of the options page.
The options page script can successfully use loadDomain() function that reads localStorage.domain value. This function is defined in a common functions script storage.js that is also injected on gmail page via <script> element along with main.js.
The problem is that loadDomain() returns undefined when called in the injected main.js instead of the actual values stored on the options page.
manifest.json:
"permissions": [
"tabs", "https://mail.google.com/*", "http://*/*, https://*/*"
],
"background": {
"scripts": ["js/background.js"],
"persistent": false
},
"browser_action": {
"default_icon": {
"38": "icon.png"
},
"default_title": "SalesUp",
"default_popup": "index.html"
},
"content_scripts": [
{
"matches": ["https://mail.google.com/*"],
"js": ["content.js"]
}
],
"web_accessible_resources": [
"js/jquery-1.10.2.min.js",
"js/gmail.js",
"main.js"
]
}
The chat discussion showed that loadDomain() was invoked from the <script>-injected main.js.
One part of the problem was caused by the fact that Chrome isolates the web page (with its scripts, also the injected ones) from the content scripts, as well as the background page. Another part was that localStorage is different on each domain (actually, origin), so whatever was stored inside the options page of the extension was not available in a content script that runs in the context of the web page and has access to its localStorage only, not the extension's localStorage.
The solution comprises two things:
instead of localStorage use chrome.storage.sync to store the extension settings or chrome.storage.local to store the temporary stuff that shouldn't be synced to Google servers.
use custom DOM-events to communicate between the injected script and the content script.
The code:
Injected main.js:
sending a request (detail key may be used to pass some data):
document.dispatchEvent(new CustomEvent("getDomains", {detail: {something: "hello"}}));
listening for a response:
document.addEventListener("receiveDomains", function(e) {
var domains = e.detail;
console.log("Received domains", domains);
...............
});
Content script content.js:
document.addEventListener("getDomains", function(e) {
chrome.storage.sync.get("domains", function(result) {
document.dispatchEvent(new CustomEvent("receiveDomains", {detail: result.domains}));
});
});
Options page options.js:
function save_options() {
chrome.storage.sync.set({domains: ["domain1", "domain2"]});
}