Firefox add-on get the DOM window which made an HTTP request - javascript

I'm capturing the HTTP requests in a Firefox Add-on SDK extension. I need to get the DOM window associated with the request. However, I'm getting an NS_NOINTERFACE error.
Here is my code:
var httpRequestObserver = {
observe: function (subject, topic, data) {
var httpRequest = subject.QueryInterface(Ci.nsIHttpChannel);
var requestUrl = subject.URI.host;
var domWin;
var assWindow;
console.log('URL: ', requestUrl);
try {
domWin = httpRequest.notificationCallbacks.getInterface(Ci.nsIDOMWindow);
assWindow = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext)
.associatedWindow;
console.log(domWin);
} catch (e) {
console.log(e);
}
// console.log('TAB: ', tabsLib.getTabForWindow(domWin.top));
var hostName = wn.domWindow.getBrowser().selectedBrowser.contentWindow.location.host;
console.log('HOST: ', hostName);
},
get observerService() {
return Cc['#mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
},
register: function () {
this.observerService.addObserver(this, 'http-on-modify-request', false);
},
unregister: function () {
this.observerService.removeObserver(this, 'http-on-modify-request');
}
};
httpRequestObserver.register();
I've tried both nsIDOMWindow and nsILoadContext, but NS_NOINTERFACE error always appears on an attempt to get the window object.

I have finally managed to get the data I need via
httpRequest.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement
For example, to get url of the document which started the request, I used
httpRequest.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement._documentURI.href

Since you already found how to get the <browser> from the request you can do the following to get back to SDK APIs:
let browser = ....topFrameElement
let xulTab = browser.ownerDocument.defaultView.gBrowser.getTabForBrowser(browser)
let sdkTab = modelFor(xulTab)
modelFor() is documented in the tabs module.

Related

Override post requests

I have this code that I put in my console:
XMLHttpRequest.prototype.send = function(body) {
// modifies inputted request
newBody = JSON.parse(body);
newBody.points = 417;
// sends modified request
this.realSend(JSON.stringify(newBody));
}
It is supposed to make the points 417 every time it sends a request, but when I look at the request body, it still says the original amount of points. Any help?
Try to add an alert() or console.log() into your modified XMLHttpRequest.prototype.send to check if it actually works. There is a way to prevent this kind of modifications silently.
As others have noted, the error you are experiencing is hard to diagnose exactly without seeing how you created this.realSend.
However, this code will work:
const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (body) {
const newBody = JSON.parse(body);
newBody.points = 417;
send.call(this, JSON.stringify(newBody));
};
Note that instead of storing the original send method on XMLHttpRequest.prototype, I've kept in a separate variable and simply invoked it with the correct this value through send.call(). This seems like a cleaner implementation with less chance for conflicts with other code.
See this codesandbox for a working example.
If your function is not being called, possible fetch is used to make ajax requests.
So you can wrap both functions, like this
const send = XMLHttpRequest.prototype.send;
const _fetch = window.fetch;
XMLHttpRequest.prototype.send = function (body) {
const newBody = JSON.parse(body);
newBody.points = 417;
send.call(this, JSON.stringify(newBody));
};
window.fetch = function(url, options){
let newBody;
if(options.body) {
newBody = JSON.parse(options.body);
newBody.points = 417;
options.body = JSON.stringify(newBody);
}
_fetch.call(this, url, options);
}

JavaScript - Stop redirection without user intervention and get destination URL

