Call content script on URL change - javascript

I'm new to Chrome extensions. I know there's lots of similar questions and info out there, but none of it seems to address this problem.
I need my content script to execute on every page that matches *://*.youtube.com/watch?v=*.
I've tried using the above value and *://*.youtube.com/* as the match property, but neither works supposedly due to the way YouTube handles requests. I've also tried using the onhashchange event, but of course YouTube doesn't use anchors in their URLs. I've read about webRequest, but I don't need the function to be called when somebody is scrolling through the comments and the page loads more comments.
All I need is a way to call my content script when the URL changes. How exactly can I accomplish this?
Additionally, I cannot load the content script at document_start because the extension scrapes the HTML and parses it.

I had the same issue and this is what I did. I added a background script to listen to all changes
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url) {
chrome.tabs.sendMessage(tabId, {
message: 'hello',
url: changeInfo.url
});
}
});
Then in my content script, I listen to it and reload the URL
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === 'hello') {
const { url } = request;
chrome.location.replace(url);
}
});

Related

get url of current (active) tab for chrome-extension

I found several approaches, but they seem outdated or simply won't work for some reasons for me. Maybe tunnelvision:
First things first:
I have the correct permissions in my manifest.json, I think:
"permissions": [
"tabs",
"activeTab"
]
I have 2 simple scripts, background.js and content.js (which are recognized correctly, the error can't be here).
In my background.js I tried several approaches:
chrome.browserAction.onClicked.addListener(buttonClicked);
var sharedUrl;
function buttonClicked(tab) {
chrome.tabs.getCurrent(function(tab) {
// please read further, this was my last resort, I tried other stuff as well
sharedUrl = console.log(window.location.href);
});
let msg = {
txt: "Hello",
url: sharedUrl
}
chrome.tabs.sendMessage(tab.id, msg);
}
I tried it with getCurrent() and then tab.url, but that didn't work (neither with tab[0].url
I tried it also with getSelected() as well as with something like this:
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
var activeTab = arrayOfTabs[0];
});
and my content.js is simply this here:
chrome.runtime.onMessage.addListener(gotMessage);
function gotMessage(message, sender, sendResponse) {
console.log(message.txt);
console.log(message.url);
}
It displays "Hello", but not the URL I'm looking for.
Edit:
It might of importance, that I want to retrieve the url after a button-click in my extension.
Thanks for the feedback and help.
Ok, based on the documentation you are not able to grab the tab object while you are not in the tab context. The tab context includes only content scripts. So you can't access to tab because you are calling it from your backend page. You can only do it, if your extension has generated the tab.
Gets the tab that this script call is being made from. May be
undefined if called from a non-tab context (for example: a background
page or popup view).
So, the only possible way is to change your extension data flow.

Chrome WebRequest - Add chrome event listener once

Problem
I'm trying to pass headers to a chrome extension that is running in a
tab. At the moment my current code, adds one event each time the page is refreshed which means if you refresh on the same page 5 times, by the end the event is triggered 5 times on each page load.
Restrictions
I have to run the tab using a background script to allow the user to
exclude sites it runs on.
In order to get the specific headers I want, I have to listen using a background extension because, attempts to re-request the resource are CORS and have access to a reduced set of headers.
Code
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// Prevent double firing of event - extension should run once on complete.
if (changeInfo.status == "complete") {
chrome.webRequest.onCompleted.addListener(
function(details) {
console.log(details);
// Pseudo code, where things are pulled from storage
// Based on this I then:
executeScript(tabId, "built_seo_traffic.js");
},
{
urls: ["*://*/*"],
types: ["main_frame"],
tabId: tab.id
},
["responseHeaders"]
);
}
});
What I've tried
I've tried setting a variable which checks if the event has already fired, however I can only get this so the script then runs once across all the tabs, rather than once on each tab.
Am I tackling this correctly? I am just missing something which makes this easy?
Thanks to #wOxxOm I managed to get the following solution:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
// Prevent double firing of event - extension should run once on complete.
if (changeInfo.status == "complete") {
chrome.webRequest.onCompleted.addListener(
executeTrafficLights,
{
urls: ["*://*/*"],
types: ["main_frame"],
tabId: tab.id
},
["responseHeaders"]
);
}
});
executeTrafficLights(details){
console.log(details);
// Pseudo code, where things are pulled from storage
// Based on this I then:
executeScript(tabId, "built_seo_traffic.js");
// Then remove self after executing
chrome.webRequest.onCompleted.removeListener(executeTrafficLights);
}
Notably the part I had been struggling with was that because the named function can only be called without parameters I wasn't sure how to pass in tabId and tab.
Turns out both the pieces of information I needed from those (the tabId and the URL) are in the details object.
Unsure if there is any other information contained in tab which isn't in details, but for me this worked great!

