<?xml version="1.0"?>

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

<window id="StandaloneNativeMenuWindow"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        width="300"
        height="300"
        onload="onLoad();"
        title="nsIStandaloneNativeMenu 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. -->
  <popupset>
    <menupopup id="standalonenativemenu">
      <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>
    </menupopup>
  </popupset>

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

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

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

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

    var executedCommandID = "";

    function testItem(menu, location, targetID) {
      var correctCommandHandler = false;
      try {
        menu.menuWillOpen();
        menu.activateNativeMenuItemAt(location);
        correctCommandHandler = executedCommandID == targetID;
      }
      catch (e) {
        dump(e + "\n");
      }
      finally {
        executedCommandID = "";
        return correctCommandHandler;
      }
    }

    var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

    function createXULMenuPopup() {
      return document.createElementNS(XUL_NS, "menupopup");
    }

    function createXULMenu(aLabel) {
      var item = document.createElementNS(XUL_NS, "menu");
      item.setAttribute("label", aLabel);
      return item;
    }

    function createXULMenuItem(aLabel, aCommandId) {
      var item = document.createElementNS(XUL_NS, "menuitem");
      item.setAttribute("label", aLabel);
      item.setAttribute("command", aCommandId);
      return item;
    }

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

    function createStandaloneNativeMenu(menuNode) {
      try {
        let menu = Cc["@mozilla.org/widget/standalonenativemenu;1"].createInstance(Ci.nsIStandaloneNativeMenu);
        menu.init(menuNode);
        return menu;
      } catch (e) {
        ok(false, "Failed creating nsIStandaloneNativeMenu instance");
        throw e;
      }
    }

    function runDetachedMenuTests(addMenupopupBeforeCreatingSNM) {
      let menu = createXULMenu("Detached menu");
      menu.setAttribute("image", 'data:image/svg+xml,<svg%20xmlns="http://www.w3.org/2000/svg"%20width="32"%20height="32"><circle%20cx="16"%20cy="16"%20r="16"/></svg>');
      let menupopup = createXULMenuPopup();

      let popupShowingFired = false;
      let itemActivated = false;

      menupopup.addEventListener("popupshowing", function (e) {
        popupShowingFired = true;

        let menuitem = document.createElementNS(XUL_NS, "menuitem");
        menuitem.setAttribute("label", "detached menu item");
        menuitem.addEventListener("command", function (e) {
          itemActivated = true;
        })
        menupopup.appendChild(menuitem);
      })

      // It shouldn't make a difference whether the menupopup is added to the
      // menu element before or after we create the nsIStandaloneNativeMenu
      // instance with it. We test both orders by calling this function twice
      // with different values for addMenupopupBeforeCreatingSNM.

      var menuSNM = null; // the nsIStandaloneNativeMenu object for "menu"
      if (addMenupopupBeforeCreatingSNM) {
        menu.appendChild(menupopup);
        menuSNM = createStandaloneNativeMenu(menu);
      } else {
        menuSNM = createStandaloneNativeMenu(menu);
        menu.appendChild(menupopup);
      }

      try {
        ok(!popupShowingFired, "popupshowing shouldn't have fired before our call to menuWillOpen()");
        menuSNM.menuWillOpen();
        ok(popupShowingFired, "calling menuWillOpen() should have notified our popupshowing listener");

        ok(!itemActivated, "our dynamically-added menuitem shouldn't have been activated yet");
        menuSNM.activateNativeMenuItemAt("0");
        ok(itemActivated, "the new menu item should have been activated now");
      } catch (ex) {
        ok(false, "dynamic menu test failed: " + ex);
      }
    }

    function onLoad() {
      var _delayedOnLoad = function() {
        try {

        var menuNode = document.getElementById("standalonenativemenu");
        var menu = createStandaloneNativeMenu(menuNode);

        // First let's run the base menu tests.
        ok(runBaseMenuTests(menu), "base tests #1");

        // Set up some nodes that we'll use.
        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.
        menuNode.appendChild(newMenu0);
        menu.forceUpdateNativeMenuAt("1|3");
        // Run basic tests again.
        ok(runBaseMenuTests(menu), "base tests #2");

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

        // Test middle items.
        ok(testItem(menu, "1|1", "cmd_NewItem1"), "#1:" + sa);
        ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#2:" + sa);

        // Hide newMenu0.
        newMenu0.setAttribute("hidden", "true");
        ok(runBaseMenuTests(menu), "base tests #3: " + sa); // the base menu should still be unhidden
        ok(!testItem(menu, "1|0", ""), "#3:" + sna);
        ok(!testItem(menu, "1|1", ""), "#4:" + sna);
        ok(!testItem(menu, "1|2", ""), "#5:" + sna);
        ok(!testItem(menu, "1|3|0", ""), "#6:" + sna);
        ok(!testItem(menu, "1|3|1", ""), "#7:" + sna);
        ok(!testItem(menu, "1|3|2", ""), "#8:" + sna);

        // Show newMenu0.
        newMenu0.setAttribute("hidden", "false");
        menu.forceUpdateNativeMenuAt("1|3");
        ok(runBaseMenuTests(menu), "base tests #4:" + sa);
        ok(testItem(menu, "1|0", "cmd_NewItem0"), "#9:" + sa);
        ok(testItem(menu, "1|1", "cmd_NewItem1"), "#10:" + sa);
        ok(testItem(menu, "1|2", "cmd_NewItem2"), "#11:" + sa);
        ok(testItem(menu, "1|3|0", "cmd_NewItem3"), "#12:" + sa);
        ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#13:" + sa);
        ok(testItem(menu, "1|3|2", "cmd_NewItem5"), "#14:" + sa);

        // Hide items.
        newMenuItem1.setAttribute("hidden", "true");
        newMenuItem4.setAttribute("hidden", "true");
        menu.forceUpdateNativeMenuAt("1|2");
        ok(runBaseMenuTests(menu), "base tests #5:" + sa);
        ok(testItem(menu, "1|0", "cmd_NewItem0"), "#15:" + sa);
        ok(testItem(menu, "1|1", "cmd_NewItem2"), "#16:" + sa);
        ok(!testItem(menu, "1|2", ""), "#17:" + sna);
        ok(testItem(menu, "1|2|0", "cmd_NewItem3"), "#18:" + sa);
        ok(testItem(menu, "1|2|1", "cmd_NewItem5"), "#19:" + sa);
        ok(!testItem(menu, "1|2|2", ""), "#20:" + sna);

        // Show items.
        newMenuItem1.setAttribute("hidden", "false");
        newMenuItem4.setAttribute("hidden", "false");
        //forceUpdateNativeMenuAt("1|3");
        ok(runBaseMenuTests(menu), "base tests #6:" + sa);
        ok(testItem(menu, "1|0", "cmd_NewItem0"), "#21:" + sa);
        ok(testItem(menu, "1|1", "cmd_NewItem1"), "#22:" + sa);
        ok(testItem(menu, "1|2", "cmd_NewItem2"), "#23:" + sa);
        ok(testItem(menu, "1|3|0", "cmd_NewItem3"), "#24:" + sa);
        ok(testItem(menu, "1|3|1", "cmd_NewItem4"), "#25:" + sa);
        ok(testItem(menu, "1|3|2", "cmd_NewItem5"), "#26:" + sa);

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

        // Remove menu.
        menuNode.removeChild(newMenu0);
        ok(runBaseMenuTests(menu), "base tests #7:" + sa);
        ok(!testItem(menu, "1|0", ""), "#27:" + sna);
        ok(!testItem(menu, "1|1", ""), "#28:" + sna);
        ok(!testItem(menu, "1|2", ""), "#29:" + sna);
        ok(!testItem(menu, "1|3|0", ""), "#30:" + sna);
        ok(!testItem(menu, "1|3|1", ""), "#31:" + sna);
        ok(!testItem(menu, "1|3|2", ""), "#32:" + sna);
        // return state to original diagramed state
        menuNode.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");
        menuNode.removeChild(newMenu0);
        menuNode.appendChild(tmpMenu0);
        menuNode.appendChild(newMenu0);
        menu.forceUpdateNativeMenuAt("1|3");
        //todo(runBaseMenuTests(menu), "base tests #8");
        todo(testItem(menu, "1|0", "cmd_NewItem0"), "#33:" +sa);
        todo(testItem(menu, "1|1", "cmd_NewItem1"), "#34:" +sa);
        todo(testItem(menu, "1|2", "cmd_NewItem2"), "#35:" +sa);
        todo(testItem(menu, "1|3|0", "cmd_NewItem3"), "#36:" +sa);
        todo(testItem(menu, "1|3|1", "cmd_NewItem4"), "#37:" +sa);
        todo(testItem(menu, "1|3|2", "cmd_NewItem5"), "#38:" +sa);
        // return state to original diagramed state
        menuNode.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");
        menu.forceUpdateNativeMenuAt("1");
        newMenuItem1.setAttribute("hidden", "false");
        newMenuItem2.setAttribute("hidden", "false");
        newMenu1.setAttribute("hidden", "false");
        menu.forceUpdateNativeMenuAt("1");

        // Run tests where the menu nodes are not in the document's node tree.
        runDetachedMenuTests(false);
        runDetachedMenuTests(true);

        } catch (e) {
          ok(false, "Caught an exception: " + e);
        } finally {
          onTestsFinished();
        }
      }

      setTimeout(_delayedOnLoad, 1000);
    }

  ]]></script>
</window>
