diff options
Diffstat (limited to 'addon-sdk/source/test/test-panel.js')
-rw-r--r-- | addon-sdk/source/test/test-panel.js | 1426 |
1 files changed, 1426 insertions, 0 deletions
diff --git a/addon-sdk/source/test/test-panel.js b/addon-sdk/source/test/test-panel.js new file mode 100644 index 000000000..13776e9db --- /dev/null +++ b/addon-sdk/source/test/test-panel.js @@ -0,0 +1,1426 @@ +/* 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'; + +module.metadata = { + 'engines': { + 'Firefox': '*' + } +}; + +const { Cc, Ci } = require("chrome"); +const { Loader } = require('sdk/test/loader'); +const { LoaderWithHookedConsole } = require("sdk/test/loader"); +const { setTimeout } = require("sdk/timers"); +const self = require('sdk/self'); +const { open, close, focus, ready } = require('sdk/window/helpers'); +const { isPrivate } = require('sdk/private-browsing'); +const { isWindowPBSupported } = require('sdk/private-browsing/utils'); +const { defer, all } = require('sdk/core/promise'); +const { getMostRecentBrowserWindow } = require('sdk/window/utils'); +const { URL } = require('sdk/url'); +const { wait } = require('./event/helpers'); +const packaging = require('@loader/options'); +const { cleanUI, after, isTravisCI } = require("sdk/test/utils"); +const { platform } = require('sdk/system'); + +const fixtures = require('./fixtures') + +const SVG_URL = fixtures.url('mofo_logo.SVG'); + +const Isolate = fn => '(' + fn + ')()'; + +function ignorePassingDOMNodeWarning(type, message) { + if (type !== 'warn' || !message.startsWith('Passing a DOM node')) + console[type](message); +} + +function makeEmptyPrivateBrowserWindow(options) { + options = options || {}; + return open('chrome://browser/content/browser.xul', { + features: { + chrome: true, + toolbar: true, + private: true + } + }); +} + +exports["test Panel"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.postMessage(1); self.on('message', () => self.postMessage(2));", + onMessage: function (message) { + assert.equal(this, panel, "The 'this' object is the panel."); + switch(message) { + case 1: + assert.pass("The panel was loaded."); + panel.postMessage(''); + break; + case 2: + assert.pass("The panel posted a message and received a response."); + panel.destroy(); + done(); + break; + } + } + }); +}; + +exports["test Panel Emit"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.port.emit('loaded');" + + "self.port.on('addon-to-content', " + + " () => self.port.emit('received'));", + }); + panel.port.on("loaded", function () { + assert.pass("The panel was loaded and sent a first event."); + panel.port.emit("addon-to-content"); + }); + panel.port.on("received", function () { + assert.pass("The panel posted a message and received a response."); + panel.destroy(); + done(); + }); +}; + +exports["test Panel Emit Early"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ + contentURL: "about:buildconfig", + contentScript: "self.port.on('addon-to-content', " + + " () => self.port.emit('received'));", + }); + panel.port.on("received", function () { + assert.pass("The panel posted a message early and received a response."); + panel.destroy(); + done(); + }); + panel.port.emit("addon-to-content"); +}; + +exports["test Show Hide Panel"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + contentURL: "data:text/html;charset=utf-8,", + onMessage: function (message) { + panel.show(); + }, + onShow: function () { + assert.pass("The panel was shown."); + assert.equal(this, panel, "The 'this' object is the panel."); + assert.equal(this.isShowing, true, "panel.isShowing == true."); + panel.hide(); + }, + onHide: function () { + assert.pass("The panel was hidden."); + assert.equal(this, panel, "The 'this' object is the panel."); + assert.equal(this.isShowing, false, "panel.isShowing == false."); + panel.destroy(); + done(); + } + }); +}; + +exports["test Document Reload"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let url2 = "data:text/html;charset=utf-8,page2"; + let content = + "<script>" + + "window.addEventListener('message', function({ data }) {"+ + " if (data == 'move') window.location = '" + url2 + "';" + + '}, false);' + + "</script>"; + let messageCount = 0; + let panel = Panel({ + // using URL here is intentional, see bug 859009 + contentURL: URL("data:text/html;charset=utf-8," + encodeURIComponent(content)), + contentScript: "self.postMessage(window.location.href);" + + // initiate change to url2 + "self.port.once('move', () => document.defaultView.postMessage('move', '*'));", + onMessage: function (message) { + messageCount++; + assert.notEqual(message, "about:blank", "about:blank is not a message " + messageCount); + + if (messageCount == 1) { + assert.ok(/data:text\/html/.test(message), "First document had a content script; " + message); + panel.port.emit('move'); + assert.pass('move message was sent'); + return; + } + else if (messageCount == 2) { + assert.equal(message, url2, "Second document too; " + message); + panel.destroy(); + done(); + } + } + }); + assert.pass('Panel was created'); +}; + +// Test disabled because of bug 910230 +/* +exports["test Parent Resize Hack"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let browserWindow = getMostRecentBrowserWindow(); + + let previousWidth = browserWindow.outerWidth; + let previousHeight = browserWindow.outerHeight; + + let content = "<script>" + + "function contentResize() {" + + " resizeTo(200,200);" + + " resizeBy(200,200);" + + " window.postMessage('resize-attempt', '*');" + + "}" + + "</script>" + + "Try to resize browser window"; + + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURIComponent(content), + contentScriptWhen: "ready", + contentScript: Isolate(() => { + self.on('message', message => { + if (message === 'resize') unsafeWindow.contentResize(); + }); + + window.addEventListener('message', ({ data }) => self.postMessage(data)); + }), + onMessage: function (message) { + if (message !== "resize-attempt") return; + + assert.equal(browserWindow, getMostRecentBrowserWindow(), + "The browser window is still the same"); + assert.equal(previousWidth, browserWindow.outerWidth, + "Size doesn't change by calling resizeTo/By/..."); + assert.equal(previousHeight, browserWindow.outerHeight, + "Size doesn't change by calling resizeTo/By/..."); + + try { + panel.destroy(); + } + catch (e) { + assert.fail(e); + throw e; + } + + done(); + }, + onShow: () => panel.postMessage('resize') + }); + + panel.show(); +} +*/ + +exports["test Resize Panel"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + // These tests fail on Linux if the browser window in which the panel + // is displayed is not active. And depending on what other tests have run + // before this one, it might not be (the untitled window in which the test + // runner executes is often active). So we make sure the browser window + // is focused by focusing it before running the tests. Then, to be the best + // possible test citizen, we refocus whatever window was focused before we + // started running these tests. + + let activeWindow = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher). + activeWindow; + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + + + function onFocus() { + browserWindow.removeEventListener("focus", onFocus, true); + + let panel = Panel({ + contentScript: "self.postMessage('')", + contentScriptWhen: "end", + contentURL: "data:text/html;charset=utf-8,", + height: 10, + width: 10, + onMessage: function (message) { + // Make sure that attempting to resize a panel while it isn't + // visible doesn't cause an error. + panel.resize(1, 1); + + panel.show(); + }, + onShow: function () { + panel.resize(100,100); + panel.hide(); + }, + onHide: function () { + assert.ok((panel.width == 100) && (panel.height == 100), + "The panel was resized."); + if (activeWindow) + activeWindow.focus(); + done(); + } + }); + } + + if (browserWindow === activeWindow) { + onFocus(); + } + else { + browserWindow.addEventListener("focus", onFocus, true); + browserWindow.focus(); + } +}; + +exports["test Hide Before Show"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let showCalled = false; + let hideCalled = false; + let panel1 = Panel({ + onShow: function () { + showCalled = true; + }, + onHide: function () { + hideCalled = true; + } + }); + panel1.show(); + panel1.hide(); + + let panel2 = Panel({ + onShow: function () { + if (showCalled) { + assert.ok(hideCalled, 'should not emit show without also emitting hide'); + } else { + assert.ok(!hideCalled, 'should not emit hide without also emitting show'); + } + panel1.destroy(); + panel2.destroy(); + done(); + }, + }); + panel2.show(); +}; + +exports["test Several Show Hides"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let hideCalled = 0; + let panel = Panel({ + contentURL: "about:buildconfig", + onShow: function () { + panel.hide(); + }, + onHide: function () { + hideCalled++; + if (hideCalled < 3) + panel.show(); + else { + assert.pass("onHide called three times as expected"); + done(); + } + } + }); + panel.on('error', function(e) { + assert.fail('error was emitted:' + e.message + '\n' + e.stack); + }); + panel.show(); +}; + +exports["test Anchor And Arrow"] = function*(assert, done) { + let { loader } = LoaderWithHookedConsole(module, ignorePassingDOMNodeWarning); + let { Panel } = loader.require('sdk/panel'); + + let count = 0; + let url = 'data:text/html;charset=utf-8,' + + '<html><head><title>foo</title></head><body>' + + '</body></html>'; + + let panel = yield new Promise(resolve => { + let browserWindow = getMostRecentBrowserWindow(); + let anchor = browserWindow.document.getElementById("identity-box"); + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8,<html><body style='padding: 0; margin: 0; " + + "background: gray; text-align: center;'>Anchor: " + + anchor.id + "</body></html>", + width: 200, + height: 100, + onShow: () => resolve(panel) + }); + panel.show(null, anchor); + }); + assert.pass("All anchored panel test displayed"); + + panel.destroy(); + assert.pass("panel was destroyed."); +}; + +exports["test Panel Focus True"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + const FM = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + + // Make sure there is a focused element + browserWindow.document.documentElement.focus(); + + // Get the current focused element + let focusedElement = FM.focusedElement; + + let panel = Panel({ + contentURL: "about:buildconfig", + focus: true, + onShow: function () { + assert.ok(focusedElement !== FM.focusedElement, + "The panel takes the focus away."); + done(); + } + }); + panel.show(); +}; + +exports["test Panel Focus False"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + const FM = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + + // Make sure there is a focused element + browserWindow.document.documentElement.focus(); + + // Get the current focused element + let focusedElement = FM.focusedElement; + + let panel = Panel({ + contentURL: "about:buildconfig", + focus: false, + onShow: function () { + assert.ok(focusedElement === FM.focusedElement, + "The panel does not take the focus away."); + done(); + } + }); + panel.show(); +}; + +exports["test Panel Focus Not Set"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + const FM = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); + + let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator). + getMostRecentWindow("navigator:browser"); + + // Make sure there is a focused element + browserWindow.document.documentElement.focus(); + + // Get the current focused element + let focusedElement = FM.focusedElement; + + let panel = Panel({ + contentURL: "about:buildconfig", + onShow: function () { + assert.ok(focusedElement !== FM.focusedElement, + "The panel takes the focus away."); + done(); + } + }); + panel.show(); +}; + +exports["test Panel Text Color"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let html = "<html><head><style>body {color: yellow}</style></head>" + + "<body><p>Foo</p></body></html>"; + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURI(html), + contentScript: "self.port.emit('color', " + + "window.getComputedStyle(document.body.firstChild, null). " + + " getPropertyValue('color'));" + }); + panel.port.on("color", function (color) { + assert.equal(color, "rgb(255, 255, 0)", + "The panel text color style is preserved when a style exists."); + panel.destroy(); + done(); + }); +}; + +// Bug 866333 +exports["test watch event name"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let html = "<html><head><style>body {color: yellow}</style></head>" + + "<body><p>Foo</p></body></html>"; + + let panel = Panel({ + contentURL: "data:text/html;charset=utf-8," + encodeURI(html), + contentScript: "self.port.emit('watch', 'test');" + }); + panel.port.on("watch", function (msg) { + assert.equal(msg, "test", 'watch event name works'); + panel.destroy(); + done(); + }); +} + +// Bug 696552: Ensure panel.contentURL modification support +exports["test Change Content URL"] = function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ + contentURL: "about:blank", + contentScript: "self.port.emit('ready', document.location.href);" + }); + + let count = 0; + panel.port.on("ready", function (location) { + count++; + if (count == 1) { + assert.equal(location, "about:blank"); + assert.equal(panel.contentURL, "about:blank"); + panel.contentURL = "about:buildconfig"; + } + else { + assert.equal(location, "about:buildconfig"); + assert.equal(panel.contentURL, "about:buildconfig"); + panel.destroy(); + done(); + } + }); +}; + +function makeEventOrderTest(options) { + let expectedEvents = []; + + return function(assert, done) { + const { Panel } = require('sdk/panel'); + + let panel = Panel({ contentURL: "about:buildconfig" }); + + function expect(event, cb) { + expectedEvents.push(event); + panel.on(event, function() { + assert.equal(event, expectedEvents.shift()); + if (cb) + setTimeout(cb, 1); + }); + return {then: expect}; + } + + options.test(assert, done, expect, panel); + } +} + +exports["test Automatic Destroy"] = function(assert) { + let loader = Loader(module); + let panel = loader.require("sdk/panel").Panel({ + contentURL: "about:buildconfig", + contentScript: + "self.port.on('event', () => self.port.emit('event-back'));" + }); + + loader.unload(); + + assert.throws(() => { + panel.port.emit("event"); + }, /already have been unloaded/, "check automatic destroy"); +}; + +exports["test Show Then Destroy"] = makeEventOrderTest({ + test: function(assert, done, expect, panel) { + panel.show(); + expect('show', function() { panel.destroy(); }). + then('hide', function() { done(); }); + } +}); + + +// TODO: Re-enable and fix this intermittent test +// See Bug 1111695 https://bugzilla.mozilla.org/show_bug.cgi?id=1111695 +/* +exports["test Show Then Hide Then Destroy"] = makeEventOrderTest({ + test: function(assert, done, expect, panel) { + panel.show(); + expect('show', function() { panel.hide(); }). + then('hide', function() { panel.destroy(); done(); }); + } +}); +*/ + +exports["test Content URL Option"] = function(assert) { + const { Panel } = require('sdk/panel'); + + const URL_STRING = "about:buildconfig"; + const HTML_CONTENT = "<html><title>Test</title><p>This is a test.</p></html>"; + let dataURL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML_CONTENT); + + let panel = Panel({ contentURL: URL_STRING }); + assert.pass("contentURL accepts a string URL."); + assert.equal(panel.contentURL, URL_STRING, + "contentURL is the string to which it was set."); + panel.destroy(); + + panel = Panel({ contentURL: dataURL }); + assert.pass("contentURL accepts a data: URL."); + panel.destroy(); + + panel = Panel({}); + assert.ok(panel.contentURL == null, "contentURL is undefined."); + panel.destroy(); + + assert.throws(() => Panel({ contentURL: "foo" }), + /The `contentURL` option must be a valid URL./, + "Panel throws an exception if contentURL is not a URL."); +}; + +exports["test SVG Document"] = function(assert) { + let panel = require("sdk/panel").Panel({ contentURL: SVG_URL }); + + panel.show(); + panel.hide(); + panel.destroy(); + + assert.pass("contentURL accepts a svg document"); + assert.equal(panel.contentURL, SVG_URL, + "contentURL is the string to which it was set."); +}; + +exports["test ContentScriptOptions Option"] = function(assert, done) { + let loader = Loader(module); + let panel = loader.require("sdk/panel").Panel({ + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", + contentScriptWhen: "end", + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, + contentURL: "data:text/html;charset=utf-8,", + onMessage: function(msg) { + assert.equal( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); + assert.equal( typeof msg[1], 'object', 'object as contentScriptOptions' ); + assert.equal( msg[1].a, true, 'boolean in contentScriptOptions' ); + assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); + assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' ); + done(); + } + }); +}; + +exports["test console.log in Panel"] = function(assert, done) { + let text = 'console.log() in Panel works!'; + let html = '<script>onload = function log(){\ + console.log("' + text + '");\ + }</script>'; + + let { loader } = LoaderWithHookedConsole(module, onMessage); + let { Panel } = loader.require('sdk/panel'); + + let panel = Panel({ + contentURL: 'data:text/html;charset=utf-8,' + encodeURIComponent(html) + }); + + panel.show(); + + function onMessage(type, message) { + assert.equal(type, 'log', 'console.log() works'); + assert.equal(message, text, 'console.log() works'); + panel.destroy(); + done(); + } +}; + +/*if (isWindowPBSupported) { + exports.testPanelDoesNotShowInPrivateWindowNoAnchor = function(assert, done) { + let { loader } = LoaderWithHookedConsole(module, ignorePassingDOMNodeWarning); + let { Panel } = loader.require("sdk/panel"); + let browserWindow = getMostRecentBrowserWindow(); + + assert.equal(isPrivate(browserWindow), false, 'open window is not private'); + + let panel = Panel({ + contentURL: SVG_URL + }); + + testShowPanel(assert, panel). + then(makeEmptyPrivateBrowserWindow). + then(focus). + then(function(window) { + assert.equal(isPrivate(window), true, 'opened window is private'); + assert.pass('private window was focused'); + return window; + }). + then(function(window) { + let { promise, resolve } = defer(); + let showTries = 0; + let showCount = 0; + + panel.on('show', function runTests() { + showCount++; + + if (showTries == 2) { + panel.removeListener('show', runTests); + assert.equal(showCount, 1, 'show count is correct - 1'); + resolve(window); + } + }); + showTries++; + panel.show(); + showTries++; + panel.show(null, browserWindow.gBrowser); + + return promise; + }). + then(function(window) { + assert.equal(panel.isShowing, true, 'panel is still showing'); + panel.hide(); + assert.equal(panel.isShowing, false, 'panel is hidden'); + return window; + }). + then(close). + then(function() { + assert.pass('private window was closed'); + }). + then(testShowPanel.bind(null, assert, panel)). + then(done, assert.fail.bind(assert)); + } + + exports.testPanelDoesNotShowInPrivateWindowWithAnchor = function(assert, done) { + let { loader } = LoaderWithHookedConsole(module, ignorePassingDOMNodeWarning); + let { Panel } = loader.require("sdk/panel"); + let browserWindow = getMostRecentBrowserWindow(); + + assert.equal(isPrivate(browserWindow), false, 'open window is not private'); + + let panel = Panel({ + contentURL: SVG_URL + }); + + testShowPanel(assert, panel). + then(makeEmptyPrivateBrowserWindow). + then(focus). + then(function(window) { + assert.equal(isPrivate(window), true, 'opened window is private'); + assert.pass('private window was focused'); + return window; + }). + then(function(window) { + let { promise, resolve } = defer(); + let showTries = 0; + let showCount = 0; + + panel.on('show', function runTests() { + showCount++; + + if (showTries == 2) { + panel.removeListener('show', runTests); + assert.equal(showCount, 1, 'show count is correct - 1'); + resolve(window); + } + }); + showTries++; + panel.show(null, window.gBrowser); + showTries++; + panel.show(null, browserWindow.gBrowser); + + return promise; + }). + then(function(window) { + assert.equal(panel.isShowing, true, 'panel is still showing'); + panel.hide(); + assert.equal(panel.isShowing, false, 'panel is hidden'); + return window; + }). + then(close). + then(function() { + assert.pass('private window was closed'); + }). + then(testShowPanel.bind(null, assert, panel)). + then(done, assert.fail.bind(assert)); + } +}*/ + +function testShowPanel(assert, panel) { + let { promise, resolve } = defer(); + + assert.ok(!panel.isShowing, 'the panel is not showing [1]'); + + panel.once('show', function() { + assert.ok(panel.isShowing, 'the panel is showing'); + + panel.once('hide', function() { + assert.ok(!panel.isShowing, 'the panel is not showing [2]'); + + resolve(null); + }); + + panel.hide(); + }) + panel.show(); + + return promise; +} + +exports['test Style Applied Only Once'] = function (assert, done) { + let loader = Loader(module); + let panel = loader.require("sdk/panel").Panel({ + contentURL: "data:text/html;charset=utf-8,", + contentScript: + 'self.port.on("check",function() { self.port.emit("count", document.getElementsByTagName("style").length); });' + + 'self.port.on("ping", function (count) { self.port.emit("pong", count); });' + }); + + panel.port.on('count', function (styleCount) { + assert.equal(styleCount, 1, 'should only have one style'); + done(); + }); + + panel.port.on('pong', function (counter) { + panel[--counter % 2 ? 'hide' : 'show'](); + panel.port.emit(!counter ? 'check' : 'ping', counter); + }); + + panel.on('show', init); + panel.show(); + + function init () { + panel.removeListener('show', init); + panel.port.emit('ping', 10); + } +}; + +exports['test Only One Panel Open Concurrently'] = function (assert, done) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel') + + let panelA = Panel({ + contentURL: 'about:buildconfig' + }); + + let panelB = Panel({ + contentURL: 'about:buildconfig', + onShow: function () { + // When loading two panels simulataneously, only the second + // should be shown, never showing the first + assert.equal(panelA.isShowing, false, 'First panel is hidden'); + assert.equal(panelB.isShowing, true, 'Second panel is showing'); + panelC.show(); + } + }); + + let panelC = Panel({ + contentURL: 'about:buildconfig', + onShow: function () { + assert.equal(panelA.isShowing, false, 'First panel is hidden'); + assert.equal(panelB.isShowing, false, 'Second panel is hidden'); + assert.equal(panelC.isShowing, true, 'Third panel is showing'); + done(); + } + }); + + panelA.show(); + panelB.show(); +}; + +exports['test passing DOM node as first argument'] = function (assert, done) { + let warned = defer(); + let shown = defer(); + + function onMessage(type, message) { + if (type != 'warn') return; + + let warning = 'Passing a DOM node to Panel.show() method is an unsupported ' + + 'feature that will be soon replaced. ' + + 'See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877'; + + assert.equal(type, 'warn', + 'the message logged is a warning'); + + assert.equal(message, warning, + 'the warning content is correct'); + + warned.resolve(); + } + + let { loader } = LoaderWithHookedConsole(module, onMessage); + let { Panel } = loader.require('sdk/panel'); + let { ActionButton } = loader.require('sdk/ui/button/action'); + let { getNodeView } = loader.require('sdk/view/core'); + let { document } = getMostRecentBrowserWindow(); + + let panel = Panel({ + onShow: function() { + let panelNode = document.getElementById('mainPopupSet').lastChild; + + assert.equal(panelNode.anchorNode, buttonNode, + 'the panel is properly anchored to the button'); + + shown.resolve(); + } + }); + + let button = ActionButton({ + id: 'panel-button', + label: 'panel button', + icon: './icon.png' + }); + + let buttonNode = getNodeView(button); + + all([warned.promise, shown.promise]). + then(loader.unload). + then(done, assert.fail) + + panel.show(buttonNode); +}; + +// This test is checking that `onpupshowing` events emitted by panel's children +// are not considered. +// See Bug 886329 +exports['test nested popups'] = function (assert, done) { + let loader = Loader(module); + let { Panel } = loader.require('sdk/panel'); + let { getActiveView } = loader.require('sdk/view/core'); + let url = '<select><option>1<option>2<option>3</select>'; + + let getContentWindow = panel => { + return getActiveView(panel).querySelector('iframe').contentWindow; + } + + let panel = Panel({ + contentURL: 'data:text/html;charset=utf-8,' + encodeURIComponent(url), + onShow: () => { + ready(getContentWindow(panel)).then(({ window, document }) => { + let select = document.querySelector('select'); + let event = document.createEvent('UIEvent'); + + event.initUIEvent('popupshowing', true, true, window, null); + select.dispatchEvent(event); + + assert.equal( + select, + getContentWindow(panel).document.querySelector('select'), + 'select is still loaded in panel' + ); + + done(); + }); + } + }); + + panel.show(); +}; + +exports['test emits on url changes'] = function (assert, done) { + let loader = Loader(module); + let { Panel } = loader.require('sdk/panel'); + let uriA = 'data:text/html;charset=utf-8,A'; + let uriB = 'data:text/html;charset=utf-8,B'; + + let panel = Panel({ + contentURL: uriA, + contentScript: 'new ' + function() { + self.port.on('hi', function() { + self.port.emit('bye', document.URL); + }); + } + }); + + panel.contentURL = uriB; + panel.port.emit('hi', 'hi') + panel.port.on('bye', function(uri) { + assert.equal(uri, uriB, 'message was delivered to new uri'); + loader.unload(); + done(); + }); +}; + +exports['test panel can be constructed without any arguments'] = function (assert) { + const { Panel } = require('sdk/panel'); + + let panel = Panel(); + assert.ok(true, "Creating a panel with no arguments does not throw"); +}; + +exports['test panel CSS'] = function(assert, done) { + const { merge } = require("sdk/util/object"); + + let loader = Loader(module, null, null, { + modules: { + "sdk/self": merge({}, self, { + data: merge({}, self.data, fixtures) + }) + } + }); + + const { Panel } = loader.require('sdk/panel'); + + const { getActiveView } = loader.require('sdk/view/core'); + + const getContentWindow = panel => + getActiveView(panel).querySelector('iframe').contentWindow; + + let panel = Panel({ + contentURL: 'data:text/html;charset=utf-8,' + + '<div style="background: silver">css test</div>', + contentStyle: 'div { height: 100px; }', + contentStyleFile: [fixtures.url("include-file.css"), "./border-style.css"], + onShow: () => { + ready(getContentWindow(panel)).then(({ window, document }) => { + let div = document.querySelector('div'); + + assert.equal(div.clientHeight, 100, + "Panel contentStyle worked"); + + assert.equal(div.offsetHeight, 120, + "Panel contentStyleFile worked"); + + assert.equal(window.getComputedStyle(div).borderTopStyle, "dashed", + "Panel contentStyleFile with relative path worked"); + + loader.unload(); + done(); + }).then(null, assert.fail); + } + }); + + panel.show(); +}; + +exports['test panel contentScriptFile'] = function(assert, done) { + const { merge } = require("sdk/util/object"); + + let loader = Loader(module, null, null, { + modules: { + "sdk/self": merge({}, self, { + data: merge({}, self.data, {url: fixtures.url}) + }) + } + }); + + const { Panel } = loader.require('sdk/panel'); + const { getActiveView } = loader.require('sdk/view/core'); + + const getContentWindow = panel => + getActiveView(panel).querySelector('iframe').contentWindow; + + let whenMessage = defer(); + let whenShown = defer(); + + let panel = Panel({ + contentURL: './test.html', + contentScriptFile: "./test-contentScriptFile.js", + onMessage: (message) => { + assert.equal(message, "msg from contentScriptFile", + "Panel contentScriptFile with relative path worked"); + + whenMessage.resolve(); + }, + onShow: () => { + ready(getContentWindow(panel)).then(({ document }) => { + assert.equal(document.title, 'foo', + "Panel contentURL with relative path worked"); + + whenShown.resolve(); + }); + } + }); + + all([whenMessage.promise, whenShown.promise]). + then(loader.unload). + then(done, assert.fail); + + panel.show(); +}; + + +exports['test panel CSS list'] = function(assert, done) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel'); + + const { getActiveView } = loader.require('sdk/view/core'); + + const getContentWindow = panel => + getActiveView(panel).querySelector('iframe').contentWindow; + + let panel = Panel({ + contentURL: 'data:text/html;charset=utf-8,' + + '<div style="width:320px; max-width: 480px!important">css test</div>', + contentStyleFile: [ + // Highlight evaluation order in this list + "data:text/css;charset=utf-8,div { border: 1px solid black; }", + "data:text/css;charset=utf-8,div { border: 10px solid black; }", + // Highlight evaluation order between contentStylesheet & contentStylesheetFile + "data:text/css;charset=utf-8s,div { height: 1000px; }", + // Highlight precedence between the author and user style sheet + "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}", + ], + contentStyle: [ + "div { height: 10px; }", + "div { height: 100px; }" + ], + onShow: () => { + ready(getContentWindow(panel)).then(({ window, document }) => { + let div = document.querySelector('div'); + let style = window.getComputedStyle(div); + + assert.equal(div.clientHeight, 100, + 'Panel contentStyle list is evaluated after contentStyleFile'); + + assert.equal(div.offsetHeight, 120, + 'Panel contentStyleFile list works'); + + assert.equal(style.width, '320px', + 'add-on author/page author stylesheet precedence works'); + + assert.equal(style.maxWidth, '480px', + 'add-on author/page author stylesheet !important precedence works'); + + loader.unload(); + }).then(done, assert.fail); + } + }); + + panel.show(); +}; + +exports['test panel contextmenu validation'] = function(assert) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel'); + + let panel = Panel({}); + + assert.equal(panel.contextMenu, false, + 'contextMenu option is `false` by default'); + + panel.destroy(); + + panel = Panel({ + contextMenu: false + }); + + assert.equal(panel.contextMenu, false, + 'contextMenu option is `false`'); + + panel.contextMenu = true; + + assert.equal(panel.contextMenu, true, + 'contextMenu option accepts boolean values'); + + panel.destroy(); + + panel = Panel({ + contextMenu: true + }); + + assert.equal(panel.contextMenu, true, + 'contextMenu option is `true`'); + + panel.contextMenu = false; + + assert.equal(panel.contextMenu, false, + 'contextMenu option accepts boolean values'); + + assert.throws(() => + Panel({contextMenu: 1}), + /The option "contextMenu" must be one of the following types: boolean, undefined, null/, + 'contextMenu only accepts boolean or nil values'); + + panel = Panel(); + + assert.throws(() => + panel.contextMenu = 1, + /The option "contextMenu" must be one of the following types: boolean, undefined, null/, + 'contextMenu only accepts boolean or nil values'); + + loader.unload(); +} + +exports['test panel contextmenu enabled'] = function*(assert) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel'); + const { getActiveView } = loader.require('sdk/view/core'); + const { getContentDocument } = loader.require('sdk/panel/utils'); + + let contextmenu = getMostRecentBrowserWindow(). + document.getElementById("contentAreaContextMenu"); + + let panel = Panel({contextMenu: true}); + + panel.show(); + + yield wait(panel, 'show'); + + let view = getActiveView(panel); + let window = getContentDocument(view).defaultView; + + let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + + yield ready(window); + + assert.equal(contextmenu.state, 'closed', + 'contextmenu must be closed'); + + sendMouseEvent('contextmenu', 20, 20, 2, 1, 0); + + yield wait(contextmenu, 'popupshown'); + + assert.equal(contextmenu.state, 'open', + 'contextmenu is opened'); + + contextmenu.hidePopup(); + + loader.unload(); +} + +exports['test panel contextmenu disabled'] = function*(assert) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel'); + const { getActiveView } = loader.require('sdk/view/core'); + const { getContentDocument } = loader.require('sdk/panel/utils'); + + let contextmenu = getMostRecentBrowserWindow(). + document.getElementById("contentAreaContextMenu"); + let listener = () => assert.fail('popupshown should never be called'); + + let panel = Panel(); + + panel.show(); + + yield wait(panel, 'show'); + + let view = getActiveView(panel); + let window = getContentDocument(view).defaultView; + + let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + + yield ready(window); + + assert.equal(contextmenu.state, 'closed', + 'contextmenu must be closed'); + + sendMouseEvent('contextmenu', 20, 20, 2, 1, 0); + + contextmenu.addEventListener('popupshown', listener); + + yield wait(1000); + + contextmenu.removeEventListener('popupshown', listener); + + assert.equal(contextmenu.state, 'closed', + 'contextmenu was never open'); + + loader.unload(); +} + +exports["test panel addon global object"] = function*(assert) { + const { merge } = require("sdk/util/object"); + + let loader = Loader(module, null, null, { + modules: { + "sdk/self": merge({}, self, { + data: merge({}, self.data, {url: fixtures.url}) + }) + } + }); + + const { Panel } = loader.require('sdk/panel'); + + let panel = Panel({ + contentURL: "./test-trusted-document.html" + }); + + panel.show(); + + yield wait(panel, "show"); + + panel.port.emit('addon-to-document', 'ok'); + + yield wait(panel.port, "document-to-addon"); + + assert.pass("Received an event from the document"); + + loader.unload(); +} + +exports["test panel load doesn't show"] = function*(assert) { + let loader = Loader(module); + + let panel = loader.require("sdk/panel").Panel({ + contentScript: "addEventListener('load', function(event) { self.postMessage('load'); });", + contentScriptWhen: "start", + contentURL: "data:text/html;charset=utf-8,", + }); + + let shown = defer(); + let messaged = defer(); + + panel.once("show", function() { + shown.resolve(); + }); + + panel.once("message", function() { + messaged.resolve(); + }); + + panel.show(); + yield all([shown.promise, messaged.promise]); + assert.ok(true, "Saw panel display"); + + panel.on("show", function() { + assert.fail("Should not have seen another show event") + }); + + messaged = defer(); + panel.once("message", function() { + assert.ok(true, "Saw panel reload"); + messaged.resolve(); + }); + + panel.contentURL = "data:text/html;charset=utf-8,<html/>"; + + yield messaged.promise; + loader.unload(); +} + +exports["test Panel without contentURL and contentScriptWhen=start should show"] = function*(assert) { + let loader = Loader(module); + + let panel = loader.require("sdk/panel").Panel({ + contentScriptWhen: "start", + // No contentURL, the bug only shows up when contentURL is not explicitly set. + }); + + yield new Promise(resolve => { + panel.once("show", resolve); + panel.show(); + }); + + assert.pass("Received show event"); + + loader.unload(); +} + +exports["test Panel links"] = function*(assert) { + const loader = Loader(module); + + const { Panel } = loader.require('sdk/panel'); + const { getActiveView } = loader.require('sdk/view/core'); + const tabs = loader.require('sdk/tabs'); + + const synthesizeClick = (panel, options) => { + let { contentWindow } = getActiveView(panel).querySelector('iframe'); + let event = new contentWindow.MouseEvent('click', options); + + contentWindow.document.querySelector('a').dispatchEvent(event); + } + + const linkURL = 'data:text/html;charset=utf-8,' + + encodeURIComponent('<html><a href="#">foo</a></html>'); + + const contentURL = 'data:text/html;charset=utf-8,' + + encodeURIComponent(`<html><a href="${linkURL}">page</a></html>`); + + let panel = Panel({ + contentURL, + contentScript: Isolate(() => self.postMessage(document.URL)) + }); + + panel.show(); + + let url = yield wait(panel, 'message'); + + assert.equal(url, contentURL, + 'content URL loaded'); + + synthesizeClick(panel, { bubbles: true }); + + url = yield wait(panel, 'message'); + + assert.equal(url, linkURL, + 'link URL loaded in the panel after click'); + + synthesizeClick(panel, { + bubbles: true, + [platform === 'darwin' ? 'metaKey' : 'ctrlKey']: true + }); + + let tab = yield wait(tabs, 'ready'); + + assert.equal(tab.url, linkURL + '#', + 'link URL loaded in a new tab after click + accel'); + + loader.unload(); +} + +exports["test Panel script allow property"] = function*(assert) { + const loader = Loader(module); + const { Panel } = loader.require('sdk/panel'); + const { getActiveView } = loader.require('sdk/view/core'); + + const contentURL = 'data:text/html;charset=utf-8,' + + encodeURIComponent(`<body onclick='postMessage("got script click", "*")'> + <script> + document.body.appendChild(document.createElement('unwanted')) + </script> + </body>`); + let panel = Panel({ + contentURL, + allow: {script: false}, + }); + + panel.show(); + + yield wait(panel, 'show'); + + let { contentWindow } = getActiveView(panel).querySelector('iframe'); + + assert.equal(contentWindow.document.body.lastElementChild.localName, "script", + "Script should not have executed"); + + panel.allow.script = true; + + let p = wait(contentWindow, "message"); + let event = new contentWindow.MouseEvent('click', {}); + contentWindow.document.body.dispatchEvent(event); + + let msg = yield p; + assert.equal(msg.data, "got script click", "Should have seen script click"); + + loader.unload(); +}; + +after(exports, function*(name, assert) { + yield cleanUI(); + assert.pass("ui was cleaned."); +}); + +if (isTravisCI) { + module.exports = { + "test skip on jpm": (assert) => assert.pass("skipping this file with jpm") + }; +} + +require("sdk/test").run(exports); |