chrome extension - passing data from background to custom html page

Creating browser extension where I have to open new tab from background.js and pass JSON data to this new tab. In new tab I am manipulating/rendering DOM using passed JSON data.
Below is part of my background.js where I create new tab with custom URL and send JSON data object
....
var analyticsUrl = chrome.extension.getURL("analytics.html");
chrome.tabs.create({ url: analyticsUrl, selected: true }, sendDataToAnalytics);
function sendDataToAnalytics(tab)
{
console.log(JSON.stringify(txnDataJSON));
chrome.tabs.sendMessage(tab.id, {"action" : "renderChartsTxns", "txns" : JSON.stringify(txnDataJSON)});
}
....
My custom analytics.html page has
<script src="analytics.js" type="text/javascript"></script>
And analytics.js looks like below
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "renderChartsTxns")
{
// JSON parsing and analytics.html DOM processing
});
Problem is, my analytics.js listener is never receiving any messages. I've confirmed that background.js is sending JSON message as expected (using background page debugging)
BTW, analytics.js/html are not registered as part of manifest.json file but these files are part of extension package.
I did this setup today morning and everything was working properly for few hours (was able to receive JSON data in analytics.js), not sure what changed later and I lost message receiving in analytics.js (for debugging I tried clearing browser cache, uninstall and reinstalled chrome and much more but no luck yet!)
The callback of chrome.tabs.create returns as soon as the tab is created, not after it fully loads.
As such, you have a race condition: your message is potentially sent before the listener is initialized. Since it's a race condition, it can sometimes work.
The correct logic here would be to send a message requesting data from the newly opened tab, and use sendResponse to pass that data from the background.
// analytics.js
chrome.runtime.sendMessage({"action" : "getTxns"}, function(txns) {
// Process the data
});
// background.js
// Register this before opening the tab
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.action == "getTxns") {
sendResponse(txnDataJSON); // No need to serialize yourself!
}
});

Chrome extension: create tab then inject content script into it

Upon receiving a message from a content script I want to create a new tab and fill the page it opens dynamically (for now I'm just trying to turn the newly created page red).
eventPage.js:
// ... code that injects another content script, works fine
// Problem code...
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
chrome.tabs.create({url: chrome.extension.getURL("blankpage.html")},
turnTabRed);
});
function turnTabRed(tab)
{
chrome.tabs.executeScript(
tab.id,
{code: 'document.body.style.backgroundColor="red"'}
);
}
This successfully creates a new tab and loads blankpage.html (which is just an HTML page with some text) fine, but fails to paint the background colour red. After inserting console.log() statements in various places and fooling around in the debugger, I have ascertained that turnTabRed is being called, tab.id is indeed the ID of the newly created tab and if I call document.body.style.backgroundColor="red" from the console, the background of the new tab turns red. I noticed that if I added
(*)
chrome.tabs.query(
{}, function (tabArr) { for (var i = 0; tabArr[i]; i++)
console.log(tabArr[i].title); });
into the body of turnTabRed the title of the new tab would not be printed into the console, which suggested that the script was being injected too early, so I tried delaying the injection with setTimeout and when that failed, I tried listening for the status-complete event:
function turnTabRed(tab)
{
chrome.tabs.onUpdated.addListener(
function(tabUpdatedId, changeInfo, tabUpdated)
{
if (tabUpdatedId == tab.id && changeInfo.status &&
changeInfo.status == 'complete')
chrome.tabs.executeScript(
tabUpdatedId,
{code: 'document.body.style.backgroundColor="red"'});
});
}
which also failed. Calling (*) after waiting with setTimeout did print the new tab's title along with all the others though.
What's wrong? How can I create a new tab, load an HTML page and then turn its background red?
The problem is that you cannot inject scripts into chrome-extension://* pages (which your blankpage.html is).
For example, change
{url: chrome.extension.getURL("blankpage.html")}
to
{url: "http://www.google.com"}
in your original codeblock and it will change the background to red. As far as I know there is no way around injecting into chrome-extension://* pages (I would assume the reason for this is that it is a giant security concern). I'm not sure what your extension is trying to do, but injecting into a "live" page should work...maybe you can create some API to generate a new "blankpage" on your server whenever the chrome.runtime.onMessage.addListener fires?
EDIT
So you can't inject stuff into chrome-extension://* pages, but you can pass messages to them and use some subset of chrome API's inside those new pages as mentioned below. So using message passing you will be able to do exactly what you want (modify the new page), albeit in a roundabout way. Here is a really simple proof of concept that works for me:
eventPage.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
chrome.tabs.create({url: chrome.extension.getURL("blankpage.html")}, turnTabRed);
});
function turnTabRed(tab)
{
chrome.tabs.sendMessage(tab.id, {"action" : "setBackground"});
}
blankpage.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.action == "setBackground") document.body.style.background = "red";
});

