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?
Related
I am using the Receipts page (IN301000) for reference purposes:
I would like to be able to obtain a field's information on the client side with the GetElementByID() method.
When I inspect the field, and identify the rendered ID, I obtain data:
However, if I refresh the page and invoke the method again, I obtain NULL:
Does anyone know how to make that information always available?
The final goal here is being able to pass data to the client side. I can - for instance -populate an unbounded field with the FieldSelecting event, and would like to ideally read data out of it.
If there are client variables that could be set from the graph, it would work as well.
Thanks!
You likely get null because the script is executed in the top iFrame instead of the main iFrame which contains the control.
The JavaScript global variable px_alls contains the editor controls and can be indexed by editor control ID.
px_alls["edTransferNbr"].getValue();
document.getElementById("ctl00_phF_form_t0_edTransferNbr_text").value;
Script executed in main iFrame context:
When the script is executed in the context of the top iFrame you would have to select the main iFrame.
var mainframe = document.querySelector('[name=main]').contentDocument;
mainFrame.getElementById('ctl00_phF_form_t0_edTransferNbr_text').value;
Script executed in top iFrame context:
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.
I am running Xpages in XPiNC.
My usual pattern for an Xpages app is to have a xpHome.xsp page that is set to be the first page that is opened. In this I page I set any scope variables that are at the application level, then head to the "real" first page.
Now I have run into a problem. My current database will send out emails when a status changes, and this will include a doc link to the document, which points to the correct Xpage to open. However, because the user is not going through the home page, then my applicationScope vars are being set.
I thought I could fix this by setting a semaphore in the initApp function - the last thing it would do is to place a "Y" in an applicationScope.semaphore field. So when I open my Xpage the first thing it does is check for that, and if it is null, then I call the initApp function.
For some reason this is not working. But even so I would like to find the equivalent of the old database script "Initialize" event. Something I can call whenever the db is opened for the first time.
How do others handle this problem?
Create a managed Java bean "app" which
works on application scope
sets all application parameters
offers all application parameters
Access the bean with app.xxx in EL or Javascript or call methods directly like app.getXxx() or app.doWhatEverYouWant() in JavaScript.
The bean "app" gets initalized automatically when one of the methods gets called first time.
You can find an example here for start or google for "XPages managed beans" for more Information.
Yes, you have to switch your current code partly to Java but it should be worth it in the long run.
If you want to or have to stay with JavaScript then initialize your application scope variables in a custom control which is included in every XPage like layout.xsp in beforePageLoad event.
Use an additional variable applicationScope.initialized. Check if applicationScope variables aren't initialized yet in JavaScript with
if (!applicationScope.initialized) {
... initialize your applicationScope variables ...
applicationScope.initialized = "yes";
}
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.
i want to execute a function inside a chrome extension (type: content_page), from the injected javascript inside a webpage loaded at a tab.
How can I do?
if you mean 'injected' as part of the extension then take a look at the developer documentation for message passing.
if the injected is part of the webpage itself, or another non-extension javascript file, i don't believe there is anyway to accomplish this, or any reason it would even be safe.
if it is a function that resides at the global level, just call it as you would any other function (watch out for collisions though; if it is a common name).