Prevent XUL notificationBox from closing when button is hit

I have a problem concerning the notificationBox. I create a notification using

appendNotification( label , value , image , priority , buttons, eventCallback )

and supply a button in the buttons argument. Now, I want to prevent the notificationBox from closing when I hit the button. The XUL Documentation states that this can be done by throwing an error in the eventCallback function:

This callback can be used to prevent the notification box from closing on button click. In the callback function just throw an error. (For example: throw new Error('prevent nb close');)

This does not work for me, however, it works when I add the throw-statement to the callback function of the button itself.

  1. Is this a bug in XUL or an inconsistency with the documentation?
  2. Is there any harm done by adding it to the button's callback function?

In my opinion, this is an error in the documentation not a bug in the code. However, throwing an error in your button callback to prevent closure is not the best way to accomplish that goal.

  • Looking at the source code, there were clearly multiple discrepancies between the code and the documentation regarding how buttons work on a notification.
  • There is a specifically coded method of preventing the notification closing from within the button callback (return true from the callback).
  • Throwing an error in order to accomplish a normal functionality is usually a bad programming practice. Doing so also results in an error showing in the console every time your button is pressed. Having errors intentionally showing in the console under normal operation is bad. It also can result in your add-on not being approved in review.
  • As it was documented (not as operational), if you wanted to close when one button was pressed and not close when another was pressed, you would have to store in a global variable which button callback was last called and then choose based on that information if you wanted to prevent closure when your notificationBox callback was executed. That would be an inappropriately complex way to design operation of these notification buttons.

Given all that, I would say that intentionally throwing an error in order to prevent closure is not the "correct" way to do it. While, trowing an error to prevent closure doesn't cause any harm to the operation of the notification box, it does show the error in the console, which is bad.

The correct way to prevent the notification from closing from within the notification button callback is to return a True value from the callback.

While it is possible that the previously inaccurately documented way of doing this the way they intended to have it operate, it is not the way it actually works. Given

  • It is easier to update the documentation than it is to make changes to the code.
  • The code works in a way that is better than the documented method.
  • There were other inaccuracies in the documentation that would have prevented people from using functionality which was supposedly working (popups/menu buttons).

I have, therefore, updated the documentation to reflect what is actually in the source code and copied, with some modification, the code from this answer to an example there.

Here is some code I used to test this:

function testNotificationBoxWithButtons() {
    //Create some common variables if they do not exist.
    //  This should work from any Firefox context.
    //  Depending on the context in which the function is being run,
    //  this could be simplified.
    if (typeof window === "undefined") {
        //If there is no window defined, get the most recent.
        var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
                             .getService(Components.interfaces.nsIWindowMediator)
                             .getMostRecentWindow("navigator:browser");
    }
    if (typeof gBrowser === "undefined") {
        //If there is no gBrowser defined, get it
        var gBrowser = window.gBrowser;
    }

    function testNotificationButton1Callback(theNotification, buttonInfo, eventTarget) {
        window.alert("Button 1 pressed");

        //Prevent notification from closing:
        //throw new Error('prevent nb close');
        return true;
    };

    function testNotificationButton2Callback(theNotification, buttonInfo, eventTarget) {
        window.alert("Button 2 pressed");

        //Do not prevent notification from closing:
    };

    function testNotificationCallback(reason) {
        window.alert("Reason is: " + reason);

        //Supposedly prevent notification from closing:
        //throw new Error('prevent nb close');
        // Does not work.
    };


    let notifyBox = gBrowser.getNotificationBox();

    let buttons = [];

    let button1 = {
        isDefault: false,
        accessKey: "1",
        label: "Button 1",
        callback: testNotificationButton1Callback,
        type: "", // If a popup, then must be: "menu-button" or "menu".
        popup: null
    };

    buttons.push(button1);

    let button2 = {
        isDefault: true,
        accessKey: "2",
        label: "Button 2",
        callback: testNotificationButton2Callback,
        type: "", // If a popup, then must be: "menu-button" or "menu".
        popup: null
    };

    buttons.push(button2);

    //appendNotification( label , value , image (URL) , priority , buttons, eventCallback )
    notifyBox.appendNotification("My Notification text", "Test notification unique ID",
                                 "chrome://browser/content/aboutRobots-icon.png",
                                 notifyBox.PRIORITY_INFO_HIGH, buttons,
                                 testNotificationCallback);
}

