I'm totally at a loss here. I want to get html content from tabs in Chrome.
manifest.json
{
"manifest_version": 2,
"name": "Test",is a test.",
"version": "1.0",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"background": {
"scripts": ["main.js"],
"persistent": false
},
"permissions": [
"tabs",
"https://www.google.com"
]
}
main.js
var timerObj = new Timer({'interval':5000});
chrome.runtime.onStartup.addListener(timerObj.start(mainF));
function mainF() {
chrome.tabs.query( {} ,function (tabs) {
for (var i = 0; i < tabs.length; i++) {
var url = tabs[i].url;
if (url != null) {
console.log(tabs[i].url);
//I want to get html source here
}
}
});
};
function Timer( obj ){
The last line function Timer( obj ){ is truncated for brevity. console.log(tabs[i].url); is there for testing. For each tab, I wish to get the html source. With that source, I'll parse for tags and other content. I've seen other resources mentioning sendMessage and onMessage, but I'm not really getting it. Many other resources refer to the deprecated sendRequest and onRequest.
To my knowledge, there are three ways to implement this.
chrome.tabs.executeScript. We can use Programming Injection to inject content script into web page, in the callback we can get the returned value.
Content Script and Message Passing. We can also inject content script in a way of manifest.json, then use chrome.runtime.sendMessage and chrome.runtime.onMessage to transfer the data.
XMLHttpRequest. Yes, this is also a way, we can directly make an ajax call in background page to get the source code of web page, because we could easily get the url. Obviously we need to start another http request compared with above two methods, but this is also a way.
In below sample code, I just use browserAction to trigger the event, then you can switch different methods to get the source code of web page by commenting out other two methods and reserve only one.
manifest.json
{
"manifest_version": 2,
"name": "Test is a test.",
"version": "1.0",
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
]
}
],
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"browser_action": {
"default_title": "title"
},
"permissions": [
"tabs",
"<all_urls>"
]
}
background.js
chrome.browserAction.onClicked.addListener(function () {
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
var id = tabs[i].id;
var url = tabs[i].url;
//method1(id);
method2(id);
//method3(url);
}
});
});
function method1(tabId) {
chrome.tabs.executeScript(tabId, { "code": "document.documentElement.outerHTML;" }, function (result) {
console.log(result);
});
}
function method2(id) {
chrome.tabs.sendMessage(id, {action: "getSource"}, function(response) {
console.log(response.sourceCode);
});
}
function method3(url) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
console.log(xhr.responseText);
};
xhr.open("GET", url);
xhr.send();
}
content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action === "getSource") {
sendResponse({sourceCode: document.documentElement.outerHTML});
}
});
Related
I put two pieces of code.
The first contains the chrome extension manifest version 2 files.
Here if I click on anchor with href pointed to zip file, then extension redirect to page from extension.
This is a worked example.
I am trying to achieve this for chrome extension with manifest version 3.
This is a second pieces of code.
First part.
Extension manifest version 2
manifest.json
{
"name": "Test app mv2",
"version": "0.1",
"manifest_version": 2,
"description": "test mv2",
"background": {
"scripts": [
"background.js"
]
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"icons": {
"128": "128.png"
},
"permissions": [
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
"web_accessible_resources": [
"web/main.html"
]
}
background.js
function getHeaderFromHeaders(headers, headerName) {
for (var i=0; i<headers.length; ++i) {
var header = headers[i];
if (header.name.toLowerCase() === headerName) {
return header;
}
}
}
function isAllowed(details) {
var header = getHeaderFromHeaders(details.responseHeaders, 'content-type');
if (header) {
var headerValue = header.value.toLowerCase().split(';',1)[0].trim();
var mimeTypes = [
'application/zip'
];
return (mimeTypes.indexOf(headerValue) !== -1);
}
}
chrome.webRequest.onHeadersReceived.addListener(
function(details) {
if (details.method !== 'GET') {
// Don't intercept POST requests until http://crbug.com/104058 is fixed.
return;
}
if (!isAllowed(details)) {
return;
}
return { redirectUrl: chrome.runtime.getURL('web/main.html') };
},
{
urls: [
'<all_urls>'
],
types: ['main_frame', 'sub_frame']
},
['blocking','responseHeaders']
);
Full source for mv2
Second part.
Extension manifest version 3
manifest.json
{
"name": "Test app mv3",
"manifest_version": 3,
"version": "0.1",
"background": {
"service_worker": "./background.js"
},
"action": {
"default_title": "SW3"
},
"host_permissions": [
"<all_urls>"
],
"permissions": [
"webRequest",
"declarativeNetRequest",
"declarativeNetRequestFeedback",
"declarativeNetRequestWithHostAccess"
],
"web_accessible_resources": [{
"resources": ["web/main.html"],
"matches": ["<all_urls>"]
}],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}
background.js
function getHeaderFromHeaders(headers, headerName) {
for (var i=0; i<headers.length; ++i) {
var header = headers[i];
if (header.name.toLowerCase() === headerName) {
return header;
}
}
}
function isAllowed(details) {
var header = getHeaderFromHeaders(details.responseHeaders, 'content-type');
if (header) {
var headerValue = header.value.toLowerCase().split(';',1)[0].trim();
var mimeTypes = [
'application/zip'
];
return (mimeTypes.indexOf(headerValue) !== -1);
}
}
chrome.webRequest.onHeadersReceived.addListener(
function(details) {
if (details.method !== 'GET') {
return;
}
if (!isAllowed(details)) {
return;
}
chrome.declarativeNetRequest.updateSessionRules({
addRules: [{
'id': 2001,
'priority': 1,
'action': {
'type': 'redirect',
'redirect': {
url: chrome.runtime.getURL('web/main.html')
}
},
'condition': {
'urlFilter': details.url,
'resourceTypes': ['main_frame']
}
}],
removeRuleIds: [2001]
});
return { redirectUrl: chrome.runtime.getURL('web/main.html') };
},
{
urls: [
'<all_urls>'
],
types: ['main_frame', 'sub_frame']
},
['responseHeaders']
);
Full source for mv3
For extension with mv3, above code achieved similar action as code for mv2.
The difference is that: when I click on anchor that pointed to zip file, then on the first click the dialog "save as" is shown and if I click on same zip anchor for second time, then redirect occurs.
For other zip files above actions are repeated.
How I can modify mv3 code to achieve same results as mv2?
This is still possible, but requires abandoning the chrome.webRequest API in favor of chrome.debugger. This allows your extension talk to the CDP protocol, where you can hook into the request/response lifecycle.
The first step is to attach the debugger the tabId in question, and send enable commands (if required) for the APIs you are using. Here you can find many examples of how to do this. For example, let's assume we want to use Fetch, so we need to send Fetch.enable.
Now, if you want to modify a request/response before it is processed, on a high level you need to:
Set up a handler for Fetch.requestPaused, configuring your target URL patterns (or * for all).
Check the request stage as described in the doc.
If Request: modify headers, and send Fetch.continueRequest.
If Response: Get the response body by sending Fetch.getResponseBody. If desired, modify body/status/headers. Fulfill the response to client by sending Fetch.fulfillRequest.
Helpful:
https://grep.app/search?q=chrome.debugger.sendCommand
https://grep.app/search?q=Fetch.fulfillRequest
I am using this approach successfully in a chrome extension project. MV3 killed the native extension request API (or at least, severely limited it) so for now this is the only option I am aware of. The downside is, an annoying bar appears under the tab, saying this browser is currently being debugged by $EXT_NAME.
I know there are similar questions and I've tried almost everything in them. I'm trying to build a chrome extension and it needs to pass a message from content.js to background.js.
The code:
background.js
var xstext;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
xstext=request.stext;
});
var wtf = "https://www.google.com/search?q="+xstext;
chrome.commands.onCommand.addListener(function(command) {
if(command=="ntab"){
chrome.tabs.create({ url: wtf});
}
});
content.js
var text = window.getSelection();
var stext=text.toString();
chrome.runtime.sendMessage({stext: stext});
manifest.json
"manifest_version": 2,
"name": "vind",
"version": "1.0",
"description": "Search stuff easily!",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": true,
"run_at": "document_start",
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png"
},
"default_popup": "popup.html"
},
"commands": {
"ntab": {
"suggested_key": {
"default": "Alt+G",
"windows": "Alt+G",
"mac": "Alt+G",
"chromeos": "Alt+G",
"linux": "Alt+G"
},
"description": "New tab for the query"
}
}
}
I want to pass the selected text from content.js to background.js, I have tried adding return: true; in the listener to no avail. I'm getting 'undefined' added to the main string, so nothing seems to get passed. what should I do?
This approach won't work because 1) your content script is saving the text at the moment it runs which happens just one time at page load and 2) since the background script is not persistent it'll unload after receiving a message and xstext will disappear.
Do the reverse: ask the content script when the hotkey is pressed.
background.js, entire contents:
chrome.commands.onCommand.addListener(command => {
if (command === 'ntab') {
chrome.tabs.query({active: true, lastFocusedWindow: true}, ([tab]) => {
if (!tab) return;
chrome.tabs.sendMessage(tab.id, 'getText', text => {
chrome.tabs.create({
url: 'https://www.google.com/search?q=' + encodeURIComponent(text),
});
});
});
}
});
content.js, entire contents:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getText' && document.hasFocus()
&& (!document.activeElement || !/^I?FRAME$/.test(document.activeElement.tagName))) {
sendResponse(getSelection().toString());
}
});
P.S. An advanced solution would be to run the content script on demand (using chrome.tabs.executeScript in background.js onCommand listener) so you can remove content_scripts section from manifest.json and use activeTab permission instead.
I try to send message from an iframe loaded from my extension to my extension (background script or content script).
The created Iframe is loaded from the extension via a content script.
I am searching for a way to communicate but all my attempts failed...
Manifest.json
{
"author": "***********",
"background": {
"page": "back/background.html",
"persistent": true
},
"browser_action": {
"default_title": "***",
"default_popup": "./popup/popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["./content/allurls.js"],
"all_frames":true
},
{
"matches": ["<all_urls>"],
"js": ["./banner/confirm_banner.js"]
}
],
"web_accessible_resources": [
"frame.html"
],
"description": "oui",
"manifest_version": 2,
"name": "***",
"permissions": ["tabs"],
"version": "1.0"
}
Confirm_banner.js (load the iframe)
var extensionOrigin = 'chrome-extension://' + chrome.runtime.id;
window.onload = load_iframe();
function load_iframe()
{
if (!location.ancestorOrigins.contains(extensionOrigin))
{
var iframe = document.createElement('iframe');
iframe.src = chrome.runtime.getURL('../frame.html');
iframe.style.cssText = 'position:fixed;top:0;left:0;display:block;' +
'width:100%;height:40px;';
document.body.appendChild(iframe);
}
}
Frame.js (script linked with frame.html)
$(document).ready(function()
{
$('#jamais').click(function()
{
send_message("BANNER", "jamais");
alert("send");
});
});
function send_message(type, data)
{
var msg = {
type: type,
data: data
};
window.postMessage(msg, "*");
}
Handler in allurls.js (content script)
window.addEventListener('message', function(event) {
if (event.data.type && (event.data.type === 'BANNER'))
{
alert("ouimonsieur");
}
});
So the message from iframe.js is well sent (prooved by the alert) but the content script recieve nothing from it, even before the :
if (event.data.type && (event.data.type === 'BANNER'))
Can someone see what is wrong or what other message passing protocol i can use (i also tried with top.window.postmessage) ?
Ty wOxxOm for the answer, i was close :
just replace window by parent in frame.js and all works perfectly.
Because even if the content script run in iframe,
Frame.js is not a content script, it's a iframe script and runs in the context of the extension.
I'm trying to access the activeTab DOM content from my popup. Here is my manifest:
{
"manifest_version": 2,
"name": "Test",
"description": "Test script",
"version": "0.1",
"permissions": [
"activeTab",
"https://api.domain.com/"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Chrome Extension test",
"default_popup": "index.html"
}
}
I'm really confused whether background scripts (event pages with persistence: false) or content_scripts are the way to go. I've read all the documentation and other SO posts and it still makes no sense to me.
Can someone explain why I might use one over the other.
Here is the background.js that I've been trying:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
// LOG THE CONTENTS HERE
console.log(request.content);
}
);
And I'm just executing this from the popup console:
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { }, function(response) {
console.log(response);
});
});
I'm getting:
Port: Could not establish connection. Receiving end does not exist.
UPDATE:
{
"manifest_version": 2,
"name": "test",
"description": "test",
"version": "0.1",
"permissions": [
"tabs",
"activeTab",
"https://api.domain.com/"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Test",
"default_popup": "index.html"
}
}
content.js
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.text && (request.text == "getDOM")) {
sendResponse({ dom: document.body.innerHTML });
}
}
);
popup.html
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { action: "getDOM" }, function(response) {
console.log(response);
});
});
When I run it, I still get the same error:
undefined
Port: Could not establish connection. Receiving end does not exist. lastError:30
undefined
The terms "background page", "popup", "content script" are still confusing you; I strongly suggest a more in-depth look at the Google Chrome Extensions Documentation.
Regarding your question if content scripts or background pages are the way to go:
Content scripts: Definitely
Content scripts are the only component of an extension that has access to the web-page's DOM.
Background page / Popup: Maybe (probably max. 1 of the two)
You may need to have the content script pass the DOM content to either a background page or the popup for further processing.
Let me repeat that I strongly recommend a more careful study of the available documentation!
That said, here is a sample extension that retrieves the DOM content on StackOverflow pages and sends it to the background page, which in turn prints it in the console:
background.js:
// Regex-pattern to check URLs against.
// It matches URLs like: http[s]://[...]stackoverflow.com[...]
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?stackoverflow\.com/;
// A function to use as callback
function doStuffWithDom(domContent) {
console.log('I received the following DOM content:\n' + domContent);
}
// When the browser-action button is clicked...
chrome.browserAction.onClicked.addListener(function (tab) {
// ...check the URL of the active tab against our pattern and...
if (urlRegex.test(tab.url)) {
// ...if it matches, send a message specifying a callback too
chrome.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
}
});
content.js:
// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.text === 'report_back') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.all[0].outerHTML);
}
});
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
...
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*.stackoverflow.com/*"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Test Extension"
},
"permissions": ["activeTab"]
}
Update for manifest v3
chrome.tabs.executeScript doesn't work in manifest v3, as noted in the comments of this answer. Instead, use chrome.scripting. You can specify a separate script to run instead of a function, or specify a function (without having to stringify it!).
Remember that your manifest.json will need to include
...
"manifest_version": 3,
"permissions": ["scripting"],
...
You don't have to use the message passing to obtain or modify DOM. I used chrome.tabs.executeScriptinstead. In my example I am using only activeTab permission, therefore the script is executed only on the active tab.
part of manifest.json
"browser_action": {
"default_title": "Test",
"default_popup": "index.html"
},
"permissions": [
"activeTab",
"<all_urls>"
]
index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<button id="test">TEST!</button>
<script src="test.js"></script>
</body>
</html>
test.js
document.getElementById("test").addEventListener('click', () => {
console.log("Popup DOM fully loaded and parsed");
function modifyDOM() {
//You can play with your DOM here or check URL against your regex
console.log('Tab script:');
console.log(document.body);
return document.body.innerHTML;
}
//We have permission to access the activeTab, so we can call chrome.tabs.executeScript:
chrome.tabs.executeScript({
code: '(' + modifyDOM + ')();' //argument here is a string but function.toString() returns function's code
}, (results) => {
//Here we have just the innerHTML and not DOM structure
console.log('Popup script:')
console.log(results[0]);
});
});
For those who tried gkalpak answer and it did not work,
be aware that chrome will add the content script to a needed page only when your extension enabled during chrome launch and also a good idea restart browser after making these changes
I am writing a Google Chrome extension to automate some common tasks. The functionality I want is as follows:
Create a new tab and navigate to my webmail
enter username and password
click "submit" button
Wait until the webmail page appears, and choose the "roundcube" client.
I have completed steps 1,2,and 3 and they work. I am having a lot of trouble trying to listen for the url change after my credentials are submitted so that the function that selects roundcube client can run
I know I can run a script when client selection page appears by adding to my manifest but I want to use "chrome.tabs.executeScript" instead so that roundcube is chosen only if I run the script from the chrome extension and not if I go to client selection page manually.
Here is my manifest.json:
{
"manifest_version": 2,
"name" : "Chrome Autobot",
"description": "This extension will run various automation scripts for google chrome",
"version" : "1.0",
"browser_action" : {
"default_icon" : "icon.png",
"default_popup": "index.html"
},
"permissions": [
"activeTab",
"webNavigation",
"tabs",
"http://*/*",
"https://*/*"
]
}
Here is my chrome script:
jQuery(function($) {
"Use Strict";
var openWebmail = function() {
chrome.tabs.create({
url: 'http://mywebmaillogin.com:2095/'
}, function() {
chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
});
chrome.tabs.onUpdated.addListener(function(){
chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
alert('i work');
});
};
var init = $('.script-init');
init.on('click', function() {
openWebmail();
});
});
and here is the content script to be executed as a callback of tab creation (when the email login page is fetched and the DOM has loaded), and also when the email credentials are submitted and the client selection page's DOM has loaded (which is not working right now)
var openEmail = function() {
var loc = window.location.href;
if(loc === 'http://mywebmaillogin.com:2095/') {
var submit = document.getElementById('login_submit');
user.value = 'myusername';
pass.value = 'mypassword';
if(user.value === 'myusername' && pass.value === 'mypassword') {
submit.click();
}
else {
openEmail();
}
}
if(loc.indexOf('http://mywebmaillogin:2095/') > -1 && loc.indexOf('login=1') > -1) {
alert('I work');
}
}()
any help would be appreciated... thanks!
As mentioned by #NycCompSci, you cannot call the chrome api from content scripts. I was able to pass api data to content scripts with message passing though, so thought I'd share that here. First call onUpdated in background.js:
Manifest
{
"name": "My test extension",
"version": "1",
"manifest_version": 2,
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
],
"permissions": [
"tabs"
]
}
background.js
chrome.tabs.onUpdated.addListener(function
(tabId, changeInfo, tab) {
// read changeInfo data and do something with it (like read the url)
if (changeInfo.url) {
// do something here
}
}
);
Then you can expand that script to send data (including the new url and other chrome.tabs.onUpdated info) from background.js to your content script like this:
background.js
chrome.tabs.onUpdated.addListener(
function(tabId, changeInfo, tab) {
// read changeInfo data and do something with it
// like send the new url to contentscripts.js
if (changeInfo.url) {
chrome.tabs.sendMessage( tabId, {
message: 'hello!',
url: changeInfo.url
})
}
}
);
Now you just need to listen for that data in your content script:
contentscript.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// listen for messages sent from background.js
if (request.message === 'hello!') {
console.log(request.url) // new url is now in content scripts!
}
});
use chrome.tabs.onUpdated
Maifest.json
{
"name": "My test extension",
"version": "1",
"manifest_version": 2,
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
],
"permissions": [
"tabs"
]
}
contentscript.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
alert('updated from contentscript');
});
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
alert('updated from background');
});
Based on / Thanks to #ztrat4dkyle's answer, what works for me:
manifest.json
{
...
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
],
"permissions": [
"tabs"
]
}
background.js
chrome.runtime.onInstalled.addListener(function() {
// ...
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
// changeInfo object: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated#changeInfo
// status is more reliable (in my case)
// use "alert(JSON.stringify(changeInfo))" to check what's available and works in your case
if (changeInfo.status === 'complete') {
chrome.tabs.sendMessage(tabId, {
message: 'TabUpdated'
});
}
})
});
content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.message === 'TabUpdated') {
console.log(document.location.href);
}
})