Communicating between Chrome DevTools and content script in extension

(I have already read this and it didn't work, and I've done a lot of searching and experimentation to no avail.)
I am writing a Chrome extension (BigConsole) with the goal of building a better Console tab for the Chrome developer tools. This means I would like to execute user-input code in the context of the page with access to the DOM and other variables on the page. To do this, the communication is structured as follows:
devtools creates a panel where the user writes code
When the user wants to execute code from the panel, the panel sends a message to a background script with the code
The background script receives the message/code from panel and passes it on to the content script which is injected into the page
The content script receives the message/code from the background script and injects a script element into the page which then runs the code
The result of the script on the page is then posted back to the content script with window.postMessage
The content script listens for the message/result from the page and passes it on to the background script
The background script receives the message/result from the content script and passes it on to the panel
The panel receives the message/result from the background script and inserts it into the log of results
Whew.
Right now, when the user tries to run the code, nothing happens. I put a bunch of console.log()s into the code but nothing appears in the console. My main question is, what have I done wrong here with the message passing that results in nothing happening? Alternatively, I would love to be told that I am making this way too complicated and there is a better way of doing things. Simplified code below...
panel.js:
window.onload = function() {
var port = chrome.runtime.connect({name: "Eval in context"});
// Add the eval'd response to the console when the background page sends it back
port.onMessage.addListener(function (msg) {
addToConsole(msg, false);
});
document.getElementById('run').addEventListener('click', function() {
var s = document.getElementById('console').value;
try {
// Ask the background page to ask the content script to inject a script
// into the DOM that can finally eval `s` in the right context.
port.postMessage(s);
// Outputting `s` to the log in the panel works here,
// but console.log() does nothing, and I can't observe any
// results of port.postMessage
}
catch(e) {}
});
};
background.js:
chrome.runtime.onConnect.addListener(function (port) {
// Listen for message from the panel and pass it on to the content
port.onMessage.addListener(function (message) {
// Request a tab for sending needed information
chrome.tabs.query({'active': true,'currentWindow': true}, function (tabs) {
// Send message to content script
if (tab) {
chrome.tabs.sendMessage(tabs[0].id, message);
}
});
});
// Post back to Devtools from content
chrome.runtime.onMessage.addListener(function (message, sender) {
port.postMessage(message);
});
});
content.js:
// Listen for the content to eval from the panel via the background page
chrome.runtime.onMessage.addListener(function (message, sender) {
executeScriptInPageContext(message);
});
function executeScriptInPageContext(m) { alert(m); }
As pointed out by Alex, here's a typo in your code which prevents it from working.
Drop your current code and use chrome.devtools.inspectedWindow.eval to directly run the code and parse the results. This simplifies your complicated logic to:
devtools creates a panel where the user writes code
devtools runs code
devtools handles result
PS. There is a way to manipulate the existing console, but I recommend against using it, unless it's for personal use. Two different ways to do this are shown in this answer.

Categories