chrome.webNavigation.onHistoryStateUpdated executes multiple times on redirects - google-chrome-extension

When I click links on webpages, the script gets executed once, as expected, but when the webpage redirects the user (such as using history.pushState() the script gets executed twice, how can I stop this?
It's mind boggling that the history API doesn't make a difference between redirects and click on links, it reports them both as type link of transitionType.
The executeScript by default only executes on the top frame (frameId 0) so that's not the problem.
And I also used frameId and parentFrameId properties of the callback function although I knew it wouldn't fix the problem because as I've said, on clicking links, the script is executed only once, but when the browser redirects, it's executed twice.
BackgroundScript:
chrome.webNavigation.onHistoryStateUpdated.addListener(function(obj){
if (obj.frameId === 0 && obj.parentFrameId === -1){
chrome.tabs.executeScript({
file: 'js/contentScript.js',
runAt: 'document_end'
});
console.log(obj.transitionType);
}
});
ContentScript:
console.log("executed!");

onHistoryStateUpdated event is triggered not only on the browser back/forward actions but also when the site scripts use window.history methods so it can happen even 1000 times per navigation if the page calls the API that many times, there's no limit.
If the problem here is the multiple execution of the script you can simply send a message first and inject only if no response is received. Or use a global variable in the content script, see how to avoid dynamically injecting the same script multiple times when using chrome.tabs.executeScript.

Related

How to dynamically initialize content script in Firefox/Chrome web extension?

We have a Firefox/Chrome web extension which contains both background and content scripts. The background script maintains a cache of shared key/value pairs which are needed by the content scripts. It is not difficult to access them via the browser.runtime.SendMessage functionality.
However, we need access to these key/value pairs as quickly as possible on page load, before any scripts run on the original page. This is generally not possible because the async nature of SendMessage means that the background script will not respond to requests made by the content scripts fast enough.
We've been looking for solutions along the following lines:
1. Sync/blocking lookup
It doesn't appear that there are any mechanisms for this.
2. Initializing the content script
We can register content scripts using browser.contentScripts.register and potentially pass a copy of the entire cache in via javascript. However, this is called only once and all subsequent tabs/pages will load whatever was specified in it. We might be able - with great difficulty - create a listener for global cache changes and re-register a new content script each time.
Are there any better approaches than this?
Update: By adding a listener to browser.webNavigation.onBeforeNavigate, I am now able to inject a global variable which displays in the console output:
In background.js:
browser.webNavigation.onBeforeNavigate.addListener(function(details) {
browser.tabs.executeScript(
details.tabId,
{
code: `window.scope.somevariable=true;
console.log("I executed in content script:", "somevariable", window.scope.somevariable);`
}
);
});
In my library which was injected by register, I am also able to print window.scope out to the console and I see the variable there.
However... when I try to access the variable programmatically, it returns "undefined".
In content script:
// this displays "somevariable" among the window scope properties:
console.log("window.scope", window.scope);
// this displays "undefined":
console.log("somevariable", window.scope.somevariable);
Why can the registered js library output the variable to the console window but can't actually read it?

Wait for chrome.tabs.update tab to finish loading

I'm trying to work on a chrome extension and am trying to clean up some of my code by relying on the sendMessage. However the callback function activates before the page has finished loading so in the case of a new tab, nobody receives and in the case of an existing tab the page that is being moved from is getting the message (but that isn't what I want). I've looked for other people asking about that problem with new tabs and there wasn't a clear answer, the best suggestion I've seen is to create a global variable and create a listener for tab loads and compare it against this global variable.
So the question is, is there a way to wait in the callback function until the page has loaded, or do I create an array of JS objects that contain the tab I'm waiting on and the information I want to send to that tab.
For reference here is the relevant code in the background javascript file.
chrome.tabs.sendMessage(tab.id, {info: "info"}, function(response)
{
//This line isn't used when I am navigating without changing tabs
chrome.tabs.create({url: response.info.linkUrl}, function(tab1)
{
chrome.tabs.update(tab1.id, {url: response.info.linkUrl}, function(tab2)
{
chrome.tabs.sendMessage(tab2.id, {info: "More Info"});
});
});
});
Otherwise I am able to confirm that all of my tab side code works, once my sendMessage was delayed enough for me to see that with my own eyes. My code is able to consistently make it past validation on the page being navigated away from, confirmed by checking document.url.
You can try injecting a second content script instead of a message.
It will execute in the same context as your other script.
Something along the lines of
chrome.tabs.executeScript(tab2.id,
{code: 'showInfo("More Info);', runAt: 'document_idle'}
);
where showInfo does the same as your message handler.
It's a bit of a hack and I'm not 100% sure the load order will be correct.
Other possible solutions are more complex.
For example, you can make the content script report back that it is ready and have a handler for that, for instance you can register a listener for onMessage in the background that waits for a message from that specific tab.id, sends "More Info" and then deregisters or disables itself.
Or, you could potentially switch to programmatic injection of your content script, which would let you control load order.

