3
votes

I've followed MDN's document to create a toggle button addon.

Everything works fine except one problem:

  1. Open a second browser window (cmd+n or ctrl+n) and click on the toggle button there
  2. Click on the toggle button on the original browser window without closing the toggle button on the second window
  3. the toggle button's panel becomes blank with the following error message:

    JavaScript error: resource:///modules/WindowsPreviewPerTab.jsm, line 406: NS_ERR OR_FAILURE: Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIT askbarTabPreview.invalidate]

problem screenshot

// ./lib/main.js
var { ToggleButton } = require("sdk/ui/button/toggle");
var panels = require("sdk/panel");
var self = require("sdk/self");

var buttonIndex = 0;
var lastKnownButtonIndex = 0;

var button = ToggleButton({
    id: "button",
    label: "my button",
    icon: {
        "16": "./icon-16.png"
    },
    onClick: handleChange,
});

var panel = panels.Panel({
    contentURL: self.data.url("menu.html"),
    onHide: handleHide
});

function handleChange(state) {
    if (state.checked) {
        panel.show({
            position: button
        });
    }
}

function handleHide() {
  button.state('window', {checked: false});
}

function assignButtonIndex() {
    return (buttonIndex++).toString();
}

The complete addon is here: https://goo.gl/9N3jle

To reproduce: Extract the zip file and $ cd testButton; cfx run and follow the above steps.

I really hope someone can help me with this. Thank you in advance!

2
I think you found a bug. We should make a simpele reproducible case and upload to bugzilla. - Noitidart
It may take a long time for Mozilla to fix this. Is there any workaround to the bug for the moment? - James_
Yes there is. Did you file this bug yet? If you didn't I'll do it for you. But the work around is making and handling panels yourself (the non-sdk way). Right now what I'm sure is hitting you, is the magic that happens by SDK, I don't really know all that magic so can't go about debugging this, I would have to dive into the SDK source and DOM Inspeting to see whats going on. For me handling this non-SDK way is much easier, would you like to go about it that way? It will remove all the comforts you may be used to of the SDK though. When you reply please use @ otherwise i get no notificaiton. - Noitidart
Thanks @Noitidart. I've filed another bug (yet to do so on this) to Mozilla stackoverflow.com/questions/29972371/…. But never heard back from them. I would appreciate if there is any workaround to the problem. - James_

2 Answers

1
votes

It's a bug; you're not doing anything wrong. It's a racing condition between the windows' focus events, and the panel's event, that prevent somehow the panel's hidden event to be emitted properly.

You can try to mitigate with a workaround the issue until is properly fixed. I added some explanation in the bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1174425#c2 but in short, you can try to add a setTimeout to delay a bit when the panel is shown, in order to avoid the racing condition with the window's focus. Something like:

const { setTimeout } = require("sdk/timers");

/* ... your code here ... */    

function handleChange(state) {
  if (state.checked) {
    setTimeout(() => panel.show({ position: button }), 100);
  }
}
0
votes

I am currently using a workaround where I dynamically create a new Panel every time the user presses the toolbar button.

It is faster than the 100ms workaround and also handles a scenario where the user outright closes one of the browser windows while the panel is open. (The 100ms workaround fails in this case and a blank panel is still displayed.)

It works like this:

let myPanel = null;

const toolbarButton = ToggleButton({
    ...,
    onChange: function (state) {
        if (state.checked) {
            createPanel();
        }
    }
});

function createPanel(){   
    // Prevent memory leaks
    if(myPanel){
        myPanel.destroy();
    }

    // Create a new instance of the panel
    myPanel = Panel({
        ...,
        onHide: function(){
            // Destroy the panel instead of just hiding it.
            myPanel.destroy();
        }
    });

    // Display the panel immediately
    myPanel.show();
}