I want to run some JS in a webpage so I can click elements that will take me to another webpage and do 2 things:
Get the destination URL.
Stop the redirection.
So far I read about adding an event listener to stop redirection:
window.addEventListener('beforeunload', function (e) {
// Cancel the event
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = '';
});
but there's always a popup and I cannot figure out the destination address.
Edit:
I was able to obtain the destination URL from the microservice by intercepting the XMLHttpRequests so the first problem is solved ... redirection is still an issue.
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
if (method === "GET") {
const urlQuery = "some_discrimination_factor";
const urlPropertyName = "redirection_url";
if(url.endsWith(urlPropertyName)) {
this.onload = function(){
const response = JSON.parse(this.responseText);
if (response.hasOwnProperty(urlPropertyName)) {
console.log(response[urlPropertyName]);
}
};
}
}
xhrOpen.call(this, method, url, async, user, pass);
};
Here's the same thing but using DOM Level 2 Events:
let xhrListener; //use only to avoid multiple listeners error while debugging
const xhrOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
if (method === "GET") {
const urlQuery = "some_discrimination_factor";
const urlPropertyName = "redirection_url";
if(url.endsWith(urlPropertyName)) {
if (xhrListener) { //use only to avoid multiple listeners error while debugging
this.removeEventListener("readystatechange", xhrListener);
}
this.addEventListener("load", function nonAnonymWrap(e){
xhrListener = nonAnonymWrap;//use only to avoid multiple listeners error while debugging
const response = JSON.parse(this.responseText);
if (response.hasOwnProperty(urlPropertyName)) {
console.log(response[urlPropertyName]);
}
});
}
}
xhrOpen.call(this, method, url, async, user, pass);
};
CLICK ME
var dom = document.getElementById('#myEle1');
dom.addListener('click', function($e){
$e.preventDefault(); // stop <a> tag behavior
var url = dom.href; // the url you want
})
like this?
Ockham's razor:
Entities should not be multiplied without necessity.
Being new to JavaScript's rabbit hole I started going heavy on XMLHttpRequest but apparently something simpler was sufficient for my case:
//backup original function in case redirection is needed later
const windowOpen = window.open;
let isRedirectionEnabled = false;
window.open = function() {
//destination URL obtained without redirection
let targetUrl = arguments[0];
console.log(targetUrl);
if(isRedirectionEnabled) {
windowOpen.apply(this, arguments);
}
};

Using MP4box.js and onSegment callback is not called

Base problem: display a H264 live stream in a browser.
Solution: let's just convert it to fragmented mp4 and load chunk-by-chunk via websocket (or XHR) into MSE.
Sounds too easy. But I want to do the fragmentation on client side with pure JS.
So I'm trying to use MP4Box.js. On its readme page it states: it has a demo: "A player that performs on-the-fly fragmentation".
That's the thing I need!
However the onSegment callbacks which should feed MSE are not called at all:
var ws; //for websocket
var mp4box; //for fragmentation
function startVideo() {
mp4box = MP4Box.createFile();
mp4box.onError = function(e) {
console.log("mp4box failed to parse data.");
};
mp4box.onMoovStart = function () {
console.log("Starting to receive File Information");
};
mp4box.onReady = function(info) {
console.log(info.mime);
mp4box.onSegment = function (id, user, buffer, sampleNum) {
console.log("Received segment on track "+id+" for object "+user+" with a length of "+buffer.byteLength+",sampleNum="+sampleNum);
}
var options = { nbSamples: 1000 };
mp4box.setSegmentOptions(info.tracks[0].id, null, options); // I don't need user object this time
var initSegs = mp4box.initializeSegmentation();
mp4box.start();
};
ws = new WebSocket("ws://a_websocket_server_which_serves_h264_file");
ws.binaryType = "arraybuffer";
ws.onmessage = function (event) {
event.data.fileStart = 0; //tried also with event.data.byteOffset, but resulted error.
var nextBufferStart = mp4box.appendBuffer(event.data);
mp4box.flush(); //tried commenting out - unclear documentation!
};
}
window.onload = function() {
startVideo();
}
Now putting this into an HTML file would result this in the JavaScript console:
Starting to receive File Information
video/mp4; codecs="avc1.4d4028"; profiles="isom,iso2,avc1,iso6,mp41"
But nothing happens afterwards. Why is the onSegment not called here? (the h264 file which the websocket-server serves is playable in VLC - however it is not fragmented)
The problem was using the nextBufferStart in a wrong way.
This should be the correct one:
var nextBufferStart = 0;
...
ws.onmessage = function (event) {
event.data.fileStart = nextBufferStart;
nextBufferStart = mp4box.appendBuffer(event.data);
mp4box.flush();
};

Creating global VAR in functions

