How to get DOM from debugging tab to extension script - google-chrome-extension

I'm looking for a bit of help here as the examples I've seen have only been from the tab to the extension and not the other way around.
I'm looking to grab the source code of a page/tab that I am debugging with a custom Chrome Extension. I want the extension to call a message and the response to be sent back to the extension panel javascript making the call.
Manifest
"permissions": [
"tabs",
"<all_urls>",
"debugger"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
background.js
chrome.browserAction.onClicked.addListener(function() {
chrome.tabs.query({active:true, windowId:chrome.windows.WINDOW_ID_CURRENT}, function(tabs) {
debuggee = {tabId:tabs[0].id};
chrome.debugger.attach(debuggee, version, onAttach.bind(null, tabs[0].id));
});
});
function onAttach(tabId) {
chrome.windows.create({url: "spy.html?" + tabId, type: "panel", width: 900, height: 700}, function(window) {
winId = window.id;
});
content.js
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.data == "getHTML") {
sendResponse({data: document.getElementById('header').innerHTML});
}
});
spy.html
<script src="spy.js" type="text/javascript"></script>
spy.js
window.addEventListener("load", function() {
chrome.debugger.sendCommand({tabId:tabId}, "DOM.getDocument");
chrome.debugger.onEvent.addListener(onEvent);
});
function onEvent(debuggeeId, message, params) {
if (message=="DOM.documentUpdated") {
chrome.tabs.sendMessage(tabId, {data: "getHTML"}, function(response) {console.log(response.data);});
}
Result
Port error: Could not establish connection. Receiving end does not exist. miscellaneous_bindings:235
chromeHidden.Port.dispatchOnDisconnect miscellaneous_bindings:235
Error in event handler for 'undefined': Cannot read property 'data' of undefined TypeError: Cannot read property 'data' of undefined
at chrome-extension://fpdkndicjblnkakkiiapbbdflkehjmgm/headers.js:132:91
at miscellaneous_bindings:279:11
at chrome.Event.dispatchToListener (event_bindings:387:21)
at chrome.Event.dispatch_ (event_bindings:373:27)
at chrome.Event.dispatch (event_bindings:393:17)
at Object.chromeHidden.Port.dispatchOnDisconnect (miscellaneous_bindings:238:27)
I get this error when I try to run it. What am I missing?

How are you capturing tabId of chrome.tabs.sendMessage(tabId,, can you post your full script to debug problem,if you are looking for a sample code for passing message from Chrome Extension to Debugging Tab check this.
References
Content Security Policy
tabs.query
tabs.sendMessage
extension.onMessage
manifest.json
Registered popup page and content scripts.
{
"name": "Pass message from Chrome Extension to Debugging Tab",
"version": "1",
"description": "http://stackoverflow.com/questions/14205155/how-can-i-pass-a-message-from-my-chrome-extension-to-debugging-tab",
"browser_action": {
"default_title": "Selected Text",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"<all_urls>"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["selection.js"]
}
],
"manifest_version": 2
}
popup.html
Ensured HTML Adheres to CSP
<!DOCTYPE html>
<html>
<head>
<style>
body {
width: 300px;
}
textarea {
width: 250px;
height: 100px;
}
</style>
<script src="popup.js"></script>
</head>
<body>
<button id="submit">Pass Message</button>
</body>
</html>
popup.js
Pass Message to Content Scripts.
function passMessage() {
//Select current tab to send message
chrome.tabs.query({"active":true,"currentWindow":true,"status":"complete","windowType":"normal"}, function(tabs) {
//It returns array so looping over tabs result
for(tab in tabs){
//Send Message to a tab
chrome.tabs.sendMessage(tabs[tab].id, {method: "Hi Content Script"});
}
});
}
// Bind On click event to passMessage() function
document.addEventListener("DOMContentLoaded",function (){
document.getElementById("submit").onclick = passMessage;
});
selection.js
Added a handler to catch messages sent from popup page
//Add a handler to handle message sent from popup.html
chrome.extension.onMessage.addListener(function(request, sender) {
console.log("Message "+request+" is recieved");
});
EDIT:
I got your code working after eliminates some deprecated API() like sendResponse
background.js
chrome.browserAction.onClicked.addListener(function () {
version = "1.0";
chrome.tabs.query({
active: true,
windowId: chrome.windows.WINDOW_ID_CURRENT
}, function (tabs) {
debuggee = {
tabId: tabs[0].id
};
chrome.debugger.attach(debuggee, version, onAttach.bind(null, tabs[0].id));
});
});
function onAttach(tabId) {
chrome.windows.create({
url: "spy.html?" + tabId,
type: "panel",
width: 900,
height: 700
}, function (window) {
winId = window.id;
});
}
content.js
chrome.extension.onMessage.addListener(function (request, sender) {
console.log("Message recieved");
if (request.data == "getHTML") {
chrome.extension.sendMessage({
"data": "Some Stuff"
});
}
});
spy.js
tabId = parseInt(window.location.search.substring(1));
window.addEventListener("load", function () {
chrome.debugger.sendCommand({
tabId: tabId
}, "DOM.getDocument");
chrome.debugger.onEvent.addListener(onEvent);
});
function onEvent(debuggeeId, message, params) {
if (message == "DOM.documentUpdated") {
chrome.tabs.sendMessage(tabId, {
"data": "getHTML"
});
}
}
chrome.extension.onMessage.addListener(function (response, sender) {
console.log(response);
});
How ever ensure you do not trigger developer tools manually during testing.

OK I've figured it out.
Since I need the DOM of the loaded page, I'm going to use the chrome.tabs.onUpdated.addListener in my background page to send the code when the page is loaded. This way I don't have to depend on the 2 way communication between the tab and extension.
Manifest
Removed content.js
background.js
Added the following
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (tabId != _tabId) {return;}
if (changeInfo.status == "complete") {
chrome.tabs.executeScript(tabId, {code:"var x = document.documentElement.innerHTML;x"}, function (r) {
chrome.extension.sendMessage(null, {"data": r[0]});
});
}
});
content.js
REMOVED
spy.js
Added the following
chrome.extension.onMessage.addListener(function(request, sender) {
console.log("Request.data: " + request.data);
});

