browser/base/content/test/test_contextmenu.html
author David Anderson <danderson@mozilla.com>
Mon, 17 Oct 2011 11:52:12 -0700
changeset 108863 f93960a93ad97a56d308bd9ce25d97cbc175d524
parent 108825 8cfeba5239a9e4f20c462d6fb20421b4e4e7c735
parent 79477 1906abbc9caf7a224cfb6db062626d269e3a40da
child 108881 8da6b16bdd33a7c882d9aa04c05277c911c74fae
permissions -rw-r--r--
Merge from mozilla-central.

<!DOCTYPE HTML>
<html>
<head>
  <title>Tests for browser context menu</title>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Browser context menu tests.
<p id="display"></p>

<div id="content">
</div>

<pre id="test">
<script class="testbody" type="text/javascript">

/** Test for Login Manager: multiple login autocomplete. **/

netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

const Cc = Components.classes;
const Ci = Components.interfaces;

function openContextMenuFor(element, shiftkey, shouldWaitForFocus) {
    // Context menu should be closed before we open it again.
    is(contextMenu.state, "closed", "checking if popup is closed");

    //Some elements need time to focus and spellcheck before any tests are
    //run on them.
    if(shouldWaitForFocus)
    {
      if (lastElement)
        lastElement.blur();
      element.focus();
      
      SimpleTest.executeSoon(function() {
        lastElement = element;
        var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
        synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
      });
    }
    else
    {
      if (lastElement)
          lastElement.blur();
      element.focus();
      lastElement = element;
      var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
      synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
    }
}

function closeContextMenu() {
    contextMenu.hidePopup();
}

function executeCopyCommand(command, expectedValue)
{
  // Just execute the command directly rather than simulating a context menu
  // press to avoid having to deal with its asynchronous nature
  subwindow.controllers.getControllerForCommand(command).doCommand(command);

  // The easiest way to check the clipboard is to paste the contents into a
  // textbox
  input.focus();
  input.value = "";
  input.controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
  is(input.value, expectedValue, "paste for command " + command);
}

function invokeItemAction(generatedItemId)
{
  var item = contextMenu.getElementsByAttribute("generateditemid",
                                                generatedItemId)[0];
  ok(item, "Got generated XUL menu item");
  item.doCommand();
  is(pagemenu.hasAttribute("hopeless"), false, "attribute got removed");
}

function getVisibleMenuItems(aMenu, aData) {
    var items = [];
    var accessKeys = {};
    for (var i = 0; i < aMenu.childNodes.length; i++) {
        var item = aMenu.childNodes[i];
        if (item.hidden)
            continue;

        var key = item.accessKey;
        if (key)
            key = key.toLowerCase();

        var isGenerated = item.hasAttribute("generateditemid");

        if (item.nodeName == "menuitem") {
            var isSpellSuggestion = item.className == "spell-suggestion";
            if (isSpellSuggestion) {
              is(item.id, "", "child menuitem #" + i + " is a spelling suggestion");
            } else if (isGenerated) {
              is(item.id, "", "child menuitem #" + i + " is a generated item");
            } else {
              ok(item.id, "child menuitem #" + i + " has an ID");
            }
            var label = item.getAttribute("label");
            ok(label.length, "menuitem " + item.id + " has a label");
            if (isSpellSuggestion) {
              is(key, "", "Spell suggestions shouldn't have an access key");
              items.push("*" + label);
            } else if (isGenerated) {
              items.push("+" + label);
            } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
                       item.id != "spell-no-suggestions") {
              ok(key, "menuitem " + item.id + " has an access key");
              if (accessKeys[key])
                  ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
              else
                  accessKeys[key] = item.id;
            }
            if (!isSpellSuggestion && !isGenerated) {
              items.push(item.id);
            }
            if (isGenerated) {
              var p = {};
              p.type = item.getAttribute("type");
              p.icon = item.getAttribute("image");
              p.checked = item.hasAttribute("checked");
              p.disabled = item.hasAttribute("disabled");
              items.push(p);
            } else {
              items.push(!item.disabled);
            }
        } else if (item.nodeName == "menuseparator") {
            ok(true, "--- seperator id is " + item.id);
            items.push("---");
            items.push(null);
        } else if (item.nodeName == "menu") {
            if (isGenerated) {
                item.id = "generated-submenu-" + aData.generatedSubmenuId++;
            }
            ok(item.id, "child menu #" + i + " has an ID");
            if (!isGenerated) {
                ok(key, "menu has an access key");
                if (accessKeys[key])
                    ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
                else
                    accessKeys[key] = item.id;
            }
            items.push(item.id);
            items.push(!item.disabled);
            // Add a dummy item to that the indexes in checkMenu are the same
            // for expectedItems and actualItems.
            items.push([]);
            items.push(null);
        } else {
            ok(false, "child #" + i + " of menu ID " + aMenu.id +
                      " has an unknown type (" + item.nodeName + ")");
        }
    }
    return items;
}

