<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

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

<window id="NativeMenuWindow"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        width="300"
        height="300"
        onload="onLoad();"
        title="Native Menu Test">

  <command id="cmd_FooItem0" oncommand="executedCommandID = 'cmd_FooItem0';"/>
  <command id="cmd_FooItem1" oncommand="executedCommandID = 'cmd_FooItem1';"/>
  <command id="cmd_BarItem0" oncommand="executedCommandID = 'cmd_BarItem0';"/>
  <command id="cmd_BarItem1" oncommand="executedCommandID = 'cmd_BarItem1';"/>
  <command id="cmd_NewItem0" oncommand="executedCommandID = 'cmd_NewItem0';"/>
  <command id="cmd_NewItem1" oncommand="executedCommandID = 'cmd_NewItem1';"/>
  <command id="cmd_NewItem2" oncommand="executedCommandID = 'cmd_NewItem2';"/>
  <command id="cmd_NewItem3" oncommand="executedCommandID = 'cmd_NewItem3';"/>
  <command id="cmd_NewItem4" oncommand="executedCommandID = 'cmd_NewItem4';"/>
  <command id="cmd_NewItem5" oncommand="executedCommandID = 'cmd_NewItem5';"/>

  <!-- We do not modify any menus or menu items defined here in testing. These
       serve as a baseline structure for us to test after other modifications.
       We add children to the menubar defined here and test by modifying those
       children. -->
  <menubar id="nativemenubar">
    <menu id="foo" label="Foo">
      <menupopup>
        <menuitem label="FooItem0" command="cmd_FooItem0"/>
        <menuitem label="FooItem1" command="cmd_FooItem1"/>
        <menuseparator/>
        <menu label="Bar">
          <menupopup>
            <menuitem label="BarItem0" command="cmd_BarItem0"/>
            <menuitem label="BarItem1" command="cmd_BarItem1"/>
          </menupopup>
        </menu>
      </menupopup>
    </menu>
  </menubar>

  <script type="application/javascript"><![CDATA[

    function ok(condition, message) {
      window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
    }

    function onTestsFinished() {
      window.close();
      window.opener.wrappedJSObject.SimpleTest.finish();
    }

    // Force a menu to update itself. All of the menus parents will be updated
    // as well. An empty string will force a complete menu system reload.
    function forceUpdateNativeMenuAt(location) {
      var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                                          getInterface(Components.interfaces.nsIDOMWindowUtils);
      try {
        utils.forceUpdateNativeMenuAt(location);
      }
      catch (e) {
        dump(e + "\n");
      }
    }

    var executedCommandID = "";

    function testItem(location, targetID) {
      var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
                                          getInterface(Components.interfaces.nsIDOMWindowUtils);
      var correctCommandHandler = false;
      try {
        utils.activateNativeMenuItemAt(location);
        correctCommandHandler = executedCommandID == targetID;
      }
      catch (e) {
        dump(e + "\n");
      }
      finally {
        executedCommandID = "";
        return correctCommandHandler;
      }
    }

    function createXULMenuPopup() {
      const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      var item = document.createElementNS(XUL_NS, "menupopup");
      return item;
    }

    function createXULMenu(aLabel) {
      const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      var item = document.createElementNS(XUL_NS, "menu");
      item.setAttribute("label", aLabel);
      return item;
    }

    function createXULMenuItem(aLabel, aCommandId) {
      const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      var item = document.createElementNS(XUL_NS, "menuitem");
      item.setAttribute("label", aLabel);
      item.setAttribute("command", aCommandId);
      return item;
    }

    function runBaseMenuTests() {
      forceUpdateNativeMenuAt("0|3");
      return testItem("0|0", "cmd_FooItem0") &&
             testItem("0|1", "cmd_FooItem1") &&
             testItem("0|3|0", "cmd_BarItem0") &&
             testItem("0|3|1", "cmd_BarItem1");
    }

    function onLoad() {
      var _delayedOnLoad = function() {
        // First let's run the base menu tests.
        ok(runBaseMenuTests());

        // Set up some nodes that we'll use.
        var menubarNode = document.getElementById("nativemenubar");
        var newMenu0 = createXULMenu("NewMenu0");
        var newMenu1 = createXULMenu("NewMenu1");
        var newMenuPopup0 = createXULMenuPopup();
        var newMenuPopup1 = createXULMenuPopup();
        var newMenuItem0 = createXULMenuItem("NewMenuItem0", "cmd_NewItem0");
        var newMenuItem1 = createXULMenuItem("NewMenuItem1", "cmd_NewItem1");
        var newMenuItem2 = createXULMenuItem("NewMenuItem2", "cmd_NewItem2");
        var newMenuItem3 = createXULMenuItem("NewMenuItem3", "cmd_NewItem3");
        var newMenuItem4 = createXULMenuItem("NewMenuItem4", "cmd_NewItem4");
        var newMenuItem5 = createXULMenuItem("NewMenuItem5", "cmd_NewItem5");

        // Create another submenu with hierarchy via DOM manipulation.
        // ******************
        // * Foo * NewMenu0 * <- Menu bar 
        // ******************
        //       ****************
        //       * NewMenuItem0 * <- NewMenu0 submenu
        //       ****************
        //       * NewMenuItem1 *
        //       ****************
        //       * NewMenuItem2 *
        //       *******************************
        //       * NewMenu1   > * NewMenuItem3 * <- NewMenu1 submenu
        //       *******************************
        //                      * NewMenuItem4 *
        //                      ****************
        //                      * NewMenuItem5 *
        //                      ****************
        newMenu0.appendChild(newMenuPopup0);
        newMenuPopup0.appendChild(newMenuItem0);
        newMenuPopup0.appendChild(newMenuItem1);
        newMenuPopup0.appendChild(newMenuItem2);
        newMenuPopup0.appendChild(newMenu1);
        newMenu1.appendChild(newMenuPopup1);
        newMenuPopup1.appendChild(newMenuItem3);
        newMenuPopup1.appendChild(newMenuItem4);
        newMenuPopup1.appendChild(newMenuItem5);
        //XXX - we have to append the menu to the top-level of the menu bar
        // only after constructing it. If we append before construction, it is
        // invalid because it has no children and we don't validate it if we add
        // children later.
        menubarNode.appendChild(newMenu0);
        forceUpdateNativeMenuAt("1|3");
        // Run basic tests again.
        ok(runBaseMenuTests());

        // Error strings.
        var sa = "Command handler(s) should have activated";
        var sna = "Command handler(s) should not have activated";

        // Test middle items.
        ok(testItem("1|1", "cmd_NewItem1"), sa);
        ok(testItem("1|3|1", "cmd_NewItem4"), sa);

        // Hide newMenu0.
        newMenu0.setAttribute("hidden", "true");
        ok(runBaseMenuTests(), sa); // the base menu should still be unhidden
        ok(!testItem("1|0", ""), sna);
        ok(!testItem("1|1", ""), sna);
        ok(!testItem("1|2", ""), sna);
        ok(!testItem("1|3|0", ""), sna);
        ok(!testItem("1|3|1", ""), sna);
        ok(!testItem("1|3|2", ""), sna);

        // Show newMenu0.
        newMenu0.setAttribute("hidden", "false");
        forceUpdateNativeMenuAt("1|3");
        ok(runBaseMenuTests(), sa);
        ok(testItem("1|0", "cmd_NewItem0"), sa);
        ok(testItem("1|1", "cmd_NewItem1"), sa);
        ok(testItem("1|2", "cmd_NewItem2"), sa);
        ok(testItem("1|3|0", "cmd_NewItem3"), sa);
        ok(testItem("1|3|1", "cmd_NewItem4"), sa);
        ok(testItem("1|3|2", "cmd_NewItem5"), sa);

        // Hide items.
        newMenuItem1.setAttribute("hidden", "true");
        newMenuItem4.setAttribute("hidden", "true");
        forceUpdateNativeMenuAt("1|2");
        ok(runBaseMenuTests(), sa);
        ok(testItem("1|0", "cmd_NewItem0"), sa);
        ok(testItem("1|1", "cmd_NewItem2"), sa);
        ok(!testItem("1|2", ""), sna);
        ok(testItem("1|2|0", "cmd_NewItem3"), sa);
        ok(testItem("1|2|1", "cmd_NewItem5"), sa);
        ok(!testItem("1|2|2", ""), sna);

        // Show items.
        newMenuItem1.setAttribute("hidden", "false");
        newMenuItem4.setAttribute("hidden", "false");
        forceUpdateNativeMenuAt("1|3");
        ok(runBaseMenuTests(), sa);
        ok(testItem("1|0", "cmd_NewItem0"), sa);
        ok(testItem("1|1", "cmd_NewItem1"), sa);
        ok(testItem("1|2", "cmd_NewItem2"), sa);
        ok(testItem("1|3|0", "cmd_NewItem3"), sa);
        ok(testItem("1|3|1", "cmd_NewItem4"), sa);
        ok(testItem("1|3|2", "cmd_NewItem5"), sa);

        // At this point in the tests the state of the menus has been returned
        // to the originally diagramed state.

        // Test command disabling
        var cmd_NewItem0 = document.getElementById("cmd_NewItem0");
        ok(testItem("1|0", "cmd_NewItem0"), sa);
        cmd_NewItem0.setAttribute("disabled", "true");
        ok(!testItem("1|0", "cmd_NewItem0"), sna);
        cmd_NewItem0.removeAttribute("disabled");
        ok(testItem("1|0", "cmd_NewItem0"), sa);

        // Remove menu.
        menubarNode.removeChild(newMenu0);
        ok(runBaseMenuTests(), sa);
        ok(!testItem("1|0", ""), sna);
        ok(!testItem("1|1", ""), sna);
        ok(!testItem("1|2", ""), sna);
        ok(!testItem("1|3|0", ""), sna);
        ok(!testItem("1|3|1", ""), sna);
        ok(!testItem("1|3|2", ""), sna);
        // return state to original diagramed state
        menubarNode.appendChild(newMenu0);

        // Test for bug 447042, make sure that adding a menu node with no children
        // to the menu bar and then adding another menu node with children works.
        // Menus with no children don't get their native menu items shown and that
        // caused internal arrays to get out of sync and an append crashed.
        var tmpMenu0 = createXULMenu("tmpMenu0");
        menubarNode.removeChild(newMenu0);
        menubarNode.appendChild(tmpMenu0);
        menubarNode.appendChild(newMenu0);
        forceUpdateNativeMenuAt("1|3");
        ok(runBaseMenuTests());
        ok(testItem("1|0", "cmd_NewItem0"), sa);
        ok(testItem("1|1", "cmd_NewItem1"), sa);
        ok(testItem("1|2", "cmd_NewItem2"), sa);
        ok(testItem("1|3|0", "cmd_NewItem3"), sa);
        ok(testItem("1|3|1", "cmd_NewItem4"), sa);
        ok(testItem("1|3|2", "cmd_NewItem5"), sa);
        // return state to original diagramed state
        menubarNode.removeChild(tmpMenu0);
        delete tmpMenu0;

        // This test is basically a crash test for bug 433858.
        newMenuItem1.setAttribute("hidden", "true");
        newMenuItem2.setAttribute("hidden", "true");
        newMenu1.setAttribute("hidden", "true");
        forceUpdateNativeMenuAt("1");
        newMenuItem1.setAttribute("hidden", "false");
        newMenuItem2.setAttribute("hidden", "false");
        newMenu1.setAttribute("hidden", "false");
        forceUpdateNativeMenuAt("1");

        onTestsFinished();
      }

      setTimeout(_delayedOnLoad, 1000);
    }

  ]]></script>
</window>