So I'm having trouble with getting a VAR in a function to be global, I have tried the following resources:
What is the scope of variables in JavaScript?
My previous question was marked as a duplicate but after reviewing the link above it did not help with my issue.
Here is my previous question:
So I'm using OpenTok to create a online conferencing tool and need to grab the session details from an API on a different server. I've created a php script on the other server that grabs session information based on the session id provided by a URL parameter. I know that the php script and most of the JavaScript is working correctly because when I console.log data from the parsed JSON it prints the correct information. However when I try to put the variables into the credentials area I get the following error:
ReferenceError: thesession is not defined
Here is the code used to get the JSON from a PHP script on a separate server:
var url_string = window.location.href;
var url = new URL(url_string);
var session = url.searchParams.get("s");
if (session == '') {
window.location.replace("http://www.google.com");
}
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
var thesession = data.sessionID;
var thetoken = data.token;
console.log(thesession);
console.log(thetoken);
});
let otCore;
const options = {
credentials: {
apiKey: "####",
sessionId: thesession,
token: thetoken
},
And here is a screenshot of the console:
The top console log is "thesession" and the second console log is "thetoken". I have tried looking up the error but can't quite find one with the same usage as mine.
The desired outcome would be that I could using the data from the parsed JSON and use the result as the credentials e.g. data.sessionID which is bound the the VAR thesession.
I know this might be a scope issue, but I'm not sure how I could alter the code to make it work as intended.
Any help would be much appreciated, this one has really got me stumped :)
How would I alter the scope to get the desired function? I have reviewed the link that was given on the previous question, but this didn't help me with my issue.
var thesession = data.sessionID;
Is defined within its execution context, which is the callback function you've passed to getJSON.
One step in the right direction is to reverse the assignment. Assign 'thesession' to the options object within the scope where 'thesession' exists.
const options = {
credentials: {
apiKey: "####",
sessionId: null,
token: thetoken
}
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
var thesession = data.sessionID;
var thetoken = data.token;
console.log(thesession);
console.log(thetoken);
options.credentials.sessionId = thesession;
});
However, it's important to realize that your program is not going to wait for this assignment. It will send the getJSON request, and then continue processing. Your options object won't have a sessionId until the getJSON call finishes and its callback has been invoked.
This would be a good opportunity to delve into Promises, which will help you better understand how to handle the non-blocking nature of javascript.
Your problem is that this line var thesession = data.sessionID is scoped within the function function(err, data) { ... }. In order to allow two functions to use the same variable, you need to make sure that the variable isn't declared somewhere they don't have access to.
It's the difference between this:
function func1() {
var x = 3
}
function func2() {
console.log(x)
}
func1();
func2();
and this:
var x;
function func1() {
x = 3
}
function func2() {
console.log(x)
}
func1();
func2();
Similarly, if you declare var thesession; at the start of your script (or at least outside that other function) then just set it with thesession = data.sessionID, your final part will have access to your variable thesession.
Edit
In context:
var url_string = window.location.href;
var url = new URL(url_string);
var session = url.searchParams.get("s");
var thesession;
var thetoken;
if (session == '') {
window.location.replace("http://www.google.com");
}
var getJSON = function(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function() {
var status = xhr.status;
if (status === 200) {
callback(null, xhr.response);
} else {
callback(status, xhr.response);
}
};
xhr.send();
};
getJSON('http://192.168.64.2/api/meeting/?uid=' + session,
function(err, data) {
if (err !== null) {
console.log('Error');
}
thesession = data.sessionID;
thetoken = data.token;
console.log(thesession);
console.log(thetoken);
});
let otCore;
const options = {
credentials: {
apiKey: "####",
sessionId: thesession,
token: thetoken
},
As a side-note - I'd also recommend not using var and instead just using let of const, depending on if you want your variable to be mutable or not.

Firefox Extension/Addon : Reading a text file from remote server(URL)

After wasting my two days to find out what's going wrong with this script, finally I decide to ask it.
What I am trying to do
I am trying to read a text file from remote server. Then storing all text file updates to an SQLITE database at the time of my Firefox Extension/Addon get loaded.
What I tried
var updatereader = {
start: function () {
//alert('reading update');
var fURL = null;
var ioService = null;
var fURI = null;
var httpChannel = null;
fURL = "http://www.example.com/addon/mlist.txt";
ioService = Components.classes["#mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
fURI = ioService.newURI(fURL, null, null);
httpChannel = ioService.newChannelFromURI(fURI).QueryInterface(Components.interfaces.nsIHttpChannel);
httpChannel.asyncOpen(updatereader.StreamReader, null);
},
onUpdateCompleted: function () {
},
StreamReader:
{
fOutputStream: null,
fPointer: null,
tempFile: "mlist.txt",
onStartRequest: function (aRequest, aContext) {
//alert('onStart');
updatereader.StreamReader.fOutputStream = Components.classes["#mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
updatereader.StreamReader.fPointer = Components.classes["#mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile);
updatereader.StreamReader.fPointer.append(updatereader.StreamReader.tempFile);
updatereader.StreamReader.fOutputStream.init(updatereader.StreamReader.fPointer, 0x02 | 0x20 | 0x08, 0644, 0);
},
onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount) {
//control flow is not entering here - may be here is somehting missing
var sStream = null;
var tempBuffer = null;
sStream = Components.classes["#mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
sStream.init(aInputStream);
tempBuffer = sStream.read(aCount);
updatereader.StreamReader.fOutputStream.write(tempBuffer, aCount);
},
onStopRequest: function (aRequest, aContext, aStatusCode) {
//alert('onStop');
var currentDate = new Date();
if (aStatusCode == 0) {
fileInputStream = Components.classes["#mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
updatereader.StreamReader.fOutputStream.close();
fileInputStream.init(updatereader.StreamReader.fPointer, 0x01, 0, 0);
lineInputStream = fileInputStream.QueryInterface(Components.interfaces.nsILineInputStream);
//pass data to somewhere
var dbH = new dbstore();
dbH.updateData(lineInputStream);
lineInputStream.close();
updatereader.StreamReader.fPointer.remove(false);
updatereader.onUpdateCompleted();
} else {
}
}
}
}
Problem:
Getting nothing in lineInputStream which passes the read data to somewhere else for storing it.
Area of problem:
Program control flow is not entring to this section
onDataAvailable:
Not getting any error.
First of all, there doesn't really seem to be any need to read the file to the disk first (unless it is really, really big).
I'd just use XMLHttpRequest to get the file, which when run from a privileged context (e.g. add-on code, but not a website) can access any and every valid URI.
XMLHttpRequest will simplify almost everything, e.g. no more onDataAvailable, (usually) no more manual text converting, etc.
Also, no need to ever hit the disk during the transfer.
Code would look something like this:
var req = new XMLHttpRequest();
req.open("GET", "http://www.example.com/addon/mlist.txt"); // file:/// would work too, BTW
req.overrideMimeType("text/plain");
req.addEventListener("load", function() {
// Do something with req.responseText
}, false);
req.addEventListener("error", function() {
// Handle error
}, false);
req.send();
If you want to use XMLHttpRequest in a non-window, e.g. js code module or js components, then you need to first initialize a constructor. This is not required for windows, including XUL windows and by that XUL overlays.
// Add XMLHttpRequest constructor, if not already present
if (!('XMLHttpRequest' in this)) {
this.XMLHttpRequest = Components.Constructor("#mozilla.org/xmlextras/xmlhttprequest;1", "nsIXMLHttpRequest");
}
SDK users should use the request module, or net/xhr if a lower-level API is required.
PS: If you're still interested in using raw channels, here is a minimal example I coded up in a Scratchpad (to run, open a Scratchpad for a privileged location, e.g. about:newtab).
You shouldn't alert from your own implementation: alert() will spin the event loop and causes reentrant code, which is not supported in this context.
var {
classes: Cc,
interfaces: Ci,
results: Cr,
utils: Cu
} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm")
Cu.import("resource://gre/modules/Services.jsm");
var ConverterStream = Components.Constructor(
"#mozilla.org/intl/converter-input-stream;1",
"nsIConverterInputStream",
"init");
var RC = Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
function Listener() {
this.content = "";
}
Listener.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener]),
onStartRequest: function(req, ctx) {
console.log("start");
},
onDataAvailable: function(req, ctx, stream, offset, count) {
console.log("data", count);
try {
var cs = new ConverterStream(stream, null /* utf-8 */, 4096, RC);
try {
var str = {};
while (cs.readString(4096, str)) {
this.content += str.value;
}
}
finally {
cs.close();
}
}
catch (ex) {
console.error("data", ex.message, ex);
}
},
onStopRequest: function(req, ctx, status) {
console.log("stop", status,
this.content.substr(0, 20), this.content.length);
}
};
var uri = Services.io.newURI("http://example.org", null, null);
Services.io.newChannelFromURI(uri).asyncOpen(new Listener(), null);

Categories

Resources