Identify tab that made request in Firefox Addon SDK - javascript

I'm using the Firefox Addon SDK to build something that monitors and displays the HTTP traffic in the browser. Similar to HTTPFox or Live HTTP Headers. I am interested in identifying which tab in the browser (if any) generated the request
Using the observer-service I am monitoring for "http-on-examine-response" events. I have code like the following to identify the nsIDomWindow that generated the request:
const observer = require("observer-service"),
{Ci} = require("chrome");
function getTabFromChannel(channel) {
try {
var noteCB= channel.notificationCallbacks ? channel.notificationCallbacks : channel.loadGroup.notificationCallbacks;
if (!noteCB) { return null; }
var domWin = noteCB.getInterface(Ci.nsIDOMWindow);
return domWin.top;
} catch (e) {
dump(e + "\n");
return null;
}
}
function logHTTPTraffic(sub, data) {
sub.QueryInterface(Ci.nsIHttpChannel);
var ab = getTabFromChannel(sub);
console.log(tab);
}
observer.add("http-on-examine-response", logHTTPTraffic);
Mostly cribbed from the documentation for how to identify the browser that generated the request. Some is also taken from the Google PageSpeed Firefox addon.
Is there a recommended or preferred way to go from the nsIDOMWindow object domWin to a tab element in the SDK tabs module?
I've considered something hacky like scanning the tabs list for one with a URL that matches the URL for domWin, but then I have to worry about multiple tabs having the same URL.

You have to keep using the internal packages. From what I can tell, getTabForWindow() function in api-utils/lib/tabs/tab.js package does exactly what you want. Untested code:
var tabsLib = require("sdk/tabs/tab.js");
return tabsLib.getTabForWindow(domWin.top);

The API has changed since this was originally asked/answered...
It should now (as of 1.15) be:
return require("sdk/tabs/utils").getTabForWindow(domWin.top);

As of Addon SDK version 1.13 change:
var tabsLib = require("tabs/tab.js");
to
var tabsLib = require("sdk/tabs/helpers.js");

If anyone still cares about this:
Although the Addon SDK is being deprecated in support of the newer WebExtensions API, I want to point out that
var a_tab = require("sdk/tabs/utils").getTabForContentWindow(window)
returns a different 'tab' object than the one you would typically get by using
worker.tab in a PageMod.
For example, a_tab will not have the 'id' attribute, but would have linkedPanel property that's similar to the 'id' attribute.

Related

Serve different cache versions using the same URL through cloudflare worker

There's a very common problem I have seen from many people who use different versions of their site for mobile and desktop, many themes have this feature. The issue is Cloudflare caches the same page regardless of the user device causing mixes and inconsistencies between desktop and mobile versions.
The most common solution is to separate the mobile version into another URL, but in my case, I want to use the same URL and make Cloudflare cache work for both desktop and mobile properly.
I found this very nice guide showing how to fix this issue, however, the worker code seems to be outdated, I had to modify some parts to make it work.
I created a new subdomain for my workers and then assigned the route to my site so it starts running.
The worker is caching everything, however, it does not have the desired feature of having different cached versions according to the device.
async function run(event) {
const { request } = event;
const cache = caches.default;
// Read the user agent of the request
const ua = request.headers.get('user-agent');
let uaValue;
if (ua.match(/mobile/i)) {
uaValue = 'mobile';
} else {
uaValue = 'desktop';
}
console.log(uaValue);
// Construct a new response object which distinguishes the cache key by device
// type.
const url = new URL(request.url);
url.searchParams.set('ua', uaValue);
const newRequest = new Request(url, request);
let response = await cache.match(newRequest);
if (!response) {
// Use the original request object when fetching the response from the
// server to avoid passing on the query parameters to our backend.
response = await fetch(request, { cf: { cacheTtl: 14400 } });
// Store the cached response with our extended query parameters.
event.waitUntil(cache.put(newRequest, response.clone()));
}
return response;
}
addEventListener('fetch', (event) => {
event.respondWith(run(event));
});
it is indeed detecting the right user agent, but it should be having two separate cache versions according to the assigned query string...
I think maybe I'm missing some configuration, I don't know why it's not working as expected. As it is right now I still get mixed my mobile and desktop cache versions.
The problem here is that fetch() itself already does normal caching, independent of your use of the Cache API around it. So fetch() might still return a cached response that is for the wrong UA.
If you could make your back-end ignore the query parameter, then you could include the query in the request passed to fetch(), so that it correctly caches the two results differently. (Enterprise customers can use custom cache keys as a way to accomplish this without changing the URL.)
If you do that, then you can also remove the cache.match() and cache.put() calls since fetch() itself will handle caching.

