firefox detect tab id in "sdk/system/events" api - javascript

Good day.
i have problem with porting chromium extension to firefox.
i need to detect all outgoing request and id's of tabs to which it belongs.
to detect requests i using system/events api, but i can't find a way how to detect id of tab from incomming events. As i understand this events is xpcom objects and i should use QueryInterface to get some interface to get some other interface to get some other interface to get some other interface ..... to get some other interface to get id of tab from it (just like in COM implementation in windows), but i can't find which interface i need, can't find documentation about this events at all...
code which i using in chromium:
chrome.webRequest.onBeforeRequest.addListener(
function(info) {
if(info.tabId)
//do stuff here
}
so it's what i want to achieve from firefox...
code which i currently write for firefox:
exports.main = function(options)
{
//stuf here ....
........
function listener(event)
{
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
console.log(channel);
//TODO: get tab here somehow
}
events.on("http-on-opening-request", listener);
}
i have looked on xpcom docs few days, but still have not enough info to implement this simple thing... so if someone have success with this, please help.

I just found a code snippet for getting the browser that fires the http-on-modify-request notification. The code there seems to be broken but I used some of it to create this function to get a tab from the channel.
const getTabFromChannel = (aChannel) => {
try {
let notificationCallbacks = aChannel.notificationCallbacks || aChannel.loadGroup.notificationCallbacks;
if (!notificationCallbacks)
return null;
let domWin = notificationCallbacks.getInterface(Ci.nsIDOMWindow);
let chromeTab = tabsUtils.getTabForContentWindow(domWin);
return getSdkTabFromChromeTab(chromeTab);
}
catch (e) {
// some type errors happen here, not sure how to handle them
console.log(e);
return null;
}
}
This function converts the low-level tab to a high-level tab. Depending on which one you need you could skip this function of course. Again, in the latest SDK you probably can replace it with tabs.viewFor(chromeTab).
const tabs = require("sdk/tabs");
const tabsUtils = require("sdk/tabs/utils");
const getSdkTabFromChromeTab = (chromeTab) => {
const tabId = tabsUtils.getTabId(chromeTab);
for each (let sdkTab in tabs){
if (sdkTab.id === tabId) {
return sdkTab;
}
}
return null;
};
There seems to be a problem that the listener fails when switching between windows when using system/events. Use Services.obs.addObserver instead:
const httpRequestObserver = {
observe: function (subject, topic, data) {
var channel = subject.QueryInterface(Ci.nsIHttpChannel);
console.log("channel");
var tab = getTabFromChannel(channel);
if(tab) {
console.log("request by tab", tab.id);
}
}
}
exports.main = function() {
Cu.import('resource://gre/modules/Services.jsm');
Services.obs.addObserver(httpRequestObserver, 'http-on-opening-request', false);
}
I can only hope that it works for all the requests you need to detect. The documentation already mentions some cases where it won't work:
Note that some HTTP requests aren't associated with a tab; for example, RSS feed updates, extension manager requests, XHR requests from XPCOM components, etc.

The article Listening to events on all tabs describes how to set up web progress listeners for tabs. With this listener you can get requests and redirects.
const tabsUtils = require("sdk/tabs/utils");
const listener = {
QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
onLocationChange: (browser, progress, request, uri) => {
let tab = tabsUtils.getTabForContentWindow(progress.DOMWindow);
// ...
},
onStateChange: (browser, progress, request, state) => {
let tab = tabsUtils.getTabForContentWindow(progress.DOMWindow);
// ...
}
// ...
};
getChromeWindow(sdkWindow).getBrowser().addTabsProgressListener(listener);
At some point you may need to convert between low- and high-level tabs or chrome/dom/sdk windows which is implemented really bad and confusing. An sdk window in this case is one you get with windows.browserWindows, the chrome window has a reference to the gBrowser. If you are using the latest sdk maybe this helps: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_API/tabs#Converting_to_XUL_tabs and https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/windows#Converting_to_DOM_windows. I used this function to get the chrome window from a sdk window: https://bugzilla.mozilla.org/show_bug.cgi?id=695143#c15
const { BrowserWindow } = require('sdk/windows');
const { windows } = require('sdk/window/utils');
function getChromeWindow(sdkWindow) {
// to include private window use the as second argument
// { includePrivate: true }
for (let window of windows('navigator:browser'))
if (BrowserWindow({window: window}) === sdkWindow)
return window;
return null;
}

Related

ShowOpenDialog not working on recent versions of electron-js

I'm new on electronjs and developing a small application that reads a json file and build a small html form and return the values entered by the user.
So I've developed small scripts in javascript that link to html 'button' tags to call dialogs so that a user can enter directories, files and save the final form. Everything works nicely... on electronjs "^3.1.13". But if I'm updating to a recent version of the lib ("^8.2.5"), then all my cool ShowOpenDialog don't work at all. Any clue of what happens?
Here is the script to open a folder if it helps:
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions,
fileNames => {
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Thanks a lot for any help.
Apart from the fact that the call to dialog.showOpenDialog has indeed been updated in recent versions of Electron, and returns a promise instead of making use of a callback function, there is another flaw in your updated code: reading the above-mentioned documentation page shows that getCurrentWindow() is not a method of dialog; it can be obtained from remote instead, so you have to add it explicitely:
const { dialog, getCurrentWindow } = require('electron').remote;
then simply call it from inside dialog.showOpenDialog:
dialog.showOpenDialog( getCurrentWindow(), dialogOptions).then(result => {
but this is an error you could have caught yourself by looking at the DevTools's console, which would display:
TypeError: dialog.getCurrentWindow is not a function
Recent version of showOpenDialog receives two arguments: optional BrowserWindow, and options as second argument. It returns promise and not requires callback.
https://github.com/electron/electron/blob/8-x-y/docs/api/dialog.md#dialogshowopendialogbrowserwindow-options
So you need to change you callback logic to promises.
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions
).then((fileNames)=>{
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
}).catch(err=>console.log('Handle Error',err))
};
asyncBtn.addEventListener("click", onButtonClick);
thanks a lot Vladimir. So I've tried to update my code as explained, updating electron package to version 8.2.5 and modifying the script as you explained but it's not going any better. If I got it well, this code should be correct, but doesn't work on electron 8.2.5. Any error you still see on this?
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate']
};
dialog.showOpenDialog( dialog.getCurrentWindow(), dialogOptions).then(result => {
if(!result.canceled) {
replyField.value = result.filePaths[0];
}
}).catch(err => {
console.log(err)
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Ok, finally got it. Apart from the most appreciated help I had, I missed
"webPreferences": {
nodeIntegration: true
}
in the main.js to make it work.
The discovering of the Developer Tools were of great help as well :)
Now everything is fine again. Thanks a lot!

How to launch a normal download from a Firefox Add-on SDK extension

I'm developing an Add-on SDK extension for Firefox. I find that I need to be able to launch a download as if the user requested it, that is, either showing the normal file save dialog or saving the file to wherever the user prefers, as it could be configured under preferences > content.
Every single post or documentation regarding downloads seem to only take in consideration the scenario where I know where to download the file, but that is not what I need in this case. In this case, it needs to be as if the user started the download.
How can this be accomplished, preferably via the methods of the SDK?
Well, you could just initiate an actual save.
Initiating a save link from your code:
In the context menu the oncommand value is gContextMenu.saveLink();. saveLink() is defined in: chrome://browser/content/nsContextMenu.js. It does some housekeeping and then calls saveHelper() which is defined in the same file. You could just call saveHelper() with appropriate arguments. It is included in panels from chrome://browser/content/web-panels.xul with:
<script type="application/javascript"
src="chrome://browser/content/nsContextMenu.js"/>
Then the gContextMenu variable declared in chrome://browser/content/browser.js as null is assigned:
gContextMenu = new nsContextMenu(this, event.shiftKey);
in the onpopupshowing event handler for context menus. It is returned to:
'gContextMenu = null;'
in the onpopuphiding event handler.
If you want to use it in your own code you can do:
let urlToSave = "http://stackoverflow.com/questions/26694442";
let linkText = "Some Link text";
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Overlay and bootstrap:
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
//*/
/* Add-on SDK:
var {Cc, Ci, Cr} = require("chrome");
//*/
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
//Create an object in which we attach nsContextMenu.js.
// It needs some support properties/functions which
// nsContextMenu.js assumes are part of its context.
let contextMenuObj = {
makeURI: function (aURL, aOriginCharset, aBaseURI) {
var ioService = Cc["#mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
},
gPrefService: Cc["#mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.QueryInterface(Ci.nsIPrefBranch),
Cc: Cc,
Ci: Ci,
Cr: Cr
};
Cc["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript("chrome://browser/content/nsContextMenu.js"
,contextMenuObj);
//Re-define the initMenu function, as there is not a desire to actually
// initialize a menu.
contextMenuObj.nsContextMenu.prototype.initMenu = function() { };
let myContextMenu = new contextMenuObj.nsContextMenu();
//Save the specified URL
myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document);
Alternative to using loadSubScript to load nsContextMenu.js:
My preference is to use loadSubScript to load the saveHelper code from nsContextMenu.js. This keeps the code up to date with any changes which are made in future Firefox releases. However, it introduces the dependency that you are using a function from a non-official API. Thus, it might change in some way in future Firefox release and require changes in your add-on. The alternative is to duplicate the saveHelper() code in your extension. It is defined as the following:
// Helper function to wait for appropriate MIME-type headers and
// then prompt the user with a file picker
saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) {
// canonical def in nsURILoader.h
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
// an object to proxy the data through to
// nsIExternalHelperAppService.doContent, which will wait for the
// appropriate MIME-type headers and then prompt the user with a
// file picker
function saveAsListener() {}
saveAsListener.prototype = {
extListener: null,
onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
// if the timer fired, the error status will have been caused by that,
// and we'll be restarting in onStopRequest, so no reason to notify
// the user
if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
return;
timer.cancel();
// some other error occured; notify the user...
if (!Components.isSuccessCode(aRequest.status)) {
try {
const sbs = Cc["#mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
const bundle = sbs.createBundle(
"chrome://mozapps/locale/downloads/downloads.properties");
const title = bundle.GetStringFromName("downloadErrorAlertTitle");
const msg = bundle.GetStringFromName("downloadErrorGeneric");
const promptSvc = Cc["#mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
promptSvc.alert(doc.defaultView, title, msg);
} catch (ex) {}
return;
}
var extHelperAppSvc =
Cc["#mozilla.org/uriloader/external-helper-app-service;1"].
getService(Ci.nsIExternalHelperAppService);
var channel = aRequest.QueryInterface(Ci.nsIChannel);
this.extListener =
extHelperAppSvc.doContent(channel.contentType, aRequest,
doc.defaultView, true);
this.extListener.onStartRequest(aRequest, aContext);
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
// it can without waiting.
saveURL(linkURL, linkText, dialogTitle, bypassCache, false,
doc.documentURIObject, doc);
}
if (this.extListener)
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
},
onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
aInputStream,
aOffset, aCount) {
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
}
function callbacks() {}
callbacks.prototype = {
getInterface: function sLA_callbacks_getInterface(aIID) {
if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
// If the channel demands authentication prompt, we must cancel it
// because the save-as-timer would expire and cancel the channel
// before we get credentials from user. Both authentication dialog
// and save as dialog would appear on the screen as we fall back to
// the old fashioned way after the timeout.
timer.cancel();
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
// if it we don't have the headers after a short time, the user
// won't have received any feedback from their click. that's bad. so
// we give up waiting for the filename.
function timerCallback() {}
timerCallback.prototype = {
notify: function sLA_timer_notify(aTimer) {
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
return;
}
}
// set up a channel to do the saving
var ioService = Cc["#mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var channel = ioService.newChannelFromURI(makeURI(linkURL));
if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
channel.setPrivate(docIsPrivate);
}
channel.notificationCallbacks = new callbacks();
let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
if (bypassCache)
flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
if (channel instanceof Ci.nsICachingChannel)
flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
channel.loadFlags |= flags;
if (channel instanceof Ci.nsIHttpChannel) {
channel.referrer = doc.documentURIObject;
if (channel instanceof Ci.nsIHttpChannelInternal)
channel.forceAllowThirdPartyCookie = true;
}
// fallback to the old way if we don't see the headers quickly
var timeToWait =
gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(new timerCallback(), timeToWait,
timer.TYPE_ONE_SHOT);
// kick off the channel with our proxy object as the listener
channel.asyncOpen(new saveAsListener(), null);
}
Like #canuckistani said, use the Downloads.jsm
let { Downloads } = require("resource://gre/modules/Downloads.jsm");
let { OS } = require("resource://gre/modules/osfile.jsm")
let { Task } = require("resource://gre/modules/Task.jsm");
Task.spawn(function () {
yield Downloads.fetch("http://www.mozilla.org/",
OS.Path.join(OS.Constants.Path.tmpDir,
"example-download.html"));
console.log("example-download.html has been downloaded.");
}).then(null, Components.utils.reportError);

Fast way to redirect tab when new page is loading

I'm trying to redirect a tab to a new page when the URL matches my pattern before it's done loading. The method I came up with does the redirection after a good part of the page is done loading yet.
var tabs = require("sdk/tabs");
var tab_utils = require("sdk/tabs/utils");
function logShow(tab) {
console.log(tab.url + " is loaded; " + pattern.test(tab.url));
if (pattern.test(tab.url)) {
var lowLevelTab = viewFor(tab);
console.log(tab_utils.setTabURL (lowLevelTab, newURL(tab.url)));
// also simply replacing this bit with
// tab.url = "foo" doesn't speed things up
}
}
tabs.on('load', logShow);
Is there a good way of calling setTabURL (...) earlier?
I finally found the best way to do it:
function listener(event) {
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
var url = event.subject.URI.spec;
// Here you should evaluate the url and decide if make a redirect or not.
if (pattern.test(url)) {
// If you want to redirect to another url,
// you have to abort current request, see: [1] (links below)
channel.cancel(Cr.NS_BINDING_ABORTED);
// Set the current gbrowser object (since
// the user may have several windows/tabs)
var goodies = loadContextGoodies(channel);
var domWin = goodies.aDOMWindow; // method suggested by
var gBrowser = goodies.gBrowser; // Noitidart [2] (links below)
var browser = goodies.browser; // for reference see comment below
var htmlWindow = goodies.contentWindow;
// and load the fixed URI
browser.loadURI(newUrl(url));
} else {
// do nothing, let Firefox keep going on the normal flow
}
}
exports.main = function() {
events.on("http-on-modify-request", listener);
}
credit where credit is due: answer by matagus (on question asked by Andrew)
[1]: Link: Intercepting Page Loads
[2]: Noitidart: 'from topics: How can I change the User Agent in just one tab of Firefox? and Is it possible to know the target DOMWindow for an HTTPRequest?'
Never used sdk/tabs before, but you could load your content hidden.
Once your page has loaded your logShow function will run.
Then build into this function some "reveal body" functionality.

Message Manager API sendAsyncMessage callback

I can return a value if I send a sync message:
// frame script
var chromeBtnText = sendSyncMessage("getChromeToolbarButtonText");
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
// chrome script
messageManager.addMessageListener("getChromeToolbarButtonText", listener);
function listener(message) {
return document.getElementById('myChromeToolbarButton').label.value;
}
How do I achieve this with a callback with sendAsyncMessage?
I was hoping to do something like:
// frame script
function myCallback(val) {
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
var chromeBtnText = sendAsyncMessage("getChromeToolbarButtonText", null, myCallback);
There is no callback for replies. In fact, there is no reply at all. The return value from the chrome message listener is simply ignored for async messages.
To do fully async communication, you'd have to send another message containing the reply.
Frame script
addMessageListener("getChromeToolbarButtonTextReply", function(message) {
alert(message.data.btnText);
});
sendAsyncMessage("getChromeToolbarButtonText");
Chrome
messageManager.addMessageListener("getChromeToolbarButtonText", function(message) {
var btnText = document.getElementById('myChromeToolbarButton').label.value;
// Only send message to the frame script/message manager
// that actually asked for it.
message.target.messageManager.sendAsyncMessage(
"getChromeToolbarButtonTextReply",
{btnText: btnText}
);
});
PS: All messages share a namespace. So to avoid conflicts when another piece of code wants to use the same name getChromeToolbarButtonText, you better choose a more unique name in the first place, like prefixing your messages with your add-on name my-unique-addoon:getChromeToolbarButtonText or something like that. ;)
I was also hoping to do something similar:
messageManager.sendAsyncMessage("my-addon-framescript-message", null, myCallback);
I'm going the other direction so the myCallback would be in chrome but it's exactly the same principle.
I'd used similar approaches to #Noitidart and #nmaier before but in this new case I wanted to bind to some local data so myCallback can behave differently based on the application state at the time the first message was sent rather than at the time the callback is executed, all while allowing for the possibility of multiple message round-trips being in progress concurrently.
Chrome:
let someLocalState = { "hello": "world" };
let callbackName = "my-addon-somethingUnique"; // based on current state or maybe generate a UUID
let myCallback = function(message) {
messageManager.removeMessageListener(callbackName, myCallback);
//message.data.foo == "bar"
//someLocalState.hello == "world"
}.bind(this); // .bind(this) is optional but useful if the local state is attached to the current object
messageManager.addMessageListener(callbackName, myCallback);
messageManager.sendAsyncMessage("my-addon-framescript-message", { callbackName: callbackName } );
Framescript:
let messageHandler = function(message) {
let responseData = { foo: "bar" };
sendAsyncMessage(message.data.callbackName, responseData);
};
addMessageListener("my-addon-framescript-message", messageHandler);
There's a real-world example here: https://github.com/luckyrat/KeeFox/commit/c50f99033d2d07068140438816f8cc5e5e290da9
It should be possible for Firefox to be improved to encapsulate this functionality in the built-in messageManager one day but in the mean-time this approach works well and with a surprisingly small amount of boiler-plate code.
in this snippet below. i add the callback before sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarbuttonText'... as i know it will send back. Then I remove it after callback executes. I know I don't have to but just to kind of make it act like real callback, just to kind of show people, maybe it helps someone understand.
Frame:
/////// frame script
function CALLBACK_getChromeToolbarButtonText(val) {
removeMessageListner('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //remove the callback
var chromeBtnText = val;
if (chromeBtnText == 'blah') {
alert('tool is blah');
}
}
addMessageListener('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage', CALLBACK_getChromeToolbarButtonText); //add the callback
var chromeBtnText = sendAsyncMessage("my-addon-id#jetpack:getChromeToolbarButtonText", null);
Chrome:
////// chrome script
messageManager.addMessageListener("my-addon-id#jetpack:getChromeToolbarButtonText", listener);
function listener() {
var val = document.getElementById('myChromeToolbarButton').label.value;
sendAsyncMessage('my-addon-id#jetpack:getChromeToolbarButtonTextCallbackMessage',val);
}

Help me create a Firefox extension (Javascript XPCOM Component)

I've been looking at different tutorials and I know I'm close but I'm getting lost in implementation details because some of them are a little bit dated and a few things have changed since Firefox 3. I have already written the javascript for the firefox extension, now I need to make it into an XPCOM component.
This is the functionality that I need:
My Javascript file is simple, I have two functions startServer() and stopServer. I need to run startServer() when the browser starts and stopServer() when firefox quits.
Edit:
I've updated my code with a working solution (thanks to Neil). The following is in MyExtension/components/myextension.js.
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const CI = Components.interfaces, CC = Components.classes, CR = Components.results;
// class declaration
function MyExtension() {}
MyExtension.prototype = {
classDescription: "My Firefox Extension",
classID: Components.ID("{xxxx-xxxx-xxx-xxxxx}"),
contractID: "#example.com/MyExtension;1",
QueryInterface: XPCOMUtils.generateQI([CI.nsIObserver]),
// add to category manager
_xpcom_categories: [{
category: "profile-after-change"
}],
// start socket server
startServer: function () { /* socket initialization code */ },
// stop socket server
stopServer: function () { /* stop server */ },
observe: function(aSubject, aTopic, aData)
{
var obs = CC["#mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
switch (aTopic)
{
case "quit-application":
this.stopServer();
obs.removeObserver(this, "quit-application");
break;
case "profile-after-change":
this.startServer();
obs.addObserver(this, "quit-application", false);
break;
default:
throw Components.Exception("Unknown topic: " + aTopic);
}
}
};
var components = [MyExtension];
function NSGetModule(compMgr, fileSpec) {
return XPCOMUtils.generateModule(components);
}
As far as I can tell, all of your code goes into your component.
You need a JavaScript object that represents your component and register it with the component registrar. (It can be a new object or you can multitask an existing object.) The way this is done depends on whether you're targetting Firefox 3.x or Firefox 4.
You need to register for the profile-after-change notification using the category manager. The way this is done also depends on whether you're targetting Firefox 3, Firefox 3.5/6 or Firefox 4.
When the profile-after-change notification fires, your component is then created and the observe method is called. This is where you start your server and also ask to observe the quit-application notification. Note that this also calls the observe method, so it has to check which notification it's getting.
function myExt() {}
myExt.prototype = {
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "quit-application":
stopServer();
obs.removeObserver(this, "quit-application");
break;
case "profile-after-change":
startServer();
obs.addObserver(this, "quit-application", false);
break;
}
}
};

Categories