Last browser window standing

Talk about add-ons and extension development.
ToolDev
Posts: 67
Joined: June 17th, 2005, 7:47 pm

Last browser window standing

Post by ToolDev »

Hi,

I need to run some cleanup code in my extensions just after the last browser window has been closed and the browser application is about to terminate. I'm currentl trying to detect this situation by checking the nsIWindowWatcher.getWindowEnumerator() collection count in the onunload() event handler of my XUL overlay (I'm assuming that the window count will be 0, when the browser is about to terminate). But something is wrong with my code, as it goes into an infinite loop.

Code: Select all

function getbrowsercount()
{
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                     .getService(Components.interfaces.nsIWindowWatcher);
  var wenum = ww.getWindowEnumerator();
  var cnt = 0;
  while (wenum.hasMoreElements)
  {
    cnt++;
    wenum.getNext();
  }
  return cnt;
}


What's wrong with my code?

I've tried to change the condition in while loop to check explicitly for hasMoreElements==true, but in this case the loop doesn't run a single time (and cnt stays at 0).

If someone know a better/simpler way how to launch a cleanup code just before the browser application is about the terminate, it would be also welcome.
ToolDev
Posts: 67
Joined: June 17th, 2005, 7:47 pm

Post by ToolDev »

OK. I solved it. The problem was, that hasMoreElements is a function, so you've to use brackets for it. Obviously, the enumeration class returned by getWindowEnumerator() is implemented in JavaScript, so, when you reference hasMoreElements without the brackets, you'll get back it's JavaScript code (as with any JS-function). As this is always not null and not zero, the while loop goes infinited.

So, the correct code is:

Code: Select all

function getbrowsercount() 
{
  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                     .getService(Components.interfaces.nsIWindowWatcher);
  var wenum = ww.getWindowEnumerator();
  var cnt = 0;
  while (wenum.hasMoreElements()) // <-- brackets here !!!!
  {
    cnt++;
    wenum.getNext();
  }
  return cnt;
}


But I'm still waiting for comments whether there is any better way to check for the about-to-terminate condition.
asqueella
Posts: 4019
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow

Post by asqueella »