function checkContextMenu(expectedItems) {
    is(contextMenu.state, "open", "checking if popup is open");
    var data = { generatedSubmenuId: 1 };
    checkMenu(contextMenu, expectedItems, data);
}

/*
 * checkMenu - checks to see if the specified <menupopup> contains the
 * expected items and state.
 * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
 * the item is enabled or not (or null to ignore it). Submenus can be checked
 * by providing a nested array entry after the expected <menu> ID.
 * For example: ["blah", true,              // item enabled
 *               "submenu", null,           // submenu
 *                   ["sub1", true,         // submenu contents
 *                    "sub2", false], null, // submenu contents
 *               "lol", false]              // item disabled
 * 
 */
function checkMenu(menu, expectedItems, data) {
    var actualItems = getVisibleMenuItems(menu, data);
    //ok(false, "Items are: " + actualItems);
    for (var i = 0; i < expectedItems.length; i+=2) {
        var actualItem   = actualItems[i];
        var actualEnabled = actualItems[i + 1];
        var expectedItem = expectedItems[i];
        var expectedEnabled = expectedItems[i + 1];
        if (expectedItem instanceof Array) {
            ok(true, "Checking submenu...");
            var menuID = expectedItems[i - 2]; // The last item was the menu ID.
            var submenu = menu.getElementsByAttribute("id", menuID)[0];
            ok(submenu && submenu.nodeName == "menu", "got expected submenu element");
            checkMenu(submenu.menupopup, expectedItem, data);
        } else {
            is(actualItem, expectedItem,
               "checking item #" + i/2 + " (" + expectedItem + ") name");

            if (typeof expectedEnabled == "object" && expectedEnabled != null ||
                typeof actualEnabled == "object" && actualEnabled != null) {

                ok(!(actualEnabled == null), "actualEnabled is not null");
                ok(!(expectedEnabled == null), "expectedEnabled is not null");
                is(typeof actualEnabled, typeof expectedEnabled, "checking types");

                if (typeof actualEnabled != typeof expectedEnabled ||
                    actualEnabled == null || expectedEnabled == null)
                  continue;

                is(actualEnabled.type, expectedEnabled.type,
                   "checking item #" + i/2 + " (" + expectedItem + ") type attr value");
                var icon = actualEnabled.icon;
                if (icon) {
                  var tmp = "";
                  var j = icon.length - 1;
                  while (j && icon[j] != "/") {
                    tmp = icon[j--] + tmp;
                  }
                  icon = tmp;
                }
                is(icon, expectedEnabled.icon,
                   "checking item #" + i/2 + " (" + expectedItem + ") icon attr value");
                is(actualEnabled.checked, expectedEnabled.checked,
                   "checking item #" + i/2 + " (" + expectedItem + ") has checked attr");
                is(actualEnabled.disabled, expectedEnabled.disabled,
                   "checking item #" + i/2 + " (" + expectedItem + ") has disabled attr");
            } else if (expectedEnabled != null)
                is(actualEnabled, expectedEnabled,
                   "checking item #" + i/2 + " (" + expectedItem + ") enabled state");
        }
    }
    // Could find unexpected extra items at the end...
    is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
}

/*
 * runTest
 *
 * Called by a popupshowing event handler. Each test checks for expected menu
 * contents, closes the popup, and finally triggers the popup on a new element
 * (thus kicking off another cycle).
 *
 */
