<?xml version="1.0"?>

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is Native Menus Test code
   -
   - The Initial Developer of the Original Code is
   - Mozilla Corporation.
   - Portions created by the Initial Developer are Copyright (C) 2008
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -   Josh Aas <josh@mozilla.com>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the GPL or the LGPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->

<?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) {
      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
      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) {
      netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
      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.

        // 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>