There's global notification you can listen to - quit-application. It's less trivial than the code you're using and may require writing a short JS component. It's more correct though and will work in cases when the last closed window was non-browser window (in which case you current code won't run).
ToolDev
Posts: 67
Joined: June 17th, 2005, 7:47 pm

Post by ToolDev »

As I (actually, not me, but the extension) will have to uninstall every one of my hooks (to avoid leaking) when closing a window I was running in, I won't be able to receive any notifications anyway when the last window isn't going to be a browser window. Or am I wrong with that?
asqueella
Posts: 4019
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow

Post by asqueella »

I'm not sure what kind of hooks you're talking about here about.

It is possible to write a component which listens for quit-application without need for any code in overlay.

The problem with your code is that the one-time cleanup code you want to run on shutdown just won't be called if the last closed window doesn't have your code attached - that is, most probably, if it's not a browser window.
ToolDev
Posts: 67
Joined: June 17th, 2005, 7:47 pm

Post by ToolDev »

asqueella wrote:I'm not sure what kind of hooks you're talking about here about.

I'm talking about registering for the "quit-application" notification thru the observer service. I thought you were talking about this, too.

It is possible to write a component which listens for quit-application without need for any code in overlay.

How?

The problem with your code is that the one-time cleanup code you want to run on shutdown just won't be called if the last closed window doesn't have your code attached - that is, most probably, if it's not a browser window.

I know, but I still do not understand how could using monitoring for notifications help me as I'll have to deinstall notification hooks too when the browser window the extension was running in is closing.
asqueella
Posts: 4019
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow

Post by asqueella »

You don't get it. You register quit-application hook in component code, which doesn't have anything to do with browser windows.
You remove the hook (and I'm not sure if you even have to do that) in the component code, which is not related to any of the browser windows in any way, either.

Anyways, you seem to think you need to remove the quit-application hook before it runs, which doesn't make any sense.

I don't have any example code here, but the Creating XPCOM Components book should have info on that, albeit with example code probably in C++. It's trivial to convert that to JavaScript, though. If you ping me around 10pm (GMT+4), I may be able to send you the example code in JS - I think I've got it on my home machine.
asqueella
Posts: 4019
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow

Post by asqueella »

Here goes. Hope it's correct and working.

Code: Select all

function MyComponent() {
// you can do |this.wrappedJSObject = this;| for the first version of the component
// (in case you don't want to write IDL yet.)
}
MyComponent.prototype = {
  classID: Components.ID("{xxxxxxxx-be3a-2b41-7a76-12533ba32166}"), // xxx generate guid
  contractID: "@mozilla.doslash.org/test/component;1", // XXX generated contractid too
  classDescription: "Test component",

  QueryInterface: function(aIID) {
    if(!aIID.equals(CI.nsISupports) && !aIID.equals(CI.nsIObserver) && !aIID.equals(CI.nsISupportsWeakReference)) // you can claim you implement more interfaces here
      throw CR.NS_ERROR_NO_INTERFACE;
    return this;
  },

  // you can implement other interfaces here..

  // nsIObserver implementation
  observe: function(aSubject, aTopic, aData) {
    switch(aTopic) {
      case "xpcom-startup":
        dump("xpcom-startup");
        // this is run very early, right after XPCOM is initialized, but before
        // user profile information is applied. Register ourselves as an observer
        // for 'profile-after-change' and 'quit-application'.
        var obsSvc = CC["@mozilla.org/observer-service;1"].getService(CI.nsIObserverService);
        obsSvc.addObserver(this, "profile-after-change", true);
        obsSvc.addObserver(this, "quit-application", true);
        break;

      case "profile-after-change":
        // This happens after profile has been loaded and user preferences have been read.
        // startup code here
        break;

      case "quit-application":
        dump("quit");
        // shutdown code here
        break;

      default:
        throw Components.Exception("Unknown topic: " + aTopic);
    }
  }
};


// constructors for objects we want to XPCOMify
var objects = [MyComponent];

/*
 * Registration code.
 *
 */

const CI = Components.interfaces, CC = Components.classes, CR = Components.results;

const MY_OBSERVER_NAME = "My Observer";

function FactoryHolder(aObj) {
  this.CID        = aObj.prototype.classID;
  this.contractID = aObj.prototype.contractID;
  this.className  = aObj.prototype.classDescription;
  this.factory = {
    createInstance: function(aOuter, aIID) {
      if(aOuter)
        throw CR.NS_ERROR_NO_AGGREGATION;
      return (new this.constructor).QueryInterface(aIID);
    }
  };
  this.factory.constructor = aObj;
}

var gModule = {
  registerSelf: function (aComponentManager, aFileSpec, aLocation, aType)
  {
    aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
    for (var key in this._objects) {
      var obj = this._objects[key];
      aComponentManager.registerFactoryLocation(obj.CID, obj.className,
        obj.contractID, aFileSpec, aLocation, aType);
    }

    // this can be deleted if you don't need to init on startup
    var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
    catman.addCategoryEntry("xpcom-startup", MY_OBSERVER_NAME,
      MyComponent.prototype.contractID, true, true);
    catman.addCategoryEntry("xpcom-shutdown", MY_OBSERVER_NAME,
      MyComponent.prototype.contractID, true, true);
  },

  unregisterSelf: function(aCompMgr, aFileSpec, aLocation) {
    // this must be deleted if you delete the above code dealing with |catman|
    var catman = CC["@mozilla.org/categorymanager;1"].getService(CI.nsICategoryManager);
    catman.deleteCategoryEntry("xpcom-startup", MY_OBSERVER_NAME, true);
    // end of deleteable code

    aComponentManager.QueryInterface(CI.nsIComponentRegistrar);
    for (var key in this._objects) {
      var obj = this._objects[key];
      aComponentManager.unregisterFactoryLocation(obj.CID, aFileSpec);
    }
  },

  getClassObject: function(aComponentManager, aCID, aIID) {
    if (!aIID.equals(CI.nsIFactory)) throw CR.NS_ERROR_NOT_IMPLEMENTED;

    for (var key in this._objects) {
      if (aCID.equals(this._objects[key].CID))
        return this._objects[key].factory;
    }
   
    throw CR.NS_ERROR_NO_INTERFACE;
  },

  canUnload: function(aComponentManager) {
    return true;
  },

  _objects: {} //FactoryHolder
};

function NSGetModule(compMgr, fileSpec)
{
  for(var i in objects)
    gModule._objects[i] = new FactoryHolder(objects[i]);
  return gModule;
}


useful links:
http://developer.mozilla.org/en/docs/Cr ... ng_WebLock - explains notifications, in particular startup notifications, in detail
http://developer.mozilla.org/en/docs/Ho ... Javascript
http://kb.mozillazine.org/Implementing_ ... JavaScript

[edit] marked the part of registration code many people won't need as optional
[edit 3/3/2006] registering as a weak observer
Last edited by asqueella on March 3rd, 2006, 5:51 pm, edited 3 times in total.
TheOneKEA
Posts: 4864
Joined: October 16th, 2003, 5:47 am
Location: Somewhere in London, riding the Underground

Post by TheOneKEA »

Thanks for the example code asqueella - I'm going to hang on to this thread for when I write XPCOM components later on.
Proud user of teh Fox of Fire
Registered Linux User #289618
old np
Posts: 0
Joined: December 31st, 1969, 5:00 pm

Post by old np »

I'm trying to do a similar thing, but I'm not quite sure how to use this code. I think I understand how the code executes once this component is registered, but I don't understand how to register it. As far as I can read, nothing would get executed if I just included this code... so what do I have to do?
asqueella
Posts: 4019
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow

Post by asqueella »

<del>Either put the .js file with this code in Firefox/components or in your XPI file and install it. Delete compreg.dat to be safe, and it should get registered.</del>

[edit] That's confusing, let's link to MDC instead:
http://developer.mozilla.org/en/docs/Ho ... stallation
Last edited by asqueella on November 15th, 2007, 8:08 am, edited 1 time in total.
Daniel_Orner
Posts: 118
Joined: May 26th, 2005, 11:06 am

Post by Daniel_Orner »

I've had problems with quit-application... it never seemed to be called for me, even though other things (like domwindowopened) worked fine.
velcrospud
Posts: 506
Joined: January 27th, 2003, 4:00 am
Contact:

Post by velcrospud »

iirc quit-application doesn't work correctly in ff 1.0.x
but 1.5 provides quit-application-requested and quit-application-granted - which seem to work
AnonEmoose
Posts: 2031
Joined: February 6th, 2004, 11:59 am

Post by AnonEmoose »

Daniel_Orner wrote:I've had problems with quit-application... it never seemed to be called for me, even though other things (like domwindowopened) worked fine.

velcrospud wrote:iirc quit-application doesn't work correctly in ff 1.0.x
but 1.5 provides quit-application-requested and quit-application-granted - which seem to work


strangely it depends on how one closes the browser (this issue is with WIN type OS's, I believe Macs are OK). File-->Exit, Alt+F4, Ctrl+W, and 'X' ing out. 'X'ing out for some reason does not trigger observers (or perhaps it exits before the action you wish to execute when observer is notified). rue ran into this issue with sessionsaver, poke aroung his code to see how he solved/worked around it. I'm not sure if it is applicaple though if you're running it as a XPCOM component though.
old np
Posts: 0
Joined: December 31st, 1969, 5:00 pm

Post by old np »

asqueella wrote:Either put the .js file with this code in Firefox/components or in your XPI file and install it. Delete compreg.dat to be safe, and it should get registered.

Just some tips for future readers of this thread:

* When using it in your extension, create a "components" folder in the extension's root folder and put it in there.
* Don't try to use alert() as a test for the startup stuff - it doesn't work.
* Remember to generate a GUID. If you forget, you must delete compreg.dat before it will get registered.

Thank you very much, asqueella. This should be on devmo or something.
Locked