I'm using a browser.webRequest.onBeforeRequest
handler. I need to block the webRequest until I have information back from calls to asynchronous methods made within the handler. How can I do that?
First, my apology for the long question. But I hope someone can help.
I have an embedded extension which contains a browser.webRequest.onBeforeRequest
(I need to use embedded extension at the moment to deal with some SDK legacy code).
The browser.webRequest.onBeforeRequest
callback function connects to an SDK extension and instructs it to perform some functions. The SDK sends reply to the webextension when it is done from the task. I used await
in the browser.runtime.sendMessage
to ensure that I stop the execution until I get a reply from SDK. To use await
, I had to use async
(but in fact I do not want async
function). When I do not use await
, I only get the reply from the SDK after the loop finishes all its iterations and not for each iteration.
The sample code below contains a lot of console messages for debugging just to monitor the execution.
The problem is: I am not getting a reliable outcome. In some cases (not all), the http request goes out before the SDK code is effective. I could identify this because the request properties must be affected by the SDK code. The console shows the execution sequence as expected. But, the http request is not affected by the SDK (in some cases).
In this simple example, the SDK just sends a message to the webextension, but assume that it performs some functions, read/write operations, etc. All the SDK tasks must be completed before the request goes out.
What I really need is to guarantee that the web request will NOT go out until all the SDK code is performed.
Reference to MDN documentation, it says that the browser.webRequest.onBeforeRequest is an async
function. I wonder if this is the source of the problem? if so, how to enforce it to be sync?
embedding-extension [directory]
- index.js
- package.json
- webextension [directory]
- main.js
- manifest.json
1) package.json
:
{
"title": "testhybrid",
"name": "testhybrid",
"version": "0.0.1",
"description": "A basic add-on",
"main": "index.js",
"author": "",
"engines": {
"firefox": ">=38.0a1",
"fennec": ">=38.0a1"
},
"license": "MIT",
"hasEmbeddedWebExtension": true,
"keywords": [
"jetpack"
]
}
2) index.js
:
const webExtension = require("sdk/webextension");
console.log("in SDK: inside embedding extension");
// Start the embedded webextension
webExtension.startup().then(api => {
const {browser} = api;
browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
if (msg == "send-to-sdk") {
console.log("in SDK: message from webExt has been received");
sendReply({
content: "reply from SDK"
}); //end send reply
}//end if
}); //end browser.runtime.onMessage
}); //end webExtension.startup
3) manifest.json
:
{
"manifest_version": 2,
"name": "webExt",
"version": "1.0",
"description": "No description.",
"background": {
"scripts": ["main.js"]
},
"permissions": [
"activeTab",
"webRequest",
"<all_urls>"
],
"browser_action": {
"default_icon": {
"64": "icons/black-64.png"
},
"default_title": "webExt"
}
}
4) main.js
:
var flag=true;
async function aMethod() {
console.log("in webExt: inside aMethod");
for(var x=0; x<2; x++)
{
console.log("loop iteration: "+x);
if(flag==true)
{
console.log("inside if");
console.log("will send message to SDK");
const reply = await browser.runtime.sendMessage("send-to-sdk").then(reply => {
if(reply)
{
console.log("in webExt: " + reply.content);
}
else {
console.log("<<no response message>>");
}
});
}//end if flag
else
{
console.log("inside else");
}//end else
}//end for
}
browser.webRequest.onBeforeRequest.addListener(
aMethod,
{urls: ["<all_urls>"],
types: ["main_frame"]}
);
The webRequest.onBeforeRequest
documenation states (emphasis mine):
To cancel or redirect the request, first include
"blocking"
in theextraInfoSpec
array argument toaddListener()
. Then, in the listener function, return aBlockingResponse
object, setting the appropriate property:
- to cancel the request, include a property
cancel
with the value true.- to redirect the request, include a property
redirectUrl
with the value set to the URL to which you want to redirect.From Firefox 52 onwards, instead of returning
BlockingResponse
, the listener can return a Promise which is resolved with aBlockingResponse
. This enables the listener to process the request asynchronously.
You appear to have done none of the above. Thus, the event is handled completely asynchronously, with no possibility of delaying the request until your handler returns. In other words, as currently written, your webRequest.onBeforeRequest
has absolutely no effect on the webRequest. You handler is just notified that the webRequest is in process.
To accomplish what you desire, delay the request until after you perform some asynchronous operations, you would need to:
Add "webRequestBlocking"
to your manifest.json permissions:
"permissions": [
"activeTab",
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
Pass "blocking"
in the extraInfoSpec
array argument to addListener()
:
browser.webRequest.onBeforeRequest.addListener(
aMethod,
{urls: ["<all_urls>"],
types: ["main_frame"]},
["blocking"]
);
Return a Promise from your handler, which resolves with a BlockingResponse
:
function aMethod() {
//Exactly how this is coded will depend on exactly what you are doing.
var promises = [];
console.log("in webExt: inside aMethod");
for (var x = 0; x < 2; x++) {
console.log("loop iteration: " + x);
if (flag == true) {
console.log("inside if");
console.log("will send message to SDK");
promises.push(browser.runtime.sendMessage("send-to-sdk").then(reply => {
if (reply) {
console.log("in webExt: " + reply.content);
} else {
console.log("<<no response message>>");
}
});
} else {
console.log("inside else");
}
}
return Promise.all(promises).then(function(){
//Resolve with an empty Object, which is a valid blockingResponse that permits
// the webRequest to complete normally, after it is resolved.
return {};
});
}