Suppose I have the following code in my background page of a Chrome extension.
var opts;
chrome.storage.local.get(options, function(result) {
opts = result[options];
});
chrome.runtime.onMessage.addListener(function(request, sender, response) {
if (request.message === 'getOpts')
response(opts);
});
In my content script, I access opts with message passing.
chrome.runtime.sendMessage({'message': 'getOpts'}, function(response) {
console.log(opts);
});
Is there any guarantee that opts will be defined prior to a content script running? For example, when starting up the browser, the background page will run, and presumably the callback to chrome.storage.local.get will be added to the background page's message queue. Will Chrome finish processing that queue before injecting content scripts?
I could call chrome.storage.local.get from the content script, but my question is more generic, as I have additional async processing in my background page. At the moment, my content script checks with the background page to make sure everything is ready (using an interval to keep checking), but I am not sure whether such checks are necessary.
You can actually answer asynchronously to a message.
chrome.runtime.onMessage.addListener(function(request, sender, response) {
if (request.message === 'getOpts') {
chrome.storage.local.get('options', function(result) {
response(result[options]);
});
return true; // Indicate that response() will be called asynchronously
}
});
In case of chrome.storage this is, indeed, stupid as the API was specifically designed to addess the roundabout nature of using localStorage + Messaging in content scripts; you can query from content scripts directly.
But in general case of async processing, you can postpone answering to the message. You just need to return a true value from the listener to indicate that you're not done yet.
Do not rely on your variable being defined even if it is in your tests. In general, due to normal timing, your var should be set by the time it receives the message, but to be sure you should check so in your background onMessage. I've dealt with this by remembering in a global whether I've already initialized the extension and do so if needed from onMessage. The answer by xan shows how to use "return true" in that case to process the message once your async init finishes.
See an example of this on my chrome extension (search for bCalled)
There you can see I have a sendResponse method that takes care of detecting if the callback was already called and if not, return true (assumes an async operation is in progress and will call the callback later)
You can also experiment and test this by faking a delay in the background code that loads that var.
Related
So, I'm building an extension that autofills different types of forms. As it's not apparent from the url which form is used on a particular website, I need to match all the urls in my manifest. I'm trying to detect the form by the 'src'-attribute in the web page.
Some of the fields of a certain form are not in the first frame. So "all_frames" has to be true in my manifest. That means content.js fires once for each frame or iFrame.
**content.js:**
async function checkForPaymentType(value, attribute = 'src') {
return document.querySelectorAll(`[${attribute}*="${value}"]`);
}
let hasThisForm;
document.addEventListener('DOMContentLoaded', () => {
checkForPaymentType('formJs.js').then((value) => {
if(value.length) {
hasThisForm = true;
}
if(hasThisForm)
fillForm();
});
});
The problem now is, that that only the first frame has the "src='formJs.js" attribute in one of its elements. So it only fills out those fields in the first frame.
My solution idea
What I am trying to do is some sort of global boolean variable ('hasThisForm') that can only be set true. So once the first frame detected that there is this form on the website the other frames fire fillForm() as well.
Problems
1.I'm not able to set a variable that can be read from all of the executions.
2.I need the other executions of content.js to wait for the first one.
Another solution would be to have some sort of window.querySelectorAll, so every execution of content.js searches in the whole page and not just in its frame.
Thanks in advance:)
So I figured it out.
As pointed out in the comment from #wOxxOm or in this question (How to get all frames to access one single global variable) you need to manage everything via the background page.
You want to set up a listener in every Frame and send a message only from the top frame (to the background page which sends it back to the whole tab).
After hours of trying I noticed that the listeners weren't even ready when the message from the topFrame was sent. I put in a sleeper before sending the message which is not the ideal way I guess. But there is no "listenerIsSet-Event".
This is my code:
content.js
document.addEventListener('DOMContentLoaded', () => {
chrome.runtime.onMessage.addListener(
function (msgFromTopFrame) {
console.log(msgFromTopFrame)
});
if (window === top) {
Sleep(1000).then(() => {
const msgToOtherFrames = {'greeting': 'hello'};
chrome.runtime.sendMessage(msgToOtherFrames);
});
}
});
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
if(('greeting' in msg)) {
chrome.tabs.sendMessage(sender.tab.id, msg);
}
});
You probably want to execute some code depending on the value received. You can write it only once in the listener. It will execute in all frames including the top frame as the background.js sends it to all frames in the tab.
Note:
There may be some errors with the dicts/keys in the messages "sent around" in this code. Just log the message in all the listeners to get the right expressions.
I recently updated Chrome to version 55.0.2883.75.
I am using a self developed Chrome Plugin to parse my HTML files wherein I use chrome.tabs.executescript to get data from background HTML page.
So when I execute chrome.extension.onRequest, I save the background page's parsed data to a global variable and access it in the callback function of chrome.tabs.executescript and process it.
This was working fine till I update to Version 55.0.2883.75.
How can I access the global variables in the new version ??
My Code Below :
Step 1 :
chrome.extension.onRequest.addListener(
function (request, sender, sendResponse) {
parser = new DOMParser();
htmlDoc = parser.parseFromString(request.content, "text/html");
//outputJson is a global variable which is Populated here
outputJson = parseMyPage(outputJson, htmlDoc);
});
Step 2:
chrome.tabs.getSelected(null, function (tab) {
// Now inject a script onto the page
chrome.tabs.executeScript(tab.id,{
code: "chrome.extension.sendRequest({content: document.body.innerHTML}, function(response) { console.log('success'); });"
}, function () {
//my code to access global variables
if (outputJson && null != outputJson) {
// other stuff
}
});
});
The way your code is designed, you are relying on the order in which two asynchronous blocks of code are executed: the extension.onRequest1 event and the callback for tabs.executeScript(). Your code requires that the extension.onRequest1 event fires before the tabs.executeScript() callback is executed. There is no guarantee that this will be the order in which these occur. If this is a released extension, it is quite possible that this was failing on users' machines, depending on their configuration. It is also possible that the code in Chrome, prior to Chrome 55, resulted in the event and callback always happening in the order you required.
The solution is to to rewrite this to not require any particular order for the execution of these asynchronous code blocks. Fortunately, there is a way to do that and reduce complexity at the same time.
You can transfer the information you desire from the content script to your background script directly into the callback of the tabs.executeScript(), without the need to explicitly pass a message. The value of the executed script is passed to the callback in an array containing one entry per frame in which the script was injected. This can very conveniently be used to pass data from a content script to the tabs.executeScript() callback. Obviously, you can only send back a single value per frame this way.
The following code should do what you desire. I hand edited this code from your code in this Question and my answer here. While the code in that answer is fully tested, the fact that I edited this only within this answer means that some errors may have crept in:
chrome.tabs.getSelected(null, function (tab) {
// Now inject a script onto the page
chrome.tabs.executeScript(tab.id,{
code: "document.body.innerHTML;"
}, function (results) {
parser = new DOMParser();
htmlDoc = parser.parseFromString(results[0], "text/html");
//outputJson is a global variable which is Populated here
outputJson = parseMyPage(outputJson, htmlDoc);
//my code to access global variables
if (outputJson && null != outputJson) {
// other stuff
}
});
});
extension.sendRequest() and extension.onRequest have been deprecated since Chrome 33. You should replace these anywhere you are using them with runtime.sendmessage() and runtime.onMessage.
I want to write an extension that does the following:
Defines a custom function
Allows Javascript code loaded from the Internet to run such a function
The function should take as a parameter an event listener. Basically, something like:
newApiFunctionDefinedInExtension( function( responseHeaders ){
console.log("Headers arrived!", responseHeaders );
} ;
Then using chrome.webRequest, my extension (which made newApiFunctionDefinedInExtension available in the first place) will call the listener (in the locally loaded page) every time response headers are received from the network.
I am new to Chrome extensions and cannot find a way to make that happen. It would be great to know:
How to make a function defined in a module available to the loaded page's scope
How to make such an EventEmitter -- is there a constructor class I can extend?
My goal is simple: the loaded page should define a function, and that function should be called every time there is a network connection.
Every webRequest event receives information about a request, including the ID of the originating tab.
So, assuming that the tab exists note 1, you can use the following flow:
// background.js
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.tabId == -1)
return; // Not related to any tab
chrome.tabs.sendMessage(details.tabId, {
responseHeaders: details.responseHeaders
});
}, {
urls: ['*://*/*'], // e.g. all http(s) URLs. See match patterns docs
// types: ['image'] // for example, defaults to **all** request types
}, ['responseHeaders']);
Then, in a content script (declared in the manifest file), you take the message and pass it to the web page:
// contentscript.js
chrome.runtime.onMessage.addListener(function(message) {
// Assuming that all messages from the background are meant for the page:
document.dispatchEvent(new CustomEvent('my-extension-event', {
detail: message
}));
});
After doing that, your web page can just receive these events as follows:
document.addEventListener('my-extension-event', function(event) {
var message = event.detail;
if (message.responseHeaders) {
// Do something with response headers
}
});
If you want to put an abstraction on top (e.g. implementing a custom EventEmitter), then you need to inject a script in the main execution environment, and declare your custom API over there.
note 1. For simplicity, I assumed that the tab existed. In reality, that is never true for type "main_frame" (and "sub_frame"), because the page has not yet been rendered. If you want to get response headers for the top-level/frame documents, then you need to temporarily store the response headers in some data structure (e.g. a queue / dictionary) in the background page, and send the data to the content script whenever the script is ready.
This can be implemented by using chrome.runtime.sendMessage in the content script to send a message to the background page. Then, whenever a page has loaded and the content script is ready, the background page can use sendResponse to deliver any queued messages.
I am making an ajax request where it may take much time to process the server-end.So I want to show a loading image at the time of request process.But loading image is not being shown while ajax requst.
var ref = createAjaxRequest();//Ajax req is created here...
if(ref){
showLoadingImg();
ref.open('POST','x.jsp',false);
ref.onreadystatechange = eventStateChange;
ref.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ref.setRequestHeader("Connection", "keep-alive");
ref.send();
}
else{ alert("Your browser does not support this feature");
}
function eventStateChange(){
if(ref.readyState==4 ){
//processing response here......
hideLoadingImg();
}
}
function showLoadingImg();{
/* <div id="ajaxLoading">(with background image is in page)
It is displayed properly when I set display:inline
manually through developer tools of a browser.</div>
*/
document.getElementById('ajaxLoading').style.display='inline';
}
function hideLoadingImg();{
document.getElementById('ajaxLoading').style.display='none';
}
is there anything wrong?
I tried to debug and found that:
Though showLoadingImg() is called before open() method, the loading image is displayed on browser only after ref.readyState==2.
But unfortunately time gap between readyState==2 and readyState==4 is very less, the loading image is immediately hidden.
Thus user cannot see the loading image...
So, what I am doubting is, doesn't ajax run the script unless it goes to readyState==2.
XMLHttpRequest blocks if you set async to false as you do with the third argument here: ref.open('POST','x.jsp',false);.
I thinks your call to open is wrong.
The third argument (boolean) indicates if the request is asynchronous or not.
Consider complete documentation here : http://www.w3schools.com/ajax/ajax_xmlhttprequest_send.asp
ref.open('POST','x.jsp',true);
Should solve your problem.
Regards
I need to implement a cross-site comet http server push mechanism using script tag long polling. (phew...) For this, I dynamically insert script tags into the DOM and the server sends back short js scripts that simply call a local callback function that processes the incoming messages. I am trying to figure out a way to associate each one of these callback calls with the script tag that sent it, to match incoming replies with their corresponding requests.
Clearly, I could simply include a request ID in the GET url, which is then returned back in the js script that the server generates, but this creates a bunch of unnecessary traffic and doesn't strike me as particularly elegant or clever.
What I would like to do is to somehow associate the request ID with the script tag that I generate and then read out this request ID from within the callback function that is called from inside this script tag. That way, all the request management would remain on the client.
This leads me to the following question: Is there a way to ask the browser for the DOM element of the currently executing script tag, so I can use the tag element to pass arguments to the contained javascript?
I found this thread:
Getting the currently executing, dynamically appended, script tag
Which is asking exactly this question, but the accepted answer isn't useful to me since it still requires bloat in the server-returned js script (setting marker-variables inside the script) and it relies on unique filenames for the scripts, which I don't have.
Also, this thread is related:
How may I reference the script tag that loaded the currently-executing script?
And, among other things, suggests to simply grab the last script in the DOM, as they are executed in order. But this seems to only work while the page is loading and not in a scenario where scripts are added dynamically and may complete loading in an order that is independent of their insertion.
Any thoughts?
PS: I am looking for a client-only solution, i.e. no request IDs or unique callback function names or other non-payload data that needs to get sent to and handled by the server. I would like for the server to (theoretically) be able to return two 100% identical scripts and the client still being able to associate them correctly.
I know you would like to avoid discussions about changing the approach, but that's really what you need to do.
First, each of the script tags being added to the DOM to fire off the poll request is disposable, i.e. each needs to be removed from the DOM as soon as its purpose has been served. Else you end up flooding your client DOM with hundreds or more dead script tags.
A good comparable example of how this works is jsonp implementations. You create a client-side named function, create your script tag to make the remote request, and pass the function name in the request. The response script wraps the json object in a function call with the name, which then executes the function on return and passes the json payload into your function. After execution, the client-side function is then deleted. jQuery does this by creating randomly generated names (they exist in the global context, which is really the only way this process works), and then deletes the callback function when its done.
In regards to long polling, its a very similar process. Inherently, there is no need for the response function call to know, nor care, about what script tag initiated it.
Lets look at an example script:
window.callback = function(obj){
console.log(obj);
}
setInterval(function(){
var remote = document.createElement('script');
remote.src = 'http://jsonip.com/callback';
remote.addEventListener('load', function(){
remote.parentNode.removeChild(remote);
},false);
document.querySelector('head').appendChild(remote);
}, 2000);
This script keeps no references to the script elements because again, they are disposable. As soon as their jobs are done, they are summarily shot.
The example can be slightly modified to not use a setInterval, in which case you would replace setInterval with a named function and add logic into the remote load event to trigger the function when the load event completes. That way, the timing between script tag events depends on the response time of your server and is much closer to the actual long polling process.
You can extend this even further by using a queueing system to manage your callbacks. This could be useful if you have different functions to respond to different kinds of data coming back.
Alternatively, and probably better, is to have login in your callback function that handles the data returned from each poll and executes whatever other specific client-side logic at that point. This also means you only need 1 callback function and can get away from creating randomly generated callback names.
If you need more assistance with this, leave a comment with any specific questions and I can go into more detail.
It's most definitely possible but you need a little trick. It's a common technique known as JSONP.
In JavaScript:
var get_a_unique_name = (function () {
var counter = 0;
return function () {
counter += 1;
return "function_" + counter;
}
}()); // no magic, just a closure
var script = document.createElement("script");
var callback_name = get_a_unique_name();
script.src = "/request.php?id=12345&callback_name=" + callback_name;
// register the callback function globally
window[callback_name] = function (the_data) {
console.log(the_data);
handle_data(the_data); // implement this function
};
// add the script
document.head.appendChild(script);
The serverside you can have:
$callback_name = $_GET["callback_name"];
$the_data = handle_request($_GET["id"]); // implement handle_request
echo $callback_name . "(" . json_encode($the_data) . ");";
exit; // done
The script that is returened by /request.php?id=12345&callback_name=XXX will look something like this:
function_0({ "hello": "world", "foo" : "bar" });
There may be a solution using onload/onreadystate events on the script. I can pass these events a closure function that carries my request ID. Then, the callback function doesn't handle the server reply immediately but instead stores it in a global variable. The onload/onreadystate handler then picks up the last stored reply and tags it with the request ID it knows and then processes the reply.
For this to work, I need to be able to rely on the order of events. If onload is always executed right after the corresponding script tag finishes execution, this will work beautifully. But, if I have two tags loading simultaneously and they return at the same time and there is a chance that the browser will execute both and afterwards execute botth onload/onreadystate events, then I will loose one reply this way.
Does anyone have any insight on this?
.
Here's some code to demonstrate this:
function loadScript(url, requestID) {
var script = document.createElement('script');
script.setAttribute("src", url);
script.setAttribute("type", "text/javascript");
script.setAttribute("language", "javascript");
script.onerror = script.onload = function() {
script.onerror = script.onload = script.onreadystatechange = function () {}
document.body.removeChild(script);
completeRequest(requestID);
}
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onerror = script.onload = script.onreadystatechange = function () {}
document.body.removeChild(script);
completeRequest(requestID);
}
}
document.body.appendChild(script);
}
var lastReply;
function myCallback(reply) {
lastReply = reply;
}
function completeRequest(requestID) {
processReply(requestID, lastReply);
}
function processReply(requestID, reply) {
// Do something
}
Now, the server simply returns scripts of the form
myCallback(message);
and doesn't need to worry at all about request IDs and such and can always use the same callback function.
The question is: If I have two scripts returning "simultaneously" is it possible that this leads to the following calling order:
myCallback(message1);
myCallback(message2);
completeRequest(requestID1);
completeRequest(requestID2);
If so, I would loose the actual reply to request 1 and wrongly associate the reply to request 2 with request 1.
It should be quite simple. There is only one script element for each server "connection", and it can easily be stored in a scoped, static variable.
function connect(nameOfCallback, eventCallback) {
var script;
window[nameOfCallback] = function() { // this is what the response invokes
reload();
eventCallback.call(null, arguments);
};
reload();
function reload() {
if (script && script.parentNode)
script.parentNode.removeChild(script);
script = document.createElement(script);
script.src = "…";
script.type = "text/javascript";
document.head.appendChild(script);
// you might use additional error handling, e.g. something like
// script.onerror = reload;
// but I guess you get the concept
}
}