Simulate "visit" for javascript analytics tracker

I am using Piwik/Matomo's tracker to provide my users with custom JS trackers, and provide a curated dashboard of analytics that are tailor made for them. One problem I am facing consistently is verifying if the tracker is installed and working correctly.
I have tried using file_get_contents and/or cURL to fetch the page and check if the tracker exists, but this doesn't always work. So I am trying to instead simulate a visit, and see if the tracker sends me any data when it happens.
Since fget/curl do not trigger javascript, is there an alternative (and lightweight) method to fire the page's javascript and trigger a visit for testing?
Update : I implemented this by using PhantomJS as suggested, with the overall function being something like this. Haven't yet tested this extensively for all my users, is there a more elegant solution? -
checktracker
{
if (data exists & data > 0)
{
return true
}
else if (data exists & data = 0)
{
simulate visit with phantomJS //Relevant to question
check again
if ( still 0 )
{
return false
}
else
{
return true
}
}
else
{
invalid site id
}
}
So you want to automatically check if a specific website has integrated Matomo correctly? I recently wanted to do the same to create a browser extention to quickly debug common errors.
One way would be checking the DOM. The Matomo Tracking Code adds a <script>-Tag to the website, so you can check the existence of it via JavaScript:
function getDomElements() {
var allElements = document.getElementsByTagName('script');
for (var i = 0, n = allElements.length; i < n; i++) {
if (allElements[i].hasAttribute("src") && allElements[i].getAttribute("src").endsWith("piwik.js")) { // TODO: support renamed piwik.js
return allElements[i];
}
}
}
But if you also have access to the JS console, the probably better solution would be checking if the tracking code has initialized correctly:
If something like this outputs your Matomo URL, chances are high that the tracking code is embedded correctly.
var tracker == window.Piwik.getAsyncTracker()
console.log(tracker.getPiwikUrl())
Of course it can still fail (e.g. if the server returns 403 on the piwik.php request, but if you control the server, this shouldn't happen)
To run the check automatically, you could look into Headless Chrome or Firefox.

Writing a chrome extension to change base url with saved presets

I work with SAAS offering (ServiceNow) with 5 different environments. I find myself frequently switching environments where the only difference is the base URL.
I've never done any plug-in work so I'm not sure if this is feasible but I'd like to set my a few base URL presets and be able to take the current URL and just swap the base with a click of a button.
It's totally possible.
The pointer in general direction is the chrome.tabs API: you'll be able to manipulate a tab's URL with it.
UI-wise, you get a button on the toolbar called a Browser Action; you can simply do something when you click on it, or you can have a small UI page drop down from it. You may also be interested in chrome.commands API to add keyboard shortcuts.
Here's a mock for the simplest architecture possible: a background script that swaps between 2 base domains on click (note that it needs "activeTab" permission).
var BASE1 = "example.com";
var BASE2 = "example.org";
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function baseToRegExp(base) {
return new RegExp("^(https?:\/\/[^\/]*)(" + escapeRegExp(base) + ")/");
}
var BASE1RegExp = baseToRegExp(BASE1);
var BASE2RegExp = baseToRegExp(BASE2);
chrome.browserAction.onClicked.addListener(function(tab) {
if (tab.url.match(BASE1RegExp)) {
chrome.tabs.update(tab.id, {
url: tab.url.replace(BASE1RegExp, "$1"+BASE2+"/")
});
} else if (tab.url.match(BASE2RegExp)) {
chrome.tabs.update(tab.id, {
url: tab.url.replace(BASE2RegExp, "$1"+BASE1+"/")
});
}
});
There are many beginner tutorials for Chrome Extensions; I would recommend starting at the Overview page.

Issuing a synchronous HTTP GET request or invoking shell script in JavaScript from iOS UIAutomation

I am trying to use Apple's UIAutomation to write unit tests for an iOS Application that has a server-side component. In order to setup the test server in various states (as well as simulate two clients communicating through my server), I would like to issue HTTP get requests from within my javascript-based test.
Can anyone provide an example of how to either issue HTTP GET requests directly from within UIAutomation javascript tests, or how to invoke a shell script from within my UIAutomation javascript tests?
FWIW, most of the core objects made available by all browsers are missing within the UIAutomation runtime. Try to use XMLHTTPRequest for example and you will get an exception reporting that it cannot find the variable.
Thanks!
Folks,
I was able to work around this by sending HTTP requests to the iOS client to process and return the results in a UIAlertView. Note that all iOS code modifications are wrapped in #if DEBUG conditional compilation directives.
First, setup your client to send out notifications in the event of a device shake. Read this post for more information.
Next, in your iOS main app delegate add this code:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(deviceShakenShowDebug:)
name:#"DeviceShaken"
object:nil];
Then add a method that looks something like this:
- (void) deviceShakenShowDebug:(id)sender
{
if (!self.textFieldEnterDebugArgs)
{
self.textFieldEnterDebugArgs = [[[UITextField alloc] initWithFrame:CGRectMake(0, 0, 260.0, 25.0)] autorelease];
self.textFieldEnterDebugArgs.accessibilityLabel = #"AlertDebugArgsField";
self.textFieldEnterDebugArgs.isAccessibilityElement = YES;
[self.textFieldEnterDebugArgs setBackgroundColor:[UIColor whiteColor]];
[self.tabBarController.selectedViewController.view addSubview:self.textFieldEnterDebugArgs];
[self.tabBarController.selectedViewController.view bringSubviewToFront:self.textFieldEnterDebugArgs];
}
else
{
if ([self.textFieldEnterDebugArgs.text length] > 0)
{
if ([self.textFieldEnterDebugArgs.text hasPrefix:#"http://"])
{
[self doDebugHttpRequest:self.textFieldEnterDebugArgs.text];
}
}
}
}
- (void)requestDidFinishLoad:(TTURLRequest*)request
{
NSString *response = [[[NSString alloc] initWithData:((TTURLDataResponse*)request.response).data
encoding:NSUTF8StringEncoding] autorelease];
UIAlertView *resultAlert =
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Request Loaded",#"")
message:response
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK",#"")
otherButtonTitles:nil] autorelease];
resultAlert.accessibilityLabel = #"AlertDebugResult";
[resultAlert show];
}
This code will add a UITextField to the very top view controller after a shake, slapped right above any navigation bar or other UI element. UIAutomation, or you the user, can manually enter a URL into this UITextField. When you shake the device again, if the text begins with "http" it will issue an HTTP request in code (exercise for the reader to implement doDebugHttpRequest).
Then, in my UIAutomation JavaScript file, I have defined the following two functions:
function httpGet(url, delayInSec) {
if (!delayInSec) delay = 1;
var alertDebugResultSeen = false;
var httpResponseValue = null;
UIATarget.onAlert = function onAlert(alert) {
httpResponseValue = alert.staticTexts().toArray()[1].name();
alert.buttons()[0].tap();
alertDebugResultSeen = true;
}
var target = UIATarget.localTarget();
var application = target.frontMostApp();
target.shake(); // bring up the input field
application.mainWindow().textFields()["AlertDebugArgsField"].setValue(url);
target.shake(); // send back to be processed
target.delay(delayInSec);
assertTrue(alertDebugResultSeen);
return httpResponseValue;
}
function httpGetJSON(url, delayInSec) {
var response = httpGet(url, delayInSec);
return eval('(' + response + ')');
}
Now, in my javascript file, I can call
httpGet('http://localhost:3000/do_something')
and it will execute an HTTP request. If I want JSON data back from the server, I call
var jsonResponse = httpGetJSON('http://localhost:3000/do_something')
If I know it is going to be a long-running call, I call
var jsonResponse = httpGetJSON('http://localhost:3000/do_something', 10 /* timeout */)
I've been using this approach successfully now for several weeks.
Try performTaskWithPathArgumentsTimeout
UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", "http://google.com", 30);
Just a small correction. The answer that suggests using UIATarget.host().performTaskWithPathArgumentsTimeout is an easy way to make a request on a URL in iOS 5.0+, but the syntax of the example is incorrect. Here is the correct way to make this call:
UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", ["http://google.com"], 30);
The "[" around the "args" param is important, and the test will die with an exception similar to the following if you forget the brackets:
Error: -[__NSCFString count]: unrecognized selector sent to instance
Here is a fully working example that hits google.com and logs all the output:
var result = UIATarget.host().performTaskWithPathArgumentsTimeout("/usr/bin/curl", ["http://www.google.com"], 30);
UIALogger.logDebug("exitCode: " + result.exitCode);
UIALogger.logDebug("stdout: " + result.stdout);
UIALogger.logDebug("stderr: " + result.stderr);
+1 for creative use of "shake()". However, that's not an option for some projects, especially those that actually use the shake feature.
Think outside the box. Do the fetching with something else (Python, Ruby, node.js, bash+wget, etc). Then, you can use the pre-canned response and auto-generate the ui-test.js on the fly by including that dynamically generated json payload as the "sample data" into the test. Then you simply execute the test.
In my opinion, the test is the test, leave that alone. The test data you are using, if it's that dynamic, it ought to be separated from the test itself. By doing it this way of fetching / generating JSON, and referencing it from the test, you can update that JSON however often you like, either immediately right before every test, or on a set interval like when you know the server has been updated. I'm not sure you would want to generate it while the test is running, that seems like it would create problems. Taking it to the next level, you could get fancy and use functions that calculate what values ought to be based on other values, and expose them as "dynamic properties" of the data, rather than that math being inside the test, but at that point I think the discussion is more of an academic one rather than the practical one of how.
Apple has recently updated UIAutomation to include a new UIAHost element for performing a task on the Host that is running the instance of Instruments that is executing the tests.