Related

Wait for asynchronous request on background page [duplicate]

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.

Why my google extension has this problem "Uncaught (in promise) Error: The message port closed before a response was received." [duplicate]

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.

XMLHttpRequest prototype override not getting called

I am trying to write a Chrome extension to monitor the XHR response for a particular web site and log messages to the console based on the content of the response. So far I am just trying to get to the response but have not been successful yet.
Here is my manifest.json file:
{
"name": "XHR Listener",
"version": "0.0.1",
"manifest_version": 2,
"description": "Listening in on XHR responses",
"background": {
"scripts": ["background.js"],
"persistent": true
},
"browser_action": {
"default_title": "Listen!"
},
"permissions": [
"https://*/*",
"http://*/*"
]
}
Here is my background.js file:
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
And here is my inject.js file:
console.log('In inject.js');
(function() {
console.log('In the function()');
const dummySend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
console.log('inside send()');
const _onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function () {
console.log('inside onreadystatechange()');
if (this.readyState === 4 && this.status === 200) {
console.log(this.response);
}
}
if (_onreadystatechange) {
_onreadystatechange.apply(this, arguments);
}
}
dummySend.apply(this, arguments);
})();
When I click on the extension icon I get the "In inject.js" and "In the function()" messages in the console, but the other messages ("Inside send()" and "Inside onreadystatechange()") never get logged to the console even though there are multiple HMLHttpRequest actions showing up in the network tab of developer tools. What am I doing wrong?

Does executeScript still work after change window.location.href

when i change the window.location.href,the executeScript didn't work.why is this?
manifest.json is
{
"name": "Page Redder",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab","*://*/*"
],
"browser_action": {
"default_title": "Make this page red"
},
"background": {
"scripts": ["jquery-1.11.1.js","background.js"]
},
"manifest_version": 2
}
background.js is
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.id,{code:'window.location.href="http://www.google.com"'},function(){
chrome.tabs.executeScript(tab.id, {file:"test.js"}, function() {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError.message);
}
});
});
});
test.js is
alert("hello work")
The problem is that the file in the second executeScript is being injected after the code of the first executeScript has been executed, but before the window.location.href call has finished. See for yourself: add a breakpoint on the line calling the second chrome.tabs.executeScript, click your browser action and then wait for the page to load before resuming - the popup will work.
One way to solve this is to add a tabs.onUpdated listener. Then, when you click your browser action store what the tabId was. Inside the tabs.onUpdated listener you can execute test.js if the updated tabId matches the tabId set in your browser action. Quick example:
var activeTabId;
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.id, {code: 'window.location.href="http://www.google.com"'}, function (){
activeTabId = tab.id;
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(tabId === activeTabId) {
activeTabId = null; //To prevent executing the script multiple times
chrome.tabs.executeScript(tabId, {file:"test.js"});
}
});

chrome extension not working for secure sites https

I've written a chrome extension, but it doesn't seem to work for https sites. Its currently a background page that injects script into the page. It runs jquery and some libraries too. The only way I've found out so far to do this is to run a background page, and use chrome.tabs.executescript. If anyone knows a better way then that would help too.
I've added permissions to http and https sites, so i thought that would be sufficient. Please can someone help, thanks.
Manifest:
{
"name": "My First Extension",
"version": "1.0",
"description": "The first extension that I made.",
"background_page": "popup.html",
"permissions": ["tabs", "http://*/*", "https://*/*"]
}
popup.html
<script type="text/javascript">
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab)
{
if(changeInfo.status == "loading")
{
chrome.tabs.insertCSS(null, { file: "jquery-ui-1.8.10.custom.css" }, null);
chrome.tabs.executeScript(null, { file: "jquery.min.js" }, null);
chrome.tabs.executeScript(null, { file: "jquery-ui-1.8.10.custom.min.js" }, null);
chrome.tabs.executeScript(null, { file: "jquery.hotkeys-0.7.9.min.js" }, null);
chrome.tabs.executeScript(null, { file: "custom.js" }, null);
}
})
</script>
the file custom.js is where i do my coding.
thanks
Try adding "conent_scripts" to manifest.json:
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"css": ["empty.css"]
}]
You have to specify 'css' or 'file'. In my add-on, scripts are loaded dynamically, so I just use a dummy css file.
See also: http://code.google.com/chrome/extensions/content_scripts.html
Maybe there is a problem with dependencies. All calls to executeScript are asynchronous. So you can not assume that jquery is injected when you start injecting jquery hotkeys. You should better use something like this:
var runScripts = function(tabId, scripts, cb) {
var current = scripts.shift();
if (current) {
chrome.tabs.executeScript(tabId, {file: current}, function(a) {
console.log("Finished running script:", current);
runScripts(tabId, scripts, cb);
});
} else {
cb();
}
};
chrome.tabs.insertCSS(null, {file: "jquery-ui-1.8.10.custom.css"}, function () {
runScripts( null, ["jquery.min.js", "jquery-ui-1.8.10.custom.min.js", "jquery.hotkeys-0.7.9.min.js", "custom.js"], function() {});
});

Resources