i do not think this is possible. from the msdn documentation for a handlerroutine, there's this sentence.

the ctrl_close_event, ctrl_logoff_event, and ctrl_shutdown_event signals give the process an opportunity to clean up before termination.

i read this as saying that ctrl_close_event is advisory, and that the process is going to exit regardless. my guess is that when the system sends ctrl_close_event, it starts a timer. the process is allowed to keep running for a little bit of time, but eventually, the os will just kill the process unilaterally.

here's the handler that i registered

bool winapi consolectrlhandler(dword dwctrltype) {
   switch (dwctrltype) {
       case ctrl_c_event:
       case ctrl_close_event:
           return true; // breakpoint set here

       default:
           return false;
   }
}

here's how i registered the handler, after my call to allocconsole:

bool result = setconsolectrlhandler(consolectrlhandler, true /* add */);
assert(result);

i set a breakpoint on the line marked //breakpoint set here. then, i ran the process under the visual studio debugger. when the console window was focused, i pressed ctrl+c. my breakpoint got hit, and i was able to step through my handler and back into kernelbase.dll!ctrlroutine and so on. the process kept running when i let the process resume normal execution.

however, when i closed the console window, my handler did get called, but i was unable to trace its execution very far. i was able to single step execution a few times, but then the process simply exited. visual studio reported "the program '[10644] win32project.exe' has exited with code -1073741510 (0xc000013a)."

0xc000013a is status_control_c_exit:

c:toolserr>err.exe 0xc000013a
# for hex 0xc000013a / decimal -1073741510 :
  status_control_c_exit                                         ntstatus.h
# {application exit by ctrl+c}
# the application terminated as a result of a ctrl+c.
# 1 matches found for "0xc000013a"

use a qwidgetaction and qcheckbox for a "checkable action" which doesn't cause the menu to close.

qcheckbox *checkbox = new qcheckbox(menu);
qwidgetaction *checkableaction = new qwidgetaction(menu);
checkableaction->setdefaultwidget(checkbox);
menu->addaction(checkableaction);

in some styles, this won't appear exactly the same as a checkable action. for example, for the plastique style, the check box needs to be indented a bit.

found: just open it using opendialog, and it will be always on top.

ex:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window width="400" height="300"
    onload="opendialog('top.xul','topwindow','chrome');"
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

    <label value="main window"/topic/>

</window>

top.xul:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window width="400" height="300"
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

    <label value="on top" />

</window>

you are expecting it to behave like languages you are used to with block scope. i suggest reading through douglas crockford's book "the good parts" to get a better understanding of why everything works the way it does. if you learn all of this from the start you will have a much easier time down the road.

javascript is functionally scoped.. in this example test is scoped inside of foo.

var foo = function() {
    var test = "peanut butter";
};

in this example, you can see that the function can modify test as it's a globally scoped variable.

var test = "peanut butter";
var foo = function() {
    test = "apple sauce";
};

there are three ways to define globally scoped variables, all of which i would suggest you avoid (not entirely, that's impossible). global variables are necessary, however they can be mitigated. their lifetime is as long as the javascript is loaded. if you define multiple global variables in different .js files that are loaded by the same page, those global variables are accessible by both, making it quite easy to accidentally overwrite vars from different files.

1: place a var statement outside of any function, like you have done.

2: add it as a property to the global object.

window.foo = value;

3. use a variable without declaring it, this is an implied global. really watch out for these. the function you declared is actually an implied global itself called "clickhandler".

foo = value;

a common practice to minimize the use of global variables is known as global abatement, in which you define an application specific global variable to hold the rest of your global variables, to avoid collision with other libraries. this is done by declaring an object literal and using the 2nd method of creating global variables. only use this when you need global variables however, and try to stick to functional scope when possible.

var awesomenewapp = {};
awesomenewapp.test = "peanut butter";

when you get deeper in to the world of javascript, you'll start learning about useful things like closures and more to keep your code clean and organized.

just don't expect javascript to work like your typical classical languages and you'll find that it is a powerful language in its own right, just different.

on your journey, i suggest using the tool jslint to test your code for following conventions and errors you won't see because it isn't compiled.


Tags: Javascript Xul