/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Test that context menu items are enabled / disabled correctly. const TEST_URL = URL_ROOT + "doc_inspector_menu.html"; const PASTE_MENU_ITEMS = [ "node-menu-pasteinnerhtml", "node-menu-pasteouterhtml", "node-menu-pastebefore", "node-menu-pasteafter", "node-menu-pastefirstchild", "node-menu-pastelastchild", ]; const ACTIVE_ON_DOCTYPE_ITEMS = [ "node-menu-showdomproperties", "node-menu-useinconsole", ]; const ACTIVE_ON_SHADOW_ROOT_ITEMS = [ "node-menu-pasteinnerhtml", "node-menu-copyinner", "node-menu-edithtml", ].concat(ACTIVE_ON_DOCTYPE_ITEMS); const ALL_MENU_ITEMS = [ "node-menu-edithtml", "node-menu-copyinner", "node-menu-copyouter", "node-menu-copyuniqueselector", "node-menu-copycsspath", "node-menu-copyxpath", "node-menu-copyimagedatauri", "node-menu-delete", "node-menu-pseudo-hover", "node-menu-pseudo-active", "node-menu-pseudo-focus", "node-menu-pseudo-focus-within", "node-menu-scrollnodeintoview", "node-menu-screenshotnode", "node-menu-add-attribute", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS); const INACTIVE_ON_DOCTYPE_ITEMS = ALL_MENU_ITEMS.filter( item => !ACTIVE_ON_DOCTYPE_ITEMS.includes(item) ); const INACTIVE_ON_DOCUMENT_ITEMS = INACTIVE_ON_DOCTYPE_ITEMS; const INACTIVE_ON_SHADOW_ROOT_ITEMS = ALL_MENU_ITEMS.filter( item => !ACTIVE_ON_SHADOW_ROOT_ITEMS.includes(item) ); /** * Test cases, each item of this array may define the following properties: * desc: string that will be logged * selector: selector of the node to be selected * disabled: items that should have disabled state * clipboardData: clipboard content * clipboardDataType: clipboard content type * attributeTrigger: attribute that will be used as context menu trigger * shadowRoot: if true, selects the shadow root from the node, rather than * the node itself. */ const TEST_CASES = [ { desc: "doctype node with empty clipboard", selector: null, disabled: INACTIVE_ON_DOCTYPE_ITEMS, }, { desc: "doctype node with html on clipboard", clipboardData: "

some text

", clipboardDataType: "text", selector: null, disabled: INACTIVE_ON_DOCTYPE_ITEMS, }, { desc: "element node HTML on the clipboard", clipboardData: "

some text

", clipboardDataType: "text", disabled: [ "node-menu-copyimagedatauri", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ], selector: "#sensitivity", }, { desc: " element", clipboardData: "

some text

", clipboardDataType: "text", selector: "html", disabled: [ "node-menu-copyimagedatauri", "node-menu-pastebefore", "node-menu-pasteafter", "node-menu-pastefirstchild", "node-menu-pastelastchild", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", "node-menu-delete", ], }, { desc: " with HTML on clipboard", clipboardData: "

some text

", clipboardDataType: "text", selector: "body", disabled: [ "node-menu-copyimagedatauri", "node-menu-pastebefore", "node-menu-pasteafter", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ], }, { desc: " with HTML on clipboard", clipboardData: "

some text

", clipboardDataType: "text", selector: "img", disabled: [ "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ], }, { desc: " with HTML on clipboard", clipboardData: "

some text

", clipboardDataType: "text", selector: "head", disabled: [ "node-menu-copyimagedatauri", "node-menu-pastebefore", "node-menu-pasteafter", "node-menu-screenshotnode", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ], }, { desc: " with no html on clipboard", selector: "head", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-screenshotnode", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " with text on clipboard", clipboardData: "some text", clipboardDataType: "text", selector: "#paste-area", disabled: [ "node-menu-copyimagedatauri", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ], }, { desc: " with base64 encoded image data uri on clipboard", clipboardData: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" + "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==", clipboardDataType: "image", selector: "#paste-area", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " with empty string on clipboard", clipboardData: "", clipboardDataType: "text", selector: "#paste-area", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " with whitespace only on clipboard", clipboardData: " \n\n\t\n\n \n", clipboardDataType: "text", selector: "#paste-area", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " that isn't visible on the page, empty clipboard", selector: "#hiddenElement", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-screenshotnode", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " nested in another hidden element, empty clipboard", selector: "#nestedHiddenElement", disabled: PASTE_MENU_ITEMS.concat([ "node-menu-copyimagedatauri", "node-menu-screenshotnode", "node-menu-copy-attribute", "node-menu-edit-attribute", "node-menu-remove-attribute", ]), }, { desc: " with context menu triggered on attribute, empty clipboard", selector: "#attributes", disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]), attributeTrigger: "data-edit", }, { desc: "Shadow Root", clipboardData: "

some text

", clipboardDataType: "text", disabled: INACTIVE_ON_SHADOW_ROOT_ITEMS, selector: "#host", shadowRoot: true, }, { desc: "Document node in iFrame", disabled: INACTIVE_ON_DOCUMENT_ITEMS, selector: "iframe", documentNode: true, }, ]; var clipboard = require("resource://devtools/shared/platform/clipboard.js"); registerCleanupFunction(() => { clipboard.copyString(""); clipboard = null; }); add_task(async function () { const { inspector } = await openInspectorForURL(TEST_URL); for (const test of TEST_CASES) { const { desc, disabled, selector, attributeTrigger, documentNode = false, shadowRoot = false, } = test; info(`Test ${desc}`); setupClipboard(test.clipboardData, test.clipboardDataType); const front = await getNodeFrontForSelector( selector, inspector, documentNode, shadowRoot ); info("Selecting the specified node."); await selectNode(front, inspector); info("Simulating context menu click on the selected node container."); const nodeFrontContainer = getContainerForNodeFront(front, inspector); const contextMenuTrigger = attributeTrigger ? nodeFrontContainer.tagLine.querySelector( `[data-attr="${attributeTrigger}"]` ) : nodeFrontContainer.tagLine; const allMenuItems = openContextMenuAndGetAllItems(inspector, { target: contextMenuTrigger, }); for (const id of ALL_MENU_ITEMS) { const menuItem = allMenuItems.find(item => item.id === id); const shouldBeDisabled = disabled.includes(id); const shouldBeDisabledText = shouldBeDisabled ? "disabled" : "enabled"; is( menuItem.disabled, shouldBeDisabled, `#${id} should be ${shouldBeDisabledText} for test case ${desc}` ); } } }); /** * A helper that fetches a front for a node that matches the given selector or * doctype node if the selector is falsy. */ async function getNodeFrontForSelector( selector, inspector, documentNode, shadowRoot ) { if (selector) { info("Retrieving front for selector " + selector); const node = await getNodeFront(selector, inspector); if (shadowRoot) { return getShadowRoot(node, inspector); } if (documentNode) { return getFrameDocument(node, inspector); } return node; } info("Retrieving front for doctype node"); const { nodes } = await inspector.walker.children(inspector.walker.rootNode); return nodes[0]; } /** * A helper that populates the clipboard with data of given type. Clears the * clipboard if data is falsy. */ function setupClipboard(data, type) { if (!data) { info("Clearing the clipboard."); clipboard.copyString(""); } else if (type === "text") { info("Populating clipboard with text."); clipboard.copyString(data); } else if (type === "image") { info("Populating clipboard with image content"); copyImageToClipboard(data); } } /** * The code below is a simplified version of the sdk/clipboard helper set() method. */ function copyImageToClipboard(data) { const imageTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools); // Image data is stored as base64 in the test. const image = atob(data); const imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance( Ci.nsISupportsInterfacePointer ); imgPtr.data = imageTools.decodeImageFromBuffer( image, image.length, "image/png" ); const xferable = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); xferable.init(null); xferable.addDataFlavor("image/png"); xferable.setTransferData("image/png", imgPtr); Services.clipboard.setData( xferable, null, Services.clipboard.kGlobalClipboard ); }