How to know if the network is (dis)connected?

How can I know, in Xul, if the network is (dis)connected?
--update
Using:
function observe(aSubject, aTopic, aState) {
if (aTopic == "network:offline-status-changed") {
write("STATUS CHANGED!");
}
}
var os = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
os.addObserver(observe, "network:offline-status-changed", false);
and the preference:
pref("network.manage-offline-status", true);
it's not working.. There's a bug report here, but I don't think it has something to do with it.
--
Actually I think it's not possible to be notified, as even in Firefox we're never notified, and the user need to manually mark "work offline" if he wants the browser to know that it's offline..
--
Screenshot my of Firefox "about:config" filtering for "offline" string, unfortunately, there no "network.manage-offline-status":
You should be able to use navigator.onLine. Here is the help page
https://developer.mozilla.org/en/Online_and_offline_events
navigator.onLine is a property that
maintains a true/false value (true for
online, false for offline). This
property is updated whenever the user
switches into "Offline Mode" by
selecting the corresponding menu item
(File -> Work Offline in Firefox).
Another solution (as commented by #Neil):
Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.addObserver(myF­unction, "network:offline-status-changed", false);
The best way I found is to use the following javascript code, that behaves like a ping, and make the test with some big websites, and assume that if none of them answers, so the network must be disconnected.
var ping = {};
ping = {
img:null,
imgPreload:null,
timer:null,
init:function() {
var sess = new Date();
var nocache = sess.getTime();
var imguri = ping.img+"?time="+nocache;
var ping.imgPreload = new Image();
ping.imgPreload.onload = function() {
clearTimeout(ping.timer);
ping.timer = null;
alert("Domain is available");
};
ping.imgPreload.src = imguri;
ping.timer = setTimeout("ping.fail_to_ping()",60000);
},
fail_to_ping:function() {
clearTimeout(ping.timer);
ping.timer = null;
ping.imgPreload = null;
alert("Ping to domain failed!");
}
};
(from http://crynobone.com/ci/index.php/archive/view/852)
--update
But, as it's not a reliable solution (as you can't rely that the image will be in the website forever), the best solution might be to develop a new XPCom component.
Eh... as per HTML5 (read echmascript 5), the on-/offline events are available.
See it here at Mozilla Hacks
Edit 20/4/2011:
I just encountered an update for this answer, when i was watching a podcast from MS MIX11:
http://channel9.msdn.com/Events/MIX/MIX11/HTM14 around time 43:36, the lecturer is actually talking about the window.navigator.onLine property, where he uses it for detecting if the browser (and the computer) is online. Then he uses the online event to do something when he gets online again.
This method is only available in modern browsers, however. So IE 8 and below have to poll for the connection.

Categories