/* 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/. */ "use strict"; const { Cc, Ci } = require("chrome"); const {openWindow, closeWindow, openTab, closeTab, openContextMenu, closeContextMenu, select, readNode, captureContextMenu, withTab, withItems } = require("./context-menu/util"); const {when} = require("sdk/dom/events"); const {Item, Menu, Separator, Contexts, Readers } = require("sdk/context-menu@2"); const prefs = require("sdk/preferences/service"); const { before, after } = require('sdk/test/utils'); const testPageURI = require.resolve("./test-context-menu").replace(".js", ".html"); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const data = input => `data:text/html;charset=utf-8,${encodeURIComponent(input)}` const menugroup = (...children) => Object.assign({ tagName: "menugroup", namespaceURI: XUL_NS, style: "-moz-box-orient: vertical;", className: "sdk-context-menu-extension" }, children.length ? {children} : {}); const menuseparator = () => ({ tagName: "menuseparator", namespaceURI: XUL_NS, className: "sdk-context-menu-separator" }) const menuitem = properties => Object.assign({ tagName: "menuitem", namespaceURI: XUL_NS, className: "sdk-context-menu-item menuitem-iconic" }, properties); const menu = (properties, ...children) => Object.assign({ tagName: "menu", namespaceURI: XUL_NS, className: "sdk-context-menu menu-iconic" }, properties, { children: [Object.assign({tagName: "menupopup", namespaceURI: XUL_NS}, children.length ? {children} : {})] }); // Destroying items that were previously created should cause them to be absent // from the menu. exports["test create / destroy menu item"] = withTab(function*(assert) { const item = new Item({ label: "test-1" }); const before = yield captureContextMenu("h1"); assert.deepEqual(before, menugroup(menuseparator(), menuitem({label: "test-1"})), "context menu contains separator & added item"); item.destroy(); const after = yield captureContextMenu("h1"); assert.deepEqual(after, menugroup(), "all items were removed children are present"); }, data`<h1>hello</h1>`); /* Bug 1115419 - Disable occasionally failing test until we figure out why it fails. // Items created should be present on all browser windows. exports["test menu item in new window"] = function*(assert) { const isMenuPopulated = function*(tab) { const state = yield captureContextMenu("h1", tab); assert.deepEqual(state, menugroup(menuseparator(), menuitem({label: "multi-window"})), "created menu item is present") }; const isMenuEmpty = function*(tab) { const state = yield captureContextMenu("h1", tab); assert.deepEqual(state, menugroup(), "no sdk items present"); }; const item = new Item({ label: "multi-window" }); const tab1 = yield openTab(`data:text/html,<h1>hello</h1>`); yield* isMenuPopulated(tab1); const window2 = yield openWindow(); assert.pass("window is ready"); const tab2 = yield openTab(`data:text/html,<h1>hello window-2</h1>`, window2); assert.pass("tab is ready"); yield* isMenuPopulated(tab2); item.destroy(); yield* isMenuEmpty(tab2); yield closeWindow(window2); yield* isMenuEmpty(tab1); yield closeTab(tab1); }; */ // Multilpe items can be created and destroyed at different points // in time & they should not affect each other. exports["test multiple items"] = withTab(function*(assert) { const item1 = new Item({ label: "one" }); const step1 = yield captureContextMenu("h1"); assert.deepEqual(step1, menugroup(menuseparator(), menuitem({label: "one"})), "item1 is present"); const item2 = new Item({ label: "two" }); const step2 = yield captureContextMenu("h1"); assert.deepEqual(step2, menugroup(menuseparator(), menuitem({label: "one"}), menuitem({label: "two"})), "both items where present"); item1.destroy(); const step3 = yield captureContextMenu("h1"); assert.deepEqual(step3, menugroup(menuseparator(), menuitem({label: "two"})), "one items left"); item2.destroy(); const step4 = yield captureContextMenu("h1"); assert.deepEqual(step4, menugroup(), "no items left"); }, data`<h1>Multiple Items</h1>`); // Destroying an item twice should not cause an error. exports["test destroy twice"] = withTab(function*(assert) { const item = new Item({ label: "destroy" }); const withItem = yield captureContextMenu("h2"); assert.deepEqual(withItem, menugroup(menuseparator(), menuitem({label:"destroy"})), "Item is added"); item.destroy(); const withoutItem = yield captureContextMenu("h2"); assert.deepEqual(withoutItem, menugroup(), "Item was removed"); item.destroy(); assert.pass("Destroying an item twice should not cause an error."); }, "data:text/html,<h2>item destroy</h2>"); // CSS selector contexts should cause their items to be absent from the menu // when the menu is not invoked on nodes that match selectors. exports["test selector context"] = withTab(function*(assert) { const item = new Item({ context: [new Contexts.Selector("body b")], label: "bold" }); const match = yield captureContextMenu("b"); assert.deepEqual(match, menugroup(menuseparator(), menuitem({label: "bold"})), "item mathched context"); const noMatch = yield captureContextMenu("i"); assert.deepEqual(noMatch, menugroup(), "item did not match context"); item.destroy(); const cleared = yield captureContextMenu("b"); assert.deepEqual(cleared, menugroup(), "item was removed"); }, data`<body><i>one</i><b>two</b></body>`); // CSS selector contexts should cause their items to be absent in the menu // when the menu is invoked even on nodes that have ancestors that match the // selectors. exports["test parent selector don't match children"] = withTab(function*(assert) { const item = new Item({ label: "parent match", context: [new Contexts.Selector("a[href]")] }); const match = yield captureContextMenu("a"); assert.deepEqual(match, menugroup(menuseparator(), menuitem({label: "parent match"})), "item mathched context"); const noMatch = yield captureContextMenu("strong"); assert.deepEqual(noMatch, menugroup(), "item did not mathch context"); item.destroy(); const destroyed = yield captureContextMenu("a"); assert.deepEqual(destroyed, menugroup(), "no items left"); }, data`<a href='/foo'>This text must be long & <strong>bold!</strong></a>`); // Page contexts should cause their items to be present in the menu when the // menu is not invoked on an active element. exports["test page context match"] = withTab(function*(assert) { const isPageMatch = (tree, description="page context matched") => assert.deepEqual(tree, menugroup(menuseparator(), menuitem({label: "page match"}), menuitem({label: "any match"})), description); const isntPageMatch = (tree, description="page context did not match") => assert.deepEqual(tree, menugroup(menuseparator(), menuitem({label: "any match"})), description); yield* withItems({ pageMatch: new Item({ label: "page match", context: [new Contexts.Page()], }), anyMatch: new Item({ label: "any match" }) }, function*({pageMatch, anyMatch}) { for (let tagName of [null, "p", "h3"]) { isPageMatch((yield captureContextMenu(tagName)), `Page context matches ${tagName} passive element`); } for (let tagName of ["button", "canvas", "img", "input", "textarea", "select", "menu", "embed" ,"object", "video", "audio", "applet"]) { isntPageMatch((yield captureContextMenu(tagName)), `Page context does not match <${tagName}/> active element`); } for (let selector of ["span"]) { isntPageMatch((yield captureContextMenu(selector)), `Page context does not match decedents of active element`); } }); }, data`<head> <style> p, object, embed { display: inline-block; } </style> </head> <body> <div><p>paragraph</p></div> <div><a href=./link><span>link</span></a></div> <h3>hi</h3> <div><button>button</button></div> <div><canvas height=10 /></div> <div><img height=10 width=10 /></div> <div><input value=input /></div> <div><textarea>text</textarea></div> <div><select><option>one</option><option>two</option></select></div> <div><menu><button>item</button></menu></div> <div><object width=10 height=10><param name=foo value=bar /></object></div> <div><embed width=10 height=10/></div> <div><video width=10 height=10 controls /></div> <div><audio width=10 height=10 controls /></div> <div><applet width=10 height=10 /></div> </body>`); // Page context does not match if if there is a selection. exports["test page context doesn't match on selection"] = withTab(function*(assert) { const isPageMatch = (tree, description="page context matched") => assert.deepEqual(tree, menugroup(menuseparator(), menuitem({label: "page match"}), menuitem({label: "any match"})), description); const isntPageMatch = (tree, description="page context did not match") => assert.deepEqual(tree, menugroup(menuseparator(), menuitem({label: "any match"})), description); yield* withItems({ pageMatch: new Item({ label: "page match", context: [new Contexts.Page()], }), anyMatch: new Item({ label: "any match" }) }, function*({pageMatch, anyMatch}) { yield select("b"); isntPageMatch((yield captureContextMenu("i")), "page context does not match if there is a selection"); yield select(null); isPageMatch((yield captureContextMenu("i")), "page context match if there is no selection"); }); }, data`<body><i>one</i><b>two</b></body>`); exports["test selection context"] = withTab(function*(assert) { yield* withItems({ item: new Item({ label: "selection", context: [new Contexts.Selection()] }) }, function*({item}) { assert.deepEqual((yield captureContextMenu()), menugroup(), "item does not match if there is no selection"); yield select("b"); assert.deepEqual((yield captureContextMenu()), menugroup(menuseparator(), menuitem({label: "selection"})), "item matches if there is a selection"); }); }, data`<i>one</i><b>two</b>`); exports["test selection context in textarea"] = withTab(function*(assert) { yield* withItems({ item: new Item({ label: "selection", context: [new Contexts.Selection()] }) }, function*({item}) { assert.deepEqual((yield captureContextMenu()), menugroup(), "does not match if there's no selection"); yield select({target:"textarea", start:0, end:5}); assert.deepEqual((yield captureContextMenu("b")), menugroup(), "does not match if target isn't input with selection"); assert.deepEqual((yield captureContextMenu("textarea")), menugroup(menuseparator(), menuitem({label: "selection"})), "matches if target is input with selected text"); yield select({target: "textarea", start: 0, end: 0}); assert.deepEqual((yield captureContextMenu("textarea")), menugroup(), "does not match when selection is cleared"); }); }, data`<textarea>Hello World</textarea><b>!!</b>`); exports["test url contexts"] = withTab(function*(assert) { yield* withItems({ a: new Item({ label: "a", context: [new Contexts.URL(testPageURI)] }), b: new Item({ label: "b", context: [new Contexts.URL("*.bogus.com")] }), c: new Item({ label: "c", context: [new Contexts.URL("*.bogus.com"), new Contexts.URL(testPageURI)] }), d: new Item({ label: "d", context: [new Contexts.URL(/.*\.html/)] }), e: new Item({ label: "e", context: [new Contexts.URL("http://*"), new Contexts.URL(testPageURI)] }), f: new Item({ label: "f", context: [new Contexts.URL("http://*").required, new Contexts.URL(testPageURI)] }), }, function*(_) { assert.deepEqual((yield captureContextMenu()), menugroup(menuseparator(), menuitem({label: "a"}), menuitem({label: "c"}), menuitem({label: "d"}), menuitem({label: "e"})), "shows only matching items"); }); }, testPageURI); exports["test iframe context"] = withTab(function*(assert) { yield* withItems({ page: new Item({ label: "page", context: [new Contexts.Page()] }), iframe: new Item({ label: "iframe", context: [new Contexts.Frame()] }), h2: new Item({ label: "element", context: [new Contexts.Selector("*")] }) }, function(_) { assert.deepEqual((yield captureContextMenu("iframe")), menugroup(menuseparator(), menuitem({label: "page"}), menuitem({label: "iframe"}), menuitem({label: "element"})), "matching items are present"); assert.deepEqual((yield captureContextMenu("h1")), menugroup(menuseparator(), menuitem({label: "page"}), menuitem({label: "element"})), "only matching items are present"); }); }, data`<h1>hello</h1> <iframe src='data:text/html,<body>Bye</body>' />`); exports["test link context"] = withTab(function*(assert) { yield* withItems({ item: new Item({ label: "link", context: [new Contexts.Link()] }) }, function*(_) { assert.deepEqual((yield captureContextMenu("h1")), menugroup(menuseparator(), menuitem({label: "link"})), "matches anchor child"); assert.deepEqual((yield captureContextMenu("i")), menugroup(menuseparator(), menuitem({label: "link"})), "matches anchor decedent"); assert.deepEqual((yield captureContextMenu("h2")), menugroup(), "does not match if not under anchor"); }); }, data`<a href="/link"><h1>Hello <i>World</i></h1></a><h2>miss</h2>`); exports["test editable context"] = withTab(function*(assert) { const isntEditable = function*(selector) { assert.deepEqual((yield captureContextMenu(selector)), menugroup(), `${selector} isn't editable`); }; const isEditable = function*(selector) { assert.deepEqual((yield captureContextMenu(selector)), menugroup(menuseparator(), menuitem({label: "editable"})), `${selector} is editable`); }; yield* withItems({ item: new Item({ label: "editable", context: [new Contexts.Editable()] }) }, function*(_) { yield* isntEditable("h1"); yield* isEditable("input[id=text]"); yield* isntEditable("input[disabled=true]"); yield* isntEditable("input[readonly=true]"); yield* isntEditable("input[type=submit]"); yield* isntEditable("input[type=radio]"); yield* isntEditable("input[type=checkbox]"); yield* isEditable("input[type=foo]"); yield* isEditable("textarea"); yield* isEditable("[contenteditable=true]"); }); }, data`<body> <h1>examles</h1> <pre contenteditable="true">This content is editable.</pre> <input type="text" readonly="true" value="readonly value"> <input type="text" disabled="true" value="disabled value"> <input type="text" id=text value="test value"> <input type="submit" /> <input type="radio" /> <input type="foo" /> <input type="checkbox" /> <textarea>A text field, with some text.</textarea> </body>`); exports["test image context"] = withTab(function*(assert) { yield withItems({ item: new Item({ label: "image", context: [new Contexts.Image()] }) }, function*(_) { assert.deepEqual((yield captureContextMenu("img")), menugroup(menuseparator(), menuitem({label: "image"})), `<img/> matches image context`); assert.deepEqual((yield captureContextMenu("p image")), menugroup(), `<image/> does not image context`); assert.deepEqual((yield captureContextMenu("svg image")), menugroup(menuseparator(), menuitem({label: "image"})), `<svg:image/> matches image context`); }); }, data`<body> <p><image style="width: 50px; height: 50px" /></p> <img src='' /> <div> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink"> <image x="0" y="0" height="50px" width="50px" xlink:href=""/> </svg> <div> </body>`); exports["test audiot & video contexts"] = withTab(function*(assert) { yield withItems({ audio: new Item({ label: "audio", context: [new Contexts.Audio()] }), video: new Item({ label: "video", context: [new Contexts.Video()] }), media: new Item({ label: "media", context: [new Contexts.Audio(), new Contexts.Video()] }) }, function*(_) { assert.deepEqual((yield captureContextMenu("img")), menugroup(), `<img/> does not match video or audio context`); assert.deepEqual((yield captureContextMenu("audio")), menugroup(menuseparator(), menuitem({label: "audio"}), menuitem({label: "media"})), `<audio/> matches audio context`); assert.deepEqual((yield captureContextMenu("video")), menugroup(menuseparator(), menuitem({label: "video"}), menuitem({label: "media"})), `<video/> matches video context`); }) }, data`<body> <div><video width=10 height=10 controls /></div> <div><audio width=10 height=10 controls /></div> <div><image style="width: 50px; height: 50px" /></div> </body>`); const predicateTestURL = data`<html> <head> <style> p, object, embed { display: inline-block; } </style> </head> <body> <strong><p>paragraph</p></strong> <p><a href=./link><span>link</span></a></p> <p><h3>hi</h3></p> <p><button>button</button></p> <p><canvas height=50 width=50 /></p> <p><img height=50 width=50 src="./no.png" /></p> <p><code contenteditable="true">This content is editable.</code></p> <p><input type="text" readonly="true" value="readonly value"></p> <p><input type="text" disabled="true" value="disabled value"></p> <p><input type="text" id=text value="test value" /></p> <p><input type="submit" /></p> <p><input type="radio" /></p> <p><input type="foo" /></p> <p><input type="checkbox" /></p> <p><textarea>A text field, with some text.</textarea></p> <p><iframe src='data:text/html,<body style="height:100%">Bye</body>'></iframe></p> <p><select><option>one</option><option>two</option></select></p> <p><menu><button>item</button></menu></p> <p><object width=10 height=10><param name=foo value=bar /></object></p> <p><embed width=10 height=10/></p> <p><video width=50 height=50 controls /></p> <p><audio width=10 height=10 controls /></p> <p><applet width=30 height=30 /></p> </body> </html>`; exports["test predicate context"] = withTab(function*(assert) { const test = function*(selector, expect) { var isMatch = false; test.return = (target) => { return isMatch = expect(target); } assert.deepEqual((yield captureContextMenu(selector)), isMatch ? menugroup(menuseparator(), menuitem({label:"predicate"})) : menugroup(), isMatch ? `predicate item matches ${selector}` : `predicate item doesn't match ${selector}`); }; test.predicate = target => test.return(target); yield* withItems({ item: new Item({ label: "predicate", read: { mediaType: new Readers.MediaType(), link: new Readers.LinkURL(), isPage: new Readers.isPage(), isFrame: new Readers.isFrame(), isEditable: new Readers.isEditable(), tagName: new Readers.Query("tagName"), appCodeName: new Readers.Query("ownerDocument.defaultView.navigator.appCodeName"), width: new Readers.Attribute("width"), src: new Readers.SrcURL(), url: new Readers.PageURL(), selection: new Readers.Selection() }, context: [Contexts.Predicate(test.predicate)] }) }, function*(items) { yield* test("strong p", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: true, isFrame: false, isEditable: false, tagName: "P", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "pagraph read test"); return true; }); yield* test("a span", target => { assert.deepEqual(target, { mediaType: null, link: "./link", isPage: false, isFrame: false, isEditable: false, tagName: "SPAN", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "video tag test"); return false; }); yield* test("h3", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: true, isFrame: false, isEditable: false, tagName: "H3", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "video tag test"); return false; }); yield select("h3"); yield* test("a span", target => { assert.deepEqual(target, { mediaType: null, link: "./link", isPage: false, isFrame: false, isEditable: false, tagName: "SPAN", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: "hi", }, "test selection with link"); return true; }); yield select(null); yield* test("button", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "BUTTON", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test button"); return true; }); yield* test("canvas", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "CANVAS", appCodeName: "Mozilla", width: "50", src: null, url: predicateTestURL, selection: null, }, "test button"); return true; }); yield* test("img", target => { assert.deepEqual(target, { mediaType: "image", link: null, isPage: false, isFrame: false, isEditable: false, tagName: "IMG", appCodeName: "Mozilla", width: "50", src: "./no.png", url: predicateTestURL, selection: null, }, "test image"); return true; }); yield* test("code", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: true, tagName: "CODE", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test content editable"); return false; }); yield* test("input[readonly=true]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test readonly input"); return false; }); yield* test("input[disabled=true]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test disabled input"); return false; }); yield select({target: "input#text", start: 0, end: 5 }); yield* test("input#text", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: true, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: "test ", }, "test editable input"); return false; }); yield select({target: "input#text", start:0, end: 0}); yield* test("input[type=submit]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test submit input"); return false; }); yield* test("input[type=radio]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test radio input"); return false; }); yield* test("input[type=checkbox]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test checkbox input"); return false; }); yield* test("input[type=foo]", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: true, tagName: "INPUT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test unrecognized input"); return false; }); yield* test("textarea", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: true, tagName: "TEXTAREA", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test textarea"); return false; }); yield* test("iframe", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: true, isFrame: true, isEditable: false, tagName: "BODY", appCodeName: "Mozilla", width: null, src: null, url: `data:text/html,<body%20style="height:100%">Bye</body>`, selection: null, }, "test iframe"); return true; }); yield* test("select", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "SELECT", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test select"); return true; }); yield* test("menu", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "MENU", appCodeName: "Mozilla", width: null, src: null, url: predicateTestURL, selection: null, }, "test menu"); return false; }); yield* test("video", target => { assert.deepEqual(target, { mediaType: "video", link: null, isPage: false, isFrame: false, isEditable: false, tagName: "VIDEO", appCodeName: "Mozilla", width: "50", src: null, url: predicateTestURL, selection: null, }, "test video"); return true; }); yield* test("audio", target => { assert.deepEqual(target, { mediaType: "audio", link: null, isPage: false, isFrame: false, isEditable: false, tagName: "AUDIO", appCodeName: "Mozilla", width: "10", src: null, url: predicateTestURL, selection: null, }, "test audio"); return true; }); yield* test("object", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "OBJECT", appCodeName: "Mozilla", width: "10", src: null, url: predicateTestURL, selection: null, }, "test object"); return true; }); yield* test("embed", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "EMBED", appCodeName: "Mozilla", width: "10", src: null, url: predicateTestURL, selection: null, }, "test embed"); return true; }); yield* test("applet", target => { assert.deepEqual(target, { mediaType: null, link: null, isPage: false, isFrame: false, isEditable: false, tagName: "APPLET", appCodeName: "Mozilla", width: "30", src: null, url: predicateTestURL, selection: null, }, "test applet"); return false; }); }); }, predicateTestURL); exports["test extractor reader"] = withTab(function*(assert) { const test = function*(selector, expect) { var isMatch = false; test.return = (target) => { return isMatch = expect(target); } assert.deepEqual((yield captureContextMenu(selector)), isMatch ? menugroup(menuseparator(), menuitem({label:"extractor"})) : menugroup(), isMatch ? `predicate item matches ${selector}` : `predicate item doesn't match ${selector}`); }; test.predicate = target => test.return(target); yield* withItems({ item: new Item({ label: "extractor", context: [Contexts.Predicate(test.predicate)], read: { tagName: Readers.Query("tagName"), selector: Readers.Extractor(target => { let node = target; let path = []; while (node) { if (node.id) { path.unshift(`#${node.id}`); node = null; } else { path.unshift(node.localName); node = node.parentElement; } } return path.join(" > "); }) } }) }, function*(_) { yield* test("footer", target => { assert.deepEqual(target, { tagName: "FOOTER", selector: "html > body > nav > footer" }, "test footer"); return false; }); }); }, data`<html> <body> <nav> <header>begin</header> <footer>end</footer> </nav> <article data-index=1> <header>First title</header> <div> <p>First paragraph</p> <p>Second paragraph</p> </div> </article> <article data-index=2> <header>Second title</header> <div> <p>First <strong id=foo>paragraph</strong></p> <p>Second paragraph</p> </div> </article> </body> </html>`); exports["test items overflow"] = withTab(function*(assert) { yield* withItems({ i1: new Item({label: "item-1"}), i2: new Item({label: "item-2"}), i3: new Item({label: "item-3"}), i4: new Item({label: "item-4"}), i5: new Item({label: "item-5"}), i6: new Item({label: "item-6"}), i7: new Item({label: "item-7"}), i8: new Item({label: "item-8"}), i9: new Item({label: "item-9"}), i10: new Item({label: "item-10"}), }, function*(_) { assert.deepEqual((yield captureContextMenu("p")), menugroup(menu({ className: "sdk-context-menu-overflow-menu", label: "Add-ons", accesskey: "A", }, menuitem({label: "item-1"}), menuitem({label: "item-2"}), menuitem({label: "item-3"}), menuitem({label: "item-4"}), menuitem({label: "item-5"}), menuitem({label: "item-6"}), menuitem({label: "item-7"}), menuitem({label: "item-8"}), menuitem({label: "item-9"}), menuitem({label: "item-10"}))), "context menu has an overflow"); }); prefs.set("extensions.addon-sdk.context-menu.overflowThreshold", 3); yield* withItems({ i1: new Item({label: "item-1"}), i2: new Item({label: "item-2"}), }, function*(_) { assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "item-1"}), menuitem({label: "item-2"})), "two items do not overflow"); }); yield* withItems({ one: new Item({label: "one"}), two: new Item({label: "two"}), three: new Item({label: "three"}) }, function*(_) { assert.deepEqual((yield captureContextMenu("p")), menugroup(menu({className: "sdk-context-menu-overflow-menu", label: "Add-ons", accesskey: "A"}, menuitem({label: "one"}), menuitem({label: "two"}), menuitem({label: "three"}))), "three items overflow"); }); prefs.reset("extensions.addon-sdk.context-menu.overflowThreshold"); yield* withItems({ one: new Item({label: "one"}), two: new Item({label: "two"}), three: new Item({label: "three"}) }, function*(_) { assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "one"}), menuitem({label: "two"}), menuitem({label: "three"})), "three items no longer overflow"); }); }, data`<p>Hello</p>`); exports["test context menus"] = withTab(function*(assert) { const one = new Item({ label: "one", context: [Contexts.Selector("p")], read: {tagName: Readers.Query("tagName")} }); assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "one"})), "item is present"); const two = new Item({ label: "two", read: {tagName: Readers.Query("tagName")} }); assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "one"}), menuitem({label: "two"})), "both items are present"); const groupLevel1 = new Menu({label: "Level 1"}, [one]); assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "two"}), menu({label: "Level 1"}, menuitem({label: "one"}))), "first item moved to group"); assert.deepEqual((yield captureContextMenu("h1")), menugroup(menuseparator(), menuitem({label: "two"})), "menu is hidden since only item does not match"); const groupLevel2 = new Menu({label: "Level 2" }, [groupLevel1]); assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menuitem({label: "two"}), menu({label: "Level 2"}, menu({label: "Level 1"}, menuitem({label: "one"})))), "top level menu moved to submenu"); assert.deepEqual((yield captureContextMenu("h1")), menugroup(menuseparator(), menuitem({label: "two"})), "menu is hidden since only item does not match"); const contextGroup = new Menu({ label: "H1 Group", context: [Contexts.Selector("h1")] }, [ two, new Separator(), new Item({ label: "three" }) ]); assert.deepEqual((yield captureContextMenu("p")), menugroup(menuseparator(), menu({label: "Level 2"}, menu({label: "Level 1"}, menuitem({label: "one"})))), "nested menu is rendered"); assert.deepEqual((yield captureContextMenu("h1")), menugroup(menuseparator(), menu({label: "H1 Group"}, menuitem({label: "two"}), menuseparator(), menuitem({label: "three"}))), "new contextual menu rendered"); yield* withItems({one, two, groupLevel1, groupLevel2, contextGroup}, function*() { }); assert.deepEqual((yield captureContextMenu("p")), menugroup(), "everyhing matching p was desposed"); assert.deepEqual((yield captureContextMenu("h1")), menugroup(), "everyhing matching h1 was desposed"); }, data`<body><h1>Title</h1><p>Content</p></body>`); exports["test unloading"] = withTab(function*(assert) { const { Loader } = require("sdk/test/loader"); const loader = Loader(module); const {Item, Menu, Separator, Contexts, Readers } = loader.require("sdk/context-menu@2"); const item = new Item({label: "item"}); const group = new Menu({label: "menu"}, [new Separator(), new Item({label: "sub-item"})]); assert.deepEqual((yield captureContextMenu()), menugroup(menuseparator(), menuitem({label: "item"}), menu({label: "menu"}, menuseparator(), menuitem({label: "sub-item"}))), "all items rendered"); loader.unload(); assert.deepEqual((yield captureContextMenu()), menugroup(), "all items disposed"); }, data`<body></body>`); if (require("@loader/options").isNative) { module.exports = { "test skip on jpm": (assert) => assert.pass("skipping this file with jpm") }; } before(exports, (name, assert) => { // Make sure Java doesn't activate prefs.set("plugin.state.java", 0); }); after(exports, (name, assert) => { prefs.reset("plugin.state.java"); }); require("sdk/test").run(module.exports);