onDOMContentLoaded not always fires

I inject my content script using filtered events instead of manifest content-scripts match rules.
When I navigate to a target page, content script doesn't run always, but only sometimes. When it fails to run I refresh the page (sometimes more than once), untill the content script starts.
For simplicity, I don't want to put the code I know is working. onDOMContentLoaded is not always firing when the filter meets the condition.
var filtro = {'url': [
{hostSuffix: 'somedomain.com', pathPrefix: '/search'},
{hostSuffix: 'somedomain.org', pathPrefix: '/search'}
]};
chrome.webNavigation.onDOMContentLoaded.addListener(listener, filter);
chrome.webNavigation.onErrorOcurred.addListener (listener, filter);
So I added onErrorOcurred, to fire when the page fails to load. But I still missing something, because it doesn't execute every time (but it executes on refreshing the page).
As many other pages, this one trows a lot of error messages to console. I imagine some of these may prevent onDOMContentLoaded to fire, but I think it should fire onErrorOcurred. Am I missing some event?
It happens to me in many different extensions and pages when using filtered events.
Thank you very much.

Need to reload content script for chrome extension on facebook

I created a Chrome extension that adds links to items (things your friends share with you) on your facebook feed. Facebook loads about 10 or 20 items on the feed on page load, and then the rest are loaded via ajax when you scroll down.
I managed to get my content script to work for the first items, but not for the rest. I've read everything i could find, and it seems that I have to reload my content script.
How can i do that considering that the URL does not change?
You can use document.addEventListener with the DOMNodeInserted event to listen for new items getting rendered in the feed. The callback will have to check each node insertion to see if it is a feed item or not. Something like the following should work.
function nodeInsertedCallback(event) { console.log(event); });
document.addEventListener('DOMNodeInserted', nodeInsertedCallback);
Well, you can hang some time driven listener that runs every XX seconds, to verify if there's new items to work with.
Unfortunately there's no event you can hang from, fired when the page's code do some Ajax.
May be you can figure out what evet you can han from to detect the user has reached the end of the loaded item's list, knowing that de page's code will do some Ajax to retrieve more items. Then you start you time driven listenter.

Hidden tabs in google chrome extensions

I've a chrome extension that sends a message from the content script to the background page and logs the tab_id of the content script.
I noticed that on google.com|de|at two messages are logged thus two content scripts are created: one for the actual web page shown in the tab (e.g. https://www.google.com/search?q=python+standard+library ) and another content script for the first item in the google result list ( in the above example http://docs.python.org/library/ )
Even stranger - the tab_id of the second content script (the hidden one) is not valid. I.e. chrome.pageAction.hide(tab_id) causes the following error to appear:
Error during pageAction.hide: No tab with id: 71
Is there a way to figure out if a content script belongs to a "hidden" tab?
thanks,
Peter
First of all, you can use onCreated and/or onUpdated to keep track of tabs and url mappings without the need for a content script.
However, if there's more to your content script than just informing the background page of the tab id, it may mean more checking.
If your content script is run on all_frames, then you will be getting messages from the content script in the top window and all internal frames. Still, when I test a sample implementation, I get the same IDs for all of them. Also, none of them appear to be from entries in the search result list.
If you are running the script in all tabs, you can ensure that only the top window sends the message by wrapping your sendRequest call with an if (window.top === window).
Could it be possible that you have another extension running that previews google results somehow? That may have this effect....

Resources