/* 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 widgets = require("sdk/widget");
const { Cc, Ci, Cu } = require("chrome");
const { Loader } = require('sdk/test/loader');
const url = require("sdk/url");
const timer = require("sdk/timers");
const self = require("sdk/self");
const windowUtils = require("sdk/deprecated/window-utils");
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { close, open, focus } = require("sdk/window/helpers");
const tabs = require("sdk/tabs/utils");
const { merge } = require("sdk/util/object");
const unload = require("sdk/system/unload");
const fixtures = require("./fixtures");
let jetpackID = "testID";
try {
jetpackID = require("sdk/self").id;
} catch(e) {}
const australis = !!require("sdk/window/utils").getMostRecentBrowserWindow().CustomizableUI;
function openNewWindowTab(url, options) {
return open('chrome://browser/content/browser.xul', {
features: {
chrome: true,
toolbar: true
}
}).then(focus).then(function(window) {
if (options.onLoad) {
options.onLoad({ target: { defaultView: window } })
}
return newTab;
});
}
exports.testConstructor = function(assert, done) {
let browserWindow = windowUtils.activeBrowserWindow;
let doc = browserWindow.document;
let AddonsMgrListener;
if (australis) {
AddonsMgrListener = {
onInstalling: () => {},
onInstalled: () => {},
onUninstalling: () => {},
onUninstalled: () => {}
};
}
else {
AddonsMgrListener = browserWindow.AddonsMgrListener;
}
function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
function getWidgets() container() ? container().querySelectorAll('[id^="widget\:"]') : [];
function widgetCount() getWidgets().length;
let widgetStartCount = widgetCount();
function widgetNode(index) getWidgets()[index];
// Test basic construct/destroy
AddonsMgrListener.onInstalling();
let w = widgets.Widget({ id: "basic-construct-destroy", label: "foo", content: "bar" });
AddonsMgrListener.onInstalled();
assert.equal(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
// test widget height
assert.equal(widgetNode(0).firstChild.boxObject.height, 16, "widget has correct default height");
AddonsMgrListener.onUninstalling();
w.destroy();
AddonsMgrListener.onUninstalled();
w.destroy();
assert.pass("Multiple destroys do not cause an error");
assert.equal(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
// Test automatic widget destroy on unload
let loader = Loader(module);
let widgetsFromLoader = loader.require("sdk/widget");
let widgetStartCount = widgetCount();
let w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
assert.equal(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
loader.unload();
assert.equal(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
// Test nothing
assert.throws(
function() widgets.Widget({}),
/^The widget must have a non-empty label property\.$/,
"throws on no properties");
// Test no label
assert.throws(
function() widgets.Widget({content: "foo"}),
/^The widget must have a non-empty label property\.$/,
"throws on no label");
// Test empty label
assert.throws(
function() widgets.Widget({label: "", content: "foo"}),
/^The widget must have a non-empty label property\.$/,
"throws on empty label");
// Test no content or image
assert.throws(
function() widgets.Widget({id: "no-content-throws", label: "foo"}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on no content");
// Test empty content, no image
assert.throws(
function() widgets.Widget({id:"empty-content-throws", label: "foo", content: ""}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on empty content");
// Test empty image, no content
assert.throws(
function() widgets.Widget({id:"empty-image-throws", label: "foo", image: ""}),
/^No content or contentURL property found\. Widgets must have one or the other\.$/,
"throws on empty content");
// Test empty content, empty image
assert.throws(
function() widgets.Widget({id:"empty-image-and-content-throws", label: "foo", content: "", image: ""}),
/^No content or contentURL property found. Widgets must have one or the other\.$/,
"throws on empty content");
// Test duplicated ID
let duplicateID = widgets.Widget({id: "foo", label: "foo", content: "bar"});
assert.throws(
function() widgets.Widget({id: "foo", label: "bar", content: "bar"}),
/^This widget ID is already used: foo$/,
"throws on duplicated id");
duplicateID.destroy();
// Test Bug 652527
assert.throws(
function() widgets.Widget({id: "", label: "bar", content: "bar"}),
/^You have to specify a unique value for the id property of your widget in order for the application to remember its position\./,
"throws on falsey id");
// Test duplicate label, different ID
let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"});
let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"});
w1.destroy();
w2.destroy();
// Test position restore on create/destroy/create
// Create 3 ordered widgets
let w1 = widgets.Widget({id: "position-first", label:"first", content: "bar"});
let w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
let w3 = widgets.Widget({id: "position-third", label:"third", content: "bar"});
// Remove the middle widget
assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
w2.destroy();
assert.equal(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
// Cleanup this testcase
AddonsMgrListener.onUninstalling();
w1.destroy();
w2.destroy();
w3.destroy();
AddonsMgrListener.onUninstalled();
// Test concurrent widget module instances on addon-bar hiding
if (!australis) {
let loader = Loader(module);
let anotherWidgetsInstance = loader.require("sdk/widget");
assert.ok(container().collapsed, "UI is hidden when no widgets");
AddonsMgrListener.onInstalling();
let w1 = widgets.Widget({id: "ui-unhide", label: "foo", content: "bar"});
// Ideally we would let AddonsMgrListener display the addon bar
// But, for now, addon bar is immediatly displayed by sdk code
// https://bugzilla.mozilla.org/show_bug.cgi?id=627484
assert.ok(!container().collapsed, "UI is already visible when we just added the widget");
AddonsMgrListener.onInstalled();
assert.ok(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
let w2 = anotherWidgetsInstance.Widget({id: "ui-stay-open", label: "bar", content: "foo"});
assert.ok(!container().collapsed, "UI still visible when we add a second widget");
AddonsMgrListener.onUninstalling();
w1.destroy();
AddonsMgrListener.onUninstalled();
assert.ok(!container().collapsed, "UI still visible when we remove one of two widgets");
AddonsMgrListener.onUninstalling();
w2.destroy();
assert.ok(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
AddonsMgrListener.onUninstalled();
assert.ok(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
}
// Helper for testing a single widget.
// Confirms proper addition and content setup.
function testSingleWidget(widgetOptions) {
// We have to display which test is being run, because here we do not
// use the regular test framework but rather a custom one that iterates
// the `tests` array.
console.info("executing: " + widgetOptions.id);
let startCount = widgetCount();
let widget = widgets.Widget(widgetOptions);
let node = widgetNode(startCount);
assert.ok(node, "widget node at index");
assert.equal(node.tagName, "toolbaritem", "widget element is correct");
assert.equal(widget.width + "px", node.style.minWidth, "widget width is correct");
assert.equal(widgetCount(), startCount + 1, "container has correct number of child elements");
let content = node.firstElementChild;
assert.ok(content, "found content");
assert.ok(/iframe|image/.test(content.tagName), "content is iframe or image");
return widget;
}
// Array of widgets to test
// and a function to test them.
let tests = [];
function nextTest() {
assert.equal(widgetCount(), 0, "widget in last test property cleaned itself up");
if (!tests.length)
done();
else
timer.setTimeout(tests.shift(), 0);
}
function doneTest() nextTest();
// text widget
tests.push(function testTextWidget() testSingleWidget({
id: "text-single",
label: "text widget",
content: "oh yeah",
contentScript: "self.postMessage(document.body.innerHTML);",
contentScriptWhen: "end",
onMessage: function (message) {
assert.equal(this.content, message, "content matches");
this.destroy();
doneTest();
}
}));
// html widget
tests.push(function testHTMLWidget() testSingleWidget({
id: "html",
label: "html widget",
content: "
oh yeah
",
contentScript: "self.postMessage(document.body.innerHTML);",
contentScriptWhen: "end",
onMessage: function (message) {
assert.equal(this.content, message, "content matches");
this.destroy();
doneTest();
}
}));
// image url widget
tests.push(function testImageURLWidget() testSingleWidget({
id: "image",
label: "image url widget",
contentURL: fixtures.url("test.html"),
contentScript: "self.postMessage({title: document.title, " +
"tag: document.body.firstElementChild.tagName, " +
"content: document.body.firstElementChild.innerHTML});",
contentScriptWhen: "end",
onMessage: function (message) {
assert.equal(message.title, "foo", "title matches");
assert.equal(message.tag, "P", "element matches");
assert.equal(message.content, "bar", "element content matches");
this.destroy();
doneTest();
}
}));
// web uri widget
tests.push(function testWebURIWidget() testSingleWidget({
id: "web",
label: "web uri widget",
contentURL: fixtures.url("test.html"),
contentScript: "self.postMessage({title: document.title, " +
"tag: document.body.firstElementChild.tagName, " +
"content: document.body.firstElementChild.innerHTML});",
contentScriptWhen: "end",
onMessage: function (message) {
assert.equal(message.title, "foo", "title matches");
assert.equal(message.tag, "P", "element matches");
assert.equal(message.content, "bar", "element content matches");
this.destroy();
doneTest();
}
}));
// event: onclick + content
tests.push(function testOnclickEventContent() testSingleWidget({
id: "click-content",
label: "click test widget - content",
content: "foo
",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
"document.getElementById('me').dispatchEvent(evt);",
contentScriptWhen: "end",
onClick: function() {
assert.pass("onClick called");
this.destroy();
doneTest();
}
}));
// event: onmouseover + content
tests.push(function testOnmouseoverEventContent() testSingleWidget({
id: "mouseover-content",
label: "mouseover test widget - content",
content: "foo
",
contentScript: "var evt = new MouseEvent('mouseover'); " +
"document.getElementById('me').dispatchEvent(evt);",
contentScriptWhen: "end",
onMouseover: function() {
assert.pass("onMouseover called");
this.destroy();
doneTest();
}
}));
// event: onmouseout + content
tests.push(function testOnmouseoutEventContent() testSingleWidget({
id: "mouseout-content",
label: "mouseout test widget - content",
content: "foo
",
contentScript: "var evt = new MouseEvent('mouseout');" +
"document.getElementById('me').dispatchEvent(evt);",
contentScriptWhen: "end",
onMouseout: function() {
assert.pass("onMouseout called");
this.destroy();
doneTest();
}
}));
// event: onclick + image
tests.push(function testOnclickEventImage() testSingleWidget({
id: "click-image",
label: "click test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('click'); " +
"document.body.firstElementChild.dispatchEvent(evt);",
contentScriptWhen: "end",
onClick: function() {
assert.pass("onClick called");
this.destroy();
doneTest();
}
}));
// event: onmouseover + image
tests.push(function testOnmouseoverEventImage() testSingleWidget({
id: "mouseover-image",
label: "mouseover test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('mouseover');" +
"document.body.firstElementChild.dispatchEvent(evt);",
contentScriptWhen: "end",
onMouseover: function() {
assert.pass("onMouseover called");
this.destroy();
doneTest();
}
}));
// event: onmouseout + image
tests.push(function testOnmouseoutEventImage() testSingleWidget({
id: "mouseout-image",
label: "mouseout test widget - image",
contentURL: fixtures.url("moz_favicon.ico"),
contentScript: "var evt = new MouseEvent('mouseout'); " +
"document.body.firstElementChild.dispatchEvent(evt);",
contentScriptWhen: "end",
onMouseout: function() {
assert.pass("onMouseout called");
this.destroy();
doneTest();
}
}));
// test multiple widgets
tests.push(function testMultipleWidgets() {
let w1 = widgets.Widget({id: "first", label: "first widget", content: "first content"});
let w2 = widgets.Widget({id: "second", label: "second widget", content: "second content"});
w1.destroy();
w2.destroy();
doneTest();
});
// test updating widget content
let loads = 0;
tests.push(function testUpdatingWidgetContent() testSingleWidget({
id: "content-updating",
label: "content update test widget",
content: "foo
",
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
if (!this.flag) {
this.content = "bar
";
this.flag = 1;
}
else {
assert.equal(this.content, "bar
", 'content is as expected');
this.destroy();
doneTest();
}
}
}));
// test updating widget contentURL
let url1 = "data:text/html;charset=utf-8,foodle";
let url2 = "data:text/html;charset=utf-8,nistel";
tests.push(function testUpdatingContentURL() testSingleWidget({
id: "content-url-updating",
label: "content update test widget",
contentURL: url1,
contentScript: "self.postMessage(document.location.href);",
contentScriptWhen: "end",
onMessage: function(message) {
if (!this.flag) {
assert.equal(this.contentURL.toString(), url1);
assert.equal(message, url1);
this.contentURL = url2;
this.flag = 1;
}
else {
assert.equal(this.contentURL.toString(), url2);
assert.equal(message, url2);
this.destroy();
doneTest();
}
}
}));
// test tooltip
tests.push(function testTooltip() testSingleWidget({
id: "text-with-tooltip",
label: "text widget",
content: "oh yeah",
tooltip: "foo",
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
assert.equal(this.tooltip, "foo", "tooltip matches");
this.destroy();
doneTest();
}
}));
// test tooltip fallback to label
tests.push(function testTooltipFallback() testSingleWidget({
id: "fallback",
label: "fallback",
content: "oh yeah",
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
assert.equal(this.tooltip, this.label, "tooltip fallbacks to label");
this.destroy();
doneTest();
}
}));
// test updating widget tooltip
let updated = false;
tests.push(function testUpdatingTooltip() testSingleWidget({
id: "tooltip-updating",
label: "tooltip update test widget",
tooltip: "foo",
content: "foo
",
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
this.tooltip = "bar";
assert.equal(this.tooltip, "bar", "tooltip gets updated");
this.destroy();
doneTest();
}
}));
// test allow attribute
tests.push(function testDefaultAllow() testSingleWidget({
id: "allow-default",
label: "allow.script attribute",
content: "",
contentScript: "self.postMessage(document.title)",
onMessage: function(message) {
assert.equal(message, "ok", "scripts are evaluated by default");
this.destroy();
doneTest();
}
}));
tests.push(function testExplicitAllow() testSingleWidget({
id: "allow-explicit",
label: "allow.script attribute",
allow: {script: true},
content: "",
contentScript: "self.postMessage(document.title)",
onMessage: function(message) {
assert.equal(message, "ok", "scripts are evaluated when we want to");
this.destroy();
doneTest();
}
}));
tests.push(function testExplicitDisallow() testSingleWidget({
id: "allow-explicit-disallow",
label: "allow.script attribute",
content: "",
allow: {script: false},
contentScript: "self.postMessage(document.title)",
onMessage: function(message) {
assert.notEqual(message, "ok", "scripts aren't evaluated when " +
"explicitly blocked it");
this.destroy();
doneTest();
}
}));
// test multiple windows
tests.push(function testMultipleWindows() {
console.log('executing test multiple windows');
openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
let browserWindow = e.target.defaultView;
assert.ok(browserWindow, 'window was opened');
let doc = browserWindow.document;
function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
function widgetCount2() container() ? container().querySelectorAll('[id^="widget\:"]').length : 0;
let widgetStartCount2 = widgetCount2();
let w1Opts = {id:"first-multi-window", label: "first widget", content: "first content"};
let w1 = testSingleWidget(w1Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
let w2Opts = {id:"second-multi-window", label: "second widget", content: "second content"};
let w2 = testSingleWidget(w2Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget");
w1.destroy();
assert.equal(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first destroy");
w2.destroy();
assert.equal(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy");
close(browserWindow).then(doneTest);
}});
});
// test window closing
tests.push(function testWindowClosing() {
// 1/ Create a new widget
let w1Opts = {
id:"first-win-closing",
label: "first widget",
content: "first content",
contentScript: "self.port.on('event', function () self.port.emit('event'))"
};
let widget = testSingleWidget(w1Opts);
let windows = require("sdk/windows").browserWindows;
// 2/ Retrieve a WidgetView for the initial browser window
let acceptDetach = false;
let mainView = widget.getView(windows.activeWindow);
assert.ok(mainView, "Got first widget view");
mainView.on("detach", function () {
// 8/ End of our test. Accept detach event only when it occurs after
// widget.destroy()
if (acceptDetach)
doneTest();
else
assert.fail("View on initial window should not be destroyed");
});
mainView.port.on("event", function () {
// 7/ Receive event sent during 6/ and cleanup our test
acceptDetach = true;
widget.destroy();
});
// 3/ First: open a new browser window
windows.open({
url: "about:blank",
onOpen: function(window) {
// 4/ Retrieve a WidgetView for this new window
let view = widget.getView(window);
assert.ok(view, "Got second widget view");
view.port.on("event", function () {
assert.fail("We should not receive event on the detach view");
});
view.on("detach", function () {
// The related view is destroyed
// 6/ Send a custom event
assert.throws(function () {
view.port.emit("event");
},
/^The widget has been destroyed and can no longer be used.$/,
"emit on a destroyed view should throw");
widget.port.emit("event");
});
// 5/ Destroy this window
window.close();
}
});
});
if (false) {
tests.push(function testAddonBarHide() {
// Hide the addon-bar
browserWindow.setToolbarVisibility(container(), false);
assert.ok(container().collapsed,
"1st window starts with an hidden addon-bar");
// Then open a browser window and verify that the addon-bar remains hidden
openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
let browserWindow2 = e.target.defaultView;
let doc2 = browserWindow2.document;
function container2() doc2.getElementById("addon-bar");
function widgetCount2() container2() ? container2().childNodes.length : 0;
let widgetStartCount2 = widgetCount2();
assert.ok(container2().collapsed,
"2nd window starts with an hidden addon-bar");
let w1Opts = {id:"first-addonbar-hide", label: "first widget", content: "first content"};
let w1 = testSingleWidget(w1Opts);
assert.equal(widgetCount2(), widgetStartCount2 + 1,
"2nd window has correct number of child elements after" +
"widget creation");
assert.ok(!container().collapsed, "1st window has a visible addon-bar");
assert.ok(!container2().collapsed, "2nd window has a visible addon-bar");
w1.destroy();
assert.equal(widgetCount2(), widgetStartCount2,
"2nd window has correct number of child elements after" +
"widget destroy");
assert.ok(container().collapsed, "1st window has an hidden addon-bar");
assert.ok(container2().collapsed, "2nd window has an hidden addon-bar");
// Reset addon-bar visibility before exiting this test
browserWindow.setToolbarVisibility(container(), true);
close(browserWindow2).then(doneTest);
}});
});
}
// test widget.width
tests.push(function testWidgetWidth() testSingleWidget({
id: "text-test-width",
label: "test widget.width",
content: "test width",
width: 200,
contentScript: "self.postMessage(1)",
contentScriptWhen: "ready",
onMessage: function(message) {
assert.equal(this.width, 200, 'width is 200');
let node = widgetNode(0);
assert.equal(this.width, node.style.minWidth.replace("px", ""));
assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
this.width = 300;
assert.equal(this.width, node.style.minWidth.replace("px", ""));
assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
this.destroy();
doneTest();
}
}));
// test click handler not respond to right-click
let clickCount = 0;
tests.push(function testNoRightClick() testSingleWidget({
id: "right-click-content",
label: "click test widget - content",
content: "foo
",
contentScript: // Left click
"var evt = new MouseEvent('click', {button: 0});" +
"document.getElementById('me').dispatchEvent(evt); " +
// Middle click
"evt = new MouseEvent('click', {button: 1});" +
"document.getElementById('me').dispatchEvent(evt); " +
// Right click
"evt = new MouseEvent('click', {button: 2});" +
"document.getElementById('me').dispatchEvent(evt); " +
// Mouseover
"evt = new MouseEvent('mouseover');" +
"document.getElementById('me').dispatchEvent(evt);",
contentScriptWhen: "end",
onClick: function() clickCount++,
onMouseover: function() {
assert.equal(clickCount, 1, "only left click was sent to click handler");
this.destroy();
doneTest();
}
}));
// kick off test execution
doneTest();
};
exports.testWidgetWithValidPanel = function(assert, done) {
const widgets = require("sdk/widget");
let widget1 = widgets.Widget({
id: "testWidgetWithValidPanel",
label: "panel widget 1",
content: "foo
",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
"document.body.dispatchEvent(evt);",
contentScriptWhen: "end",
panel: require("sdk/panel").Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
let { document } = getMostRecentBrowserWindow();
let widgetEle = document.getElementById("widget:" + jetpackID + "-" + widget1.id);
let panelEle = document.getElementById('mainPopupSet').lastChild;
// See bug https://bugzilla.mozilla.org/show_bug.cgi?id=859592
assert.equal(panelEle.getAttribute("type"), "arrow", 'the panel is a arrow type');
assert.strictEqual(panelEle.anchorNode, widgetEle, 'the panel is properly anchored to the widget');
widget1.destroy();
assert.pass("panel displayed on click");
done();
}
})
});
};
exports.testWidgetWithInvalidPanel = function(assert) {
const widgets = require("sdk/widget");
assert.throws(
function() {
widgets.Widget({
id: "panel2",
label: "panel widget 2",
panel: {}
});
},
/^The option \"panel\" must be one of the following types: null, undefined, object$/,
"widget.panel must be a Panel object");
};
exports.testPanelWidget3 = function testPanelWidget3(assert, done) {
const widgets = require("sdk/widget");
let onClickCalled = false;
let widget3 = widgets.Widget({
id: "panel3",
label: "panel widget 3",
content: "foo
",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
"document.body.firstElementChild.dispatchEvent(evt);",
contentScriptWhen: "end",
onClick: function() {
onClickCalled = true;
this.panel.show();
},
panel: require("sdk/panel").Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
assert.ok(
onClickCalled,
"onClick called on click for widget with both panel and onClick");
widget3.destroy();
done();
}
})
});
};
exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
let CustomizableUI;
try {
({CustomizableUI}) = Cu.import("resource:///modules/CustomizableUI.jsm", {});
}
catch (e) {
assert.pass("Test skipped: no CustomizableUI object found.");
done();
return;
}
const widgets = require("sdk/widget");
let widget1 = widgets.Widget({
id: "panel1",
label: "panel widget 1",
content: "foo
",
contentScript: "new " + function() {
self.port.on('click', () => {
let evt = new MouseEvent('click', {button: 0});
document.body.dispatchEvent(evt);
});
},
contentScriptWhen: "end",
panel: require("sdk/panel").Panel({
contentURL: "data:text/html;charset=utf-8,Look ma, a panel!",
onShow: function() {
let { document } = getMostRecentBrowserWindow();
let { anchorNode } = document.getElementById('mainPopupSet').lastChild;
let panelButtonNode = document.getElementById("PanelUI-menu-button");
assert.strictEqual(anchorNode, panelButtonNode,
'the panel is anchored to the panel menu button instead of widget');
widget1.destroy();
done();
}
})
});
let widgetId = "widget:" + jetpackID + "-" + widget1.id;
CustomizableUI.addListener({
onWidgetAdded: function(id) {
if (id !== widgetId) return;
let { document, PanelUI } = getMostRecentBrowserWindow();
PanelUI.panel.addEventListener('popupshowing', function onshow({type}) {
this.removeEventListener(type, onshow);
widget1.port.emit('click');
});
document.getElementById("PanelUI-menu-button").click()
}
});
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
};
exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
let origMessage = "foo";
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "widget-messaging",
label: "foo",
content: "baz",
contentScriptWhen: "end",
contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
onMessage: function(message) {
if (message == "ready")
widget.postMessage(origMessage);
else {
assert.equal(origMessage, message);
widget.destroy();
done();
}
}
});
};
exports.testWidgetViews = function testWidgetViews(assert, done) {
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "widget-views",
label: "foo",
content: "baz",
contentScriptWhen: "ready",
contentScript: "self.on('message', function(data) self.postMessage(data)); self.postMessage('ready')",
onAttach: function(view) {
assert.pass("WidgetView created");
view.on("message", function () {
assert.pass("Got message in WidgetView");
widget.destroy();
});
view.on("detach", function () {
assert.pass("WidgetView destroyed");
done();
});
}
});
};
exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done) {
const widgets = require("sdk/widget");
let view = null;
let widget = widgets.Widget({
id: "widget-view-ui-events",
label: "foo",
content: "foo
",
contentScript: "var evt = new MouseEvent('click', {button: 0});" +
"document.getElementById('me').dispatchEvent(evt);",
contentScriptWhen: "ready",
onAttach: function(attachView) {
view = attachView;
assert.pass("Got attach event");
},
onClick: function (eventView) {
assert.equal(view, eventView,
"event first argument is equal to the WidgetView");
let view2 = widget.getView(require("sdk/windows").browserWindows.activeWindow);
assert.equal(view, view2,
"widget.getView return the same WidgetView");
widget.destroy();
done();
}
});
};
exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(assert, done) {
const widgets = require("sdk/widget");
let widget = widgets.Widget({
id: "widget-view-custom-events",
label: "foo",
content: "foo
",
contentScript: "self.port.emit('event', 'ok');",
contentScriptWhen: "ready",
onAttach: function(view) {
view.port.on("event", function (data) {
assert.equal(data, "ok",
"event argument is valid on WidgetView");
});
},
});
widget.port.on("event", function (data) {
assert.equal(data, "ok", "event argument is valid on Widget");
widget.destroy();
done();
});
};
exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(assert, done) {
const widgets = require("sdk/widget");
let widget = new widgets.Widget({
id: "widget-views-tooltip",
label: "foo",
content: "foo"
});
let view = widget.getView(require("sdk/windows").browserWindows.activeWindow);
widget.tooltip = null;
assert.equal(view.tooltip, "foo",
"view tooltip defaults to base widget label");
assert.equal(widget.tooltip, "foo",
"tooltip defaults to base widget label");
widget.destroy();
done();
};
exports.testWidgetMove = function testWidgetMove(assert, done) {
let windowUtils = require("sdk/deprecated/window-utils");
let widgets = require("sdk/widget");
let browserWindow = windowUtils.activeBrowserWindow;
let doc = browserWindow.document;
let label = "unique-widget-label";
let origMessage = "message after node move";
let gotFirstReady = false;
let widget = widgets.Widget({
id: "widget-move",
label: label,
content: "baz",
contentScriptWhen: "ready",
contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
onMessage: function(message) {
if (message == "ready") {
if (!gotFirstReady) {
assert.pass("Got first ready event");
let widgetNode = doc.querySelector('toolbaritem[label="' + label + '"]');
let parent = widgetNode.parentNode;
parent.insertBefore(widgetNode, parent.firstChild);
gotFirstReady = true;
}
else {
assert.pass("Got second ready event");
widget.postMessage(origMessage);
}
}
else {
assert.equal(origMessage, message, "Got message after node move");
widget.destroy();
done();
}
}
});
};
/*
The bug is exhibited when a widget with HTML content has it's content
changed to new HTML content with a pound in it. Because the src of HTML
content is converted to a data URI, the underlying iframe doesn't
consider the content change a navigation change, so doesn't load
the new content.
*/
exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
function getWidgetContent(widget) {
let windowUtils = require("sdk/deprecated/window-utils");
let browserWindow = windowUtils.activeBrowserWindow;
let doc = browserWindow.document;
let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]');
assert.ok(widgetNode, 'found widget node in the front-end');
return widgetNode.firstChild.contentDocument.body.innerHTML;
}
let widgets = require("sdk/widget");
let count = 0;
let widget = widgets.Widget({
id: "1",
label: "foo",
content: "foo",
contentScript: "window.addEventListener('load', self.postMessage, false);",
onMessage: function() {
count++;
if (count == 1) {
widget.content = "foo#";
}
else {
assert.equal(getWidgetContent(widget), "foo#", "content updated to pound?");
widget.destroy();
done();
}
}
});
};
exports.testContentScriptOptionsOption = function(assert, done) {
let widget = require("sdk/widget").Widget({
id: "widget-script-options",
label: "fooz",
content: "fooz",
contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
contentScriptWhen: "end",
contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
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' );
widget.destroy();
done();
}
});
};
exports.testOnAttachWithoutContentScript = function(assert, done) {
let widget = require("sdk/widget").Widget({
id: "onAttachNoCS",
label: "onAttachNoCS",
content: "onAttachNoCS",
onAttach: function (view) {
assert.pass("received attach event");
widget.destroy();
done();
}
});
};
exports.testPostMessageOnAttach = function(assert, done) {
let widget = require("sdk/widget").Widget({
id: "onAttach",
label: "onAttach",
content: "onAttach",
// 1) Send a message immediatly after `attach` event
onAttach: function (view) {
view.postMessage("ok");
},
// 2) Listen to it and forward it back to the widget
contentScript: "self.on('message', self.postMessage);",
// 3) Listen to this forwarded message
onMessage: function (msg) {
assert.equal( msg, "ok", "postMessage works on `attach` event");
widget.destroy();
done();
}
});
};
exports.testPostMessageOnLocationChange = function(assert, done) {
let attachEventCount = 0;
let messagesCount = 0;
let widget = require("sdk/widget").Widget({
id: "onLocationChange",
label: "onLocationChange",
content: "onLocationChange",
contentScript: "new " + function ContentScriptScope() {
// Emit an event when content script is applied in order to know when
// the first document is loaded so that we can load the 2nd one
self.postMessage("ready");
// And forward any incoming message back to the widget to see if
// messaging is working on 2nd document
self.on("message", self.postMessage);
},
onMessage: function (msg) {
messagesCount++;
if (messagesCount == 1) {
assert.equal(msg, "ready", "First document is loaded");
widget.content = "location changed";
}
else if (messagesCount == 2) {
assert.equal(msg, "ready", "Second document is loaded");
widget.postMessage("ok");
}
else if (messagesCount == 3) {
assert.equal(msg, "ok",
"We receive the message sent to the 2nd document");
widget.destroy();
done();
}
}
});
};
exports.testSVGWidget = function(assert, done) {
// use of capital SVG here is intended, that was failing..
let SVG_URL = fixtures.url("mofo_logo.SVG");
let widget = require("sdk/widget").Widget({
id: "mozilla-svg-logo",
label: "moz foundation logo",
contentURL: SVG_URL,
contentScript: "self.postMessage({count: window.document.images.length, src: window.document.images[0].src});",
onMessage: function(data) {
widget.destroy();
assert.equal(data.count, 1, 'only one image');
assert.equal(data.src, SVG_URL, 'only one image');
done();
}
});
};
exports.testReinsertion = function(assert, done) {
const WIDGETID = "test-reinsertion";
let windowUtils = require("sdk/deprecated/window-utils");
let browserWindow = windowUtils.activeBrowserWindow;
let widget = require("sdk/widget").Widget({
id: "test-reinsertion",
label: "test reinsertion",
content: "Test",
});
let realWidgetId = "widget:" + jetpackID + "-" + WIDGETID;
// Remove the widget:
if (australis) {
browserWindow.CustomizableUI.removeWidgetFromArea(realWidgetId);
} else {
let widget = browserWindow.document.getElementById(realWidgetId);
let container = widget.parentNode;
container.currentSet = container.currentSet.replace("," + realWidgetId, "");
container.setAttribute("currentset", container.currentSet);
container.ownerDocument.persist(container.id, "currentset");
}
openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
assert.equal(e.target.defaultView.document.getElementById(realWidgetId), null);
close(e.target.defaultView).then(done);
}});
};
if (!australis) {
exports.testNavigationBarWidgets = function testNavigationBarWidgets(assert, done) {
let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
// First wait for all 3 widgets to be added to the current browser window
let firstAttachCount = 0;
function onAttachFirstWindow(widget) {
if (++firstAttachCount<3)
return;
onWidgetsReady();
}
w1.once("attach", onAttachFirstWindow);
w2.once("attach", onAttachFirstWindow);
w3.once("attach", onAttachFirstWindow);
function getWidgetNode(toolbar, position) {
return toolbar.getElementsByTagName("toolbaritem")[position];
}
function openBrowserWindow() {
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
let urlString = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
urlString.data = "about:blank";
return ww.openWindow(null, "chrome://browser/content/browser.xul",
"_blank", "chrome,all,dialog=no", urlString);
}
// Then move them before openeing a new browser window
function onWidgetsReady() {
// Hack to move 2nd and 3rd widgets manually to the navigation bar right after
// the search box.
let browserWindow = windowUtils.activeBrowserWindow;
let doc = browserWindow.document;
let addonBar = doc.getElementById("addon-bar");
let w2ToolbarItem = getWidgetNode(addonBar, 1);
let w3ToolbarItem = getWidgetNode(addonBar, 2);
let navBar = doc.getElementById("nav-bar");
let searchBox = doc.getElementById("search-container");
// Insert 3rd at the right of search box by adding it before its right sibling
navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
// Then insert 2nd before 3rd
navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false);
// Widget and Firefox codes rely on this `currentset` attribute,
// so ensure it is correctly saved
navBar.setAttribute("currentset", navBar.currentSet);
doc.persist(navBar.id, "currentset");
// Update addonbar too as we removed widget from there.
// Otherwise, widgets may still be added to this toolbar.
addonBar.setAttribute("currentset", addonBar.currentSet);
doc.persist(addonBar.id, "currentset");
// Wait for all widget to be attached to this new window before checking
// their position
let attachCount = 0;
let browserWindow2;
function onAttach(widget) {
if (++attachCount < 3)
return;
let doc = browserWindow2.document;
let addonBar = doc.getElementById("addon-bar");
let searchBox = doc.getElementById("search-container");
// Ensure that 1st is in addon bar
assert.equal(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
// And that 2nd and 3rd keep their original positions in navigation bar,
// i.e. right after search box
assert.equal(searchBox.nextSibling.getAttribute("label"), w2.label);
assert.equal(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
w1.destroy();
w2.destroy();
w3.destroy();
close(browserWindow2).then(done);
}
w1.on("attach", onAttach);
w2.on("attach", onAttach);
w3.on("attach", onAttach);
browserWindow2 = openBrowserWindow(browserWindow);
}
};
}
require("sdk/test").run(exports);