function runTest(testNum) {
  // Seems we need to enable this again, or sendKeyEvent() complaints.
  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
  ok(true, "Starting test #" + testNum);

  switch (testNum) {
    case 1:
        // Invoke context menu for next test.
        openContextMenuFor(text);
        break;

    case 2:
        // Context menu for plain text
        checkContextMenu(["context-back",         false,
                          "context-forward",      false,
                          "context-reload",       true,
                          "context-stop",         false,
                          "---",                  null,
                          "context-bookmarkpage", true,
                          "context-savepage",     true,
                          "context-sendpage",     true,
                          "---",                  null,
                          "context-viewbgimage",  false,
                          "context-selectall",    true,
                          "---",                  null,
                          "context-viewsource",   true,
                          "context-viewinfo",     true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(link); // Invoke context menu for next test.
        break;

    case 3:
        // Context menu for text link
        checkContextMenu(["context-openlinkintab", true,
                          "context-openlink",      true,
                          "---",                   null,
                          "context-bookmarklink",  true,
                          "context-savelink",      true,
                          "context-sendlink",      true,
                          "context-copylink",      true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(mailto); // Invoke context menu for next test.
        break;

    case 4:
        // Context menu for text mailto-link
        checkContextMenu(["context-copyemail", true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(input); // Invoke context menu for next test.
        break;

    case 5:
        // Context menu for text input field
        checkContextMenu(["context-undo",        false,
                          "---",                 null,
                          "context-cut",         false,
                          "context-copy",        false,
                          "context-paste",       null, // ignore clipboard state
                          "context-delete",      false,
                          "---",                 null,
                          "context-selectall",   false,
                          "---",                 null,
                          "spell-check-enabled", true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(img); // Invoke context menu for next test.
        break;

    case 6:
        // Context menu for an image
        checkContextMenu(["context-viewimage",            true,
                          "context-copyimage-contents",   true,
                          "context-copyimage",            true,
                          "---",                          null,
                          "context-saveimage",            true,
                          "context-sendimage",            true,
                          "context-setDesktopBackground", true,
                          "context-viewimageinfo",        true,
                          "---",                          null,
                          "context-inspect",              true]);
        closeContextMenu();
        openContextMenuFor(canvas); // Invoke context menu for next test.
        break;

    case 7:
        // Context menu for a canvas
        checkContextMenu(["context-viewimage",    true,
                          "context-saveimage",    true,
                          "context-bookmarkpage", true,
                          "context-selectall",    true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(video_ok); // Invoke context menu for next test.
        break;

    case 8:
        // Context menu for a video (with a VALID media source)
        checkContextMenu(["context-media-play",         true,
                          "context-media-mute",         true,
                          "context-media-hidecontrols", true,
                          "context-video-showstats",    true,
                          "context-video-fullscreen",   true,
                          "---",                        null,
                          "context-viewvideo",          true,
                          "context-copyvideourl",       true,
                          "---",                        null,
                          "context-savevideo",          true,
                          "context-video-saveimage",    true,
                          "context-sendvideo",          true,
                          "---",                        null,
                          "context-inspect",            true]);
        closeContextMenu();
        openContextMenuFor(video_bad); // Invoke context menu for next test.
        break;

    case 9:
        // Context menu for a video (with an INVALID media source)
        checkContextMenu(["context-media-play",         false,
                          "context-media-mute",         false,
                          "context-media-hidecontrols", false,
                          "context-video-showstats",    false,
                          "context-video-fullscreen",   false,
                          "---",                        null,
                          "context-viewvideo",          true,
                          "context-copyvideourl",       true,
                          "---",                        null,
                          "context-savevideo",          true,
                          "context-video-saveimage",    false,
                          "context-sendvideo",          true,
                          "---",                        null,
                          "context-inspect",            true]);
        closeContextMenu();
        openContextMenuFor(video_bad2); // Invoke context menu for next test.
        break;

    case 10:
        // Context menu for a video (with an INVALID media source)
        checkContextMenu(["context-media-play",         false,
                          "context-media-mute",         false,
                          "context-media-hidecontrols", false,
                          "context-video-showstats",    false,
                          "context-video-fullscreen",   false,
                          "---",                        null,
                          "context-viewvideo",          false,
                          "context-copyvideourl",       false,
                          "---",                        null,
                          "context-savevideo",          false,
                          "context-video-saveimage",    false,
                          "context-sendvideo",          false,
                          "---",                        null,
                          "context-inspect",            true]);
        closeContextMenu();
        openContextMenuFor(iframe); // Invoke context menu for next test.
        break;

    case 11:
        // Context menu for an iframe
        checkContextMenu(["context-back",         false,
                          "context-forward",      false,
                          "context-reload",       true,
                          "context-stop",         false,
                          "---",                  null,
                          "context-bookmarkpage", true,
                          "context-savepage",     true,
                          "context-sendpage",     true,
                          "---",                  null,
                          "context-viewbgimage",  false,
                          "context-selectall",    true,
                          "frame",                null,
                              ["context-showonlythisframe", true,
                               "context-openframeintab",    true,
                               "context-openframe",         true,
                               "---",                       null,
                               "context-reloadframe",       true,
                               "---",                       null,
                               "context-bookmarkframe",     true,
                               "context-saveframe",         true,
                               "---",                       null,
                               "context-printframe",        true,
                               "---",                       null,
                               "context-viewframesource",   true,
                               "context-viewframeinfo",     true], null,
                          "---",                  null,
                          "context-viewsource",   true,
                          "context-viewinfo",     true,
                          "---",                  null,
                          "context-inspect",      true]);
        closeContextMenu();
        openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck.
        break;

    case 12:
        // Context menu for textarea
        checkContextMenu(["*chubbiness",         true, // spelling suggestion
                          "spell-add-to-dictionary", true,
                          "---",                 null,
                          "context-undo",        false,
                          "---",                 null,
                          "context-cut",         false,
                          "context-copy",        false,
                          "context-paste",       null, // ignore clipboard state
                          "context-delete",      false,
                          "---",                 null,
                          "context-selectall",   true,
                          "---",                 null,
                          "spell-check-enabled", true,
                          "spell-dictionaries",  true,
                              ["spell-check-dictionary-en-US", true,
                               "---",                          null,
                               "spell-add-dictionaries",       true], null,
                          "---",                  null,
                          "context-inspect",      true]);

        closeContextMenu();
        openContextMenuFor(contenteditable); // Invoke context menu for next test.
        break;

    case 13:
        // Context menu for contenteditable
        checkContextMenu(["spell-no-suggestions", false,
                          "spell-add-to-dictionary", true,
                          "---",                 null,
                          "context-undo",        false,
                          "---",                 null,
                          "context-cut",         false,
                          "context-copy",        false,
                          "context-paste",       null, // ignore clipboard state
                          "context-delete",      false,
                          "---",                 null,
                          "context-selectall",   true,
                          "---",                 null,
                          "spell-check-enabled", true,
                          "spell-dictionaries",  true,
                              ["spell-check-dictionary-en-US", true,
                               "---",                          null,
                               "spell-add-dictionaries",       true], null,
                          "---",                 null,
                          "context-inspect",     true]);

        closeContextMenu();
        openContextMenuFor(inputspell); // Invoke context menu for next test.
        break;

    case 14:
        // Context menu for spell-check input
        checkContextMenu(["*prodigality",        true, // spelling suggestion
                          "spell-add-to-dictionary", true,
                          "---",                 null,
                          "context-undo",        false,
                          "---",                 null,
                          "context-cut",         false,
                          "context-copy",        false,
                          "context-paste",       null, // ignore clipboard state
                          "context-delete",      false,
                          "---",                 null,
                          "context-selectall",   true,
                          "---",                 null,
                          "spell-check-enabled", true,
                          "spell-dictionaries",  true,
                              ["spell-check-dictionary-en-US", true,
                               "---",                          null,
                               "spell-add-dictionaries",       true], null,
                          "---",                 null,
                          "context-inspect",     true]);

        closeContextMenu();
        openContextMenuFor(link); // Invoke context menu for next test.
        break;

    case 15:
        executeCopyCommand("cmd_copyLink", "http://mozilla.com/");
        closeContextMenu();
        openContextMenuFor(pagemenu); // Invoke context menu for next test.
        break;

    case 16:
        // Context menu for element with assigned content context menu
        checkContextMenu(["+Plain item",          {type: "", icon: "", checked: false, disabled: false},
                          "+Disabled item",       {type: "", icon: "", checked: false, disabled: true},
                          "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
                          "---",                  null,
                          "+Checkbox",            {type: "checkbox", icon: "", checked: true, disabled: false},
                          "---",                  null,
                          "+Radio1",              {type: "checkbox", icon: "", checked: true, disabled: false},
                          "+Radio2",              {type: "checkbox", icon: "", checked: false, disabled: false},
                          "+Radio3",              {type: "checkbox", icon: "", checked: false, disabled: false},
                          "---",                  null,
                          "+Item w/ icon",        {type: "", icon: "favicon.ico", checked: false, disabled: false},
                          "+Item w/ bad icon",    {type: "", icon: "", checked: false, disabled: false},
                          "---",                  null,
                          "generated-submenu-1",  true,
                              ["+Radio1",             {type: "checkbox", icon: "", checked: false, disabled: false},
                               "+Radio2",             {type: "checkbox", icon: "", checked: true, disabled: false},
                               "+Radio3",             {type: "checkbox", icon: "", checked: false, disabled: false},
                               "---",                 null,
                               "+Checkbox",           {type: "checkbox", icon: "", checked: false, disabled: false}], null,
                          "---",                  null,
                          "context-back",         false,
                          "context-forward",      false,
                          "context-reload",       true,
                          "context-stop",         false,
                          "---",                  null,
                          "context-bookmarkpage", true,
                          "context-savepage",     true,
                          "context-sendpage",     true,
                          "---",                  null,
                          "context-viewbgimage",  false,
                          "context-selectall",    true,
                          "---",                  null,
                          "context-viewsource",   true,
                          "context-viewinfo",     true,
                          "---",                  null,
                          "context-inspect",      true]);

        invokeItemAction("0");
        closeContextMenu();
        openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
        break;

    case 17:
        // Context menu for element with assigned content context menu
        // The shift key should bypass content context menu processing
        checkContextMenu(["context-back",         false,
                          "context-forward",      false,
                          "context-reload",       true,
                          "context-stop",         false,
                          "---",                  null,
                          "context-bookmarkpage", true,
                          "context-savepage",     true,
                          "context-sendpage",     true,
                          "---",                  null,
                          "context-viewbgimage",  false,
                          "context-selectall",    true,
                          "---",                  null,
                          "context-viewsource",   true,
                          "context-viewinfo",     true,
                          "---",                  null,
                          "context-inspect",      true]);

        subwindow.close();
        SimpleTest.finish();
        return;

    /*
     * Other things that would be nice to test:
     *  - selected text
     *  - spelling / misspelled word (in text input?)
     *  - check state of disabled items
     *  - test execution of menu items (maybe as a separate test?)
     */

    default:
        ok(false, "Unexpected invocation of test #" + testNum);
        subwindow.close();
        SimpleTest.finish();
        return;
  }

}


var testNum = 1;
var subwindow, chromeWin, contextMenu, lastElement;
var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
    iframe, textarea, contenteditable, inputspell, pagemenu;

function startTest() {
    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
    chromeWin = subwindow
                    .QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShellTreeItem)
                    .rootTreeItem
                    .QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindow)
                    .QueryInterface(Ci.nsIDOMChromeWindow);
    contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
    ok(contextMenu, "Got context menu XUL");

    if (chromeWin.document.getElementById("Browser:Stop").getAttribute("disabled") != "true") {
      SimpleTest.executeSoon(startTest);
      return;
    }

    lastElement = null;

    text   = subwindow.document.getElementById("test-text");
    link   = subwindow.document.getElementById("test-link");
    mailto = subwindow.document.getElementById("test-mailto");
    input  = subwindow.document.getElementById("test-input");
    img    = subwindow.document.getElementById("test-image");
    canvas = subwindow.document.getElementById("test-canvas");
    video_ok   = subwindow.document.getElementById("test-video-ok");
    video_bad  = subwindow.document.getElementById("test-video-bad");
    video_bad2 = subwindow.document.getElementById("test-video-bad2");
    iframe = subwindow.document.getElementById("test-iframe");
    textarea = subwindow.document.getElementById("test-textarea");
    contenteditable = subwindow.document.getElementById("test-contenteditable");
    contenteditable.focus(); // content editable needs to be focused to enable spellcheck
    inputspell = subwindow.document.getElementById("test-input-spellcheck");
    pagemenu = subwindow.document.getElementById("test-pagemenu");

    contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false);
    runTest(1);
}

// We open this in a separate window, because the Mochitests run inside a frame.
// The frame causes an extra menu item, and prevents running the test
// standalone (ie, clicking the test name in the Mochitest window) to see
// success/failure messages.
var painted = false, loaded = false;

function waitForEvents(event)
{
  if (event.type == "MozAfterPaint")
    painted = true;
  else if (event.type == "load")
    loaded = true;
  if (painted && loaded) {
    subwindow.removeEventListener("MozAfterPaint", waitForEvents, false);
    subwindow.onload = null;
    startTest();
  }
}

var subwindow = window.open("./subtst_contextmenu.html", "contextmenu-subtext", "width=600,height=700");
subwindow.addEventListener("MozAfterPaint", waitForEvents, false);
subwindow.onload = waitForEvents;

SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>