firefox-addonxulgeckoxulrunner

Why destructor of an extended binding is not called when extending binding is removed?


Creating a XulRunner application for Windows I found that if binding B extends binding A. And B is being removed, then only destructor of B is called, not followed by a call of A's destructor.

Is there something wrong with my code, or this is a XulRunner bug (I haven't find matching bug in bugzilla)?

log after binding B is removed

Here is an example that I tested on XulRunner 23 and 35:

main.xul:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="main" title="My App" width="500" height="300" sizemode="normal"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:html="http://www.w3.org/1999/xhtml">
  <script><![CDATA[
    function print(aString) {
      document.getElementById("log_msg").value += aString + "\n";
    }
    function removeElementById(aId) {
      document.getElementById(aId).remove();
    };
    function callF(aId) {
      document.getElementById(aId).f();
    }
  ]]></script>
  <vbox flex="1">
    <label onclick="removeElementById('A')">remove A</label>
    <label onclick="removeElementById('B')">remove B</label>
    <label onclick="callF('A')">Call A.f()</label>
    <label onclick="callF('B')">Call B.f()</label>
    <!--<binding-a id="A" style="-moz-binding: url('binding.xml#binding-a')"/>-->
    <binding-b id="B" style="-moz-binding: url('binding.xml#binding-b')"/>
    <textbox id="log_msg" label="Text" placeholder="placeholder" multiline="true" rows="6"/>
  </vbox>
</window>

binding.xml:

<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl"
     xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  <binding id="binding-a">
    <content>
      <xul:label anonid="label" value="Binding A"/>
    </content>
    <implementation>
      <constructor><![CDATA[
        var label = document.getAnonymousElementByAttribute(this, "anonid", "label");
        label.value = label.value + " A.constructor";
        window.print("A.constructor");
      ]]></constructor>
      <destructor><![CDATA[
        window.print("A.destructor");
      ]]></destructor>
      <method name="f">
        <body><![CDATA[
          window.print("A.f");
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="binding-b" extends="#binding-a">
    <content>
      <xul:label anonid="label" value="Binding B"/>
    </content>
    <implementation>
      <constructor><![CDATA[
        window.print("B.constructor");
      ]]></constructor>
      <destructor><![CDATA[
        window.print("B.destructor");
      ]]></destructor>
    </implementation>
  </binding>
</bindings>

Solution

  • here is the solution for your problem:

    bindings.xml:

    <?xml version="1.0"?>
    <bindings xmlns="http://www.mozilla.org/xbl"
         xmlns:xbl="http://www.mozilla.org/xbl" 
         xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
      <binding id="binding-a">
        <content>
          <xul:label anonid="label" value="Binding A"/>
        </content>
        <implementation>
          <constructor><![CDATA[
            var label = document.getAnonymousElementByAttribute(this, "anonid", "label");
            label.value = label.value + " A.constructor";
            window.print("A.constructor");
          ]]></constructor>
          <destructor><![CDATA[
            this.destruct();
          ]]></destructor>
          <method name="f">
            <body><![CDATA[
              window.print("A.f");
            ]]></body>
          </method>
          <method name="destruct">
            <body><![CDATA[
              window.print("A.destructor");
            ]]></body>
          </method>
        </implementation>
      </binding>
    
      <binding id="binding-b" extends="#binding-a">
        <content>
          <xul:label anonid="label" value="Binding B"/>
        </content>
        <implementation>
          <constructor><![CDATA[
            window.print("B.constructor");
          ]]></constructor>
          <destructor><![CDATA[
              this.destruct();
          ]]></destructor>
          <method name="destruct">
            <body><![CDATA[
            this.__proto__.__proto__.destruct.call(this);
              window.print("B.destructor");
            ]]></body>
          </method>
        </implementation>
      </binding>
    </bindings> 
    

    bindings.css:

    @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
    
    binding-a { -moz-binding: url("bindings.xml#binding-a"); }
    binding-b { -moz-binding: url("bindings.xml#binding-b"); }
    

    main.xul:

    <?xml version="1.0"?>
    <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
    <?xml-stylesheet href="bindings.css" type="text/css"?>
    <window id="main" title="My App" width="500" height="300" sizemode="normal"
            xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
            xmlns:html="http://www.w3.org/1999/xhtml">
      <script><![CDATA[
        function print(aString) {
          document.getElementById("log_msg").value += aString + "\n";
        }
        function removeElementById(aId) {
          document.getElementById(aId).remove();
        };
        function callF(aId) {
          document.getElementById(aId).f();
        }
      ]]></script>
      <vbox flex="1">
        <label onclick="removeElementById('A')">remove A</label>
        <label onclick="removeElementById('B')">remove B</label>
        <label onclick="callF('A')">Call A.f()</label>
        <label onclick="callF('B')">Call B.f()</label>
        <binding-a id="A"/>
        <binding-b id="B"/>
        <textbox id="log_msg" label="Text" placeholder="placeholder" multiline="true" rows="6"/>
      </vbox>
    </window>
    

    I know that it is a hack in fact but it works.