Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 'use strict';
6 module.metadata = {
7 'engines': {
8 'Firefox': '*'
9 }
10 };
12 const { Cc, Ci, Cu } = require("chrome");
13 const { LoaderWithHookedConsole } = require('sdk/test/loader');
14 const url = require("sdk/url");
15 const timer = require("sdk/timers");
16 const self = require("sdk/self");
17 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
18 const { close, open, focus } = require("sdk/window/helpers");
19 const tabs = require("sdk/tabs/utils");
20 const { merge } = require("sdk/util/object");
21 const unload = require("sdk/system/unload");
22 const fixtures = require("./fixtures");
24 let jetpackID = "testID";
25 try {
26 jetpackID = require("sdk/self").id;
27 } catch(e) {}
29 function openNewWindowTab(url, options) {
30 return open('chrome://browser/content/browser.xul', {
31 features: {
32 chrome: true,
33 toolbar: true
34 }
35 }).then(focus).then(function(window) {
36 if (options.onLoad) {
37 options.onLoad({ target: { defaultView: window } })
38 }
40 return newTab;
41 });
42 }
44 exports.testDeprecationMessage = function(assert, done) {
45 let { loader } = LoaderWithHookedConsole(module, onMessage);
47 // Intercept all console method calls
48 let calls = [];
49 function onMessage(type, msg) {
50 assert.equal(type, 'error', 'the only message is an error');
51 assert.ok(/^DEPRECATED:/.test(msg), 'deprecated');
52 loader.unload();
53 done();
54 }
55 loader.require('sdk/widget');
56 }
58 exports.testConstructor = function(assert, done) {
59 let { loader: loader0 } = LoaderWithHookedConsole(module);
60 const widgets = loader0.require("sdk/widget");
61 let browserWindow = getMostRecentBrowserWindow();
62 let doc = browserWindow.document;
63 let AddonsMgrListener;
65 AddonsMgrListener = {
66 onInstalling: () => {},
67 onInstalled: () => {},
68 onUninstalling: () => {},
69 onUninstalled: () => {}
70 };
72 let container = () => doc.getElementById("nav-bar");
73 let getWidgets = () => container() ? container().querySelectorAll('[id^="widget\:"]') : [];
74 let widgetCount = () => getWidgets().length;
75 let widgetStartCount = widgetCount();
76 let widgetNode = (index) => getWidgets()[index];
78 // Test basic construct/destroy
79 AddonsMgrListener.onInstalling();
80 let w = widgets.Widget({ id: "basic-construct-destroy", label: "foo", content: "bar" });
81 AddonsMgrListener.onInstalled();
82 assert.equal(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
84 // test widget height
85 assert.equal(widgetNode(0).firstChild.boxObject.height, 16, "widget has correct default height");
87 AddonsMgrListener.onUninstalling();
88 w.destroy();
89 AddonsMgrListener.onUninstalled();
90 w.destroy();
91 assert.pass("Multiple destroys do not cause an error");
92 assert.equal(widgetCount(), widgetStartCount, "panel has correct number of child elements after destroy");
94 // Test automatic widget destroy on unload
95 let { loader } = LoaderWithHookedConsole(module);
96 let widgetsFromLoader = loader.require("sdk/widget");
97 let widgetStartCount = widgetCount();
98 let w = widgetsFromLoader.Widget({ id: "destroy-on-unload", label: "foo", content: "bar" });
99 assert.equal(widgetCount(), widgetStartCount + 1, "widget has been correctly added");
100 loader.unload();
101 assert.equal(widgetCount(), widgetStartCount, "widget has been destroyed on module unload");
103 // Test nothing
104 assert.throws(
105 function() widgets.Widget({}),
106 /^The widget must have a non-empty label property\.$/,
107 "throws on no properties");
109 // Test no label
110 assert.throws(
111 function() widgets.Widget({content: "foo"}),
112 /^The widget must have a non-empty label property\.$/,
113 "throws on no label");
115 // Test empty label
116 assert.throws(
117 function() widgets.Widget({label: "", content: "foo"}),
118 /^The widget must have a non-empty label property\.$/,
119 "throws on empty label");
121 // Test no content or image
122 assert.throws(
123 function() widgets.Widget({id: "no-content-throws", label: "foo"}),
124 /^No content or contentURL property found\. Widgets must have one or the other\.$/,
125 "throws on no content");
127 // Test empty content, no image
128 assert.throws(
129 function() widgets.Widget({id:"empty-content-throws", label: "foo", content: ""}),
130 /^No content or contentURL property found\. Widgets must have one or the other\.$/,
131 "throws on empty content");
133 // Test empty image, no content
134 assert.throws(
135 function() widgets.Widget({id:"empty-image-throws", label: "foo", image: ""}),
136 /^No content or contentURL property found\. Widgets must have one or the other\.$/,
137 "throws on empty content");
139 // Test empty content, empty image
140 assert.throws(
141 function() widgets.Widget({id:"empty-image-and-content-throws", label: "foo", content: "", image: ""}),
142 /^No content or contentURL property found. Widgets must have one or the other\.$/,
143 "throws on empty content");
145 // Test duplicated ID
146 let duplicateID = widgets.Widget({id: "foo", label: "foo", content: "bar"});
147 assert.throws(
148 function() widgets.Widget({id: "foo", label: "bar", content: "bar"}),
149 /^This widget ID is already used: foo$/,
150 "throws on duplicated id");
151 duplicateID.destroy();
153 // Test Bug 652527
154 assert.throws(
155 function() widgets.Widget({id: "", label: "bar", content: "bar"}),
156 /^You have to specify a unique value for the id property of your widget in order for the application to remember its position\./,
157 "throws on falsey id");
159 // Test duplicate label, different ID
160 let w1 = widgets.Widget({id: "id1", label: "foo", content: "bar"});
161 let w2 = widgets.Widget({id: "id2", label: "foo", content: "bar"});
162 w1.destroy();
163 w2.destroy();
165 // Test position restore on create/destroy/create
166 // Create 3 ordered widgets
167 let w1 = widgets.Widget({id: "position-first", label:"first", content: "bar"});
168 let w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
169 let w3 = widgets.Widget({id: "position-third", label:"third", content: "bar"});
170 // Remove the middle widget
171 assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is the second widget inserted");
172 w2.destroy();
173 assert.equal(widgetNode(1).getAttribute("label"), "third", "second widget is removed, so second widget is now the third one");
174 w2 = widgets.Widget({id: "position-second", label:"second", content: "bar"});
175 assert.equal(widgetNode(1).getAttribute("label"), "second", "second widget is created again, at the same location");
176 // Cleanup this testcase
177 AddonsMgrListener.onUninstalling();
178 w1.destroy();
179 w2.destroy();
180 w3.destroy();
181 AddonsMgrListener.onUninstalled();
183 // Helper for testing a single widget.
184 // Confirms proper addition and content setup.
185 function testSingleWidget(widgetOptions) {
186 // We have to display which test is being run, because here we do not
187 // use the regular test framework but rather a custom one that iterates
188 // the `tests` array.
189 assert.pass("executing: " + widgetOptions.id);
191 let startCount = widgetCount();
192 let widget = widgets.Widget(widgetOptions);
193 let node = widgetNode(startCount);
194 assert.ok(node, "widget node at index");
195 assert.equal(node.tagName, "toolbaritem", "widget element is correct");
196 assert.equal(widget.width + "px", node.style.minWidth, "widget width is correct");
197 assert.equal(widgetCount(), startCount + 1, "container has correct number of child elements");
198 let content = node.firstElementChild;
199 assert.ok(content, "found content");
200 assert.ok(/iframe|image/.test(content.tagName), "content is iframe or image");
201 return widget;
202 }
204 // Array of widgets to test
205 // and a function to test them.
206 let tests = [];
207 function nextTest() {
208 assert.equal(widgetCount(), 0, "widget in last test property cleaned itself up");
209 if (!tests.length) {
210 loader0.unload();
211 done();
212 }
213 else
214 timer.setTimeout(tests.shift(), 0);
215 }
216 function doneTest() nextTest();
218 // text widget
219 tests.push(function testTextWidget() testSingleWidget({
220 id: "text-single",
221 label: "text widget",
222 content: "oh yeah",
223 contentScript: "self.postMessage(document.body.innerHTML);",
224 contentScriptWhen: "end",
225 onMessage: function (message) {
226 assert.equal(this.content, message, "content matches");
227 this.destroy();
228 doneTest();
229 }
230 }));
232 // html widget
233 tests.push(function testHTMLWidget() testSingleWidget({
234 id: "html",
235 label: "html widget",
236 content: "<div>oh yeah</div>",
237 contentScript: "self.postMessage(document.body.innerHTML);",
238 contentScriptWhen: "end",
239 onMessage: function (message) {
240 assert.equal(this.content, message, "content matches");
241 this.destroy();
242 doneTest();
243 }
244 }));
246 // image url widget
247 tests.push(function testImageURLWidget() testSingleWidget({
248 id: "image",
249 label: "image url widget",
250 contentURL: fixtures.url("test.html"),
251 contentScript: "self.postMessage({title: document.title, " +
252 "tag: document.body.firstElementChild.tagName, " +
253 "content: document.body.firstElementChild.innerHTML});",
254 contentScriptWhen: "end",
255 onMessage: function (message) {
256 assert.equal(message.title, "foo", "title matches");
257 assert.equal(message.tag, "P", "element matches");
258 assert.equal(message.content, "bar", "element content matches");
259 this.destroy();
260 doneTest();
261 }
262 }));
264 // web uri widget
265 tests.push(function testWebURIWidget() testSingleWidget({
266 id: "web",
267 label: "web uri widget",
268 contentURL: fixtures.url("test.html"),
269 contentScript: "self.postMessage({title: document.title, " +
270 "tag: document.body.firstElementChild.tagName, " +
271 "content: document.body.firstElementChild.innerHTML});",
272 contentScriptWhen: "end",
273 onMessage: function (message) {
274 assert.equal(message.title, "foo", "title matches");
275 assert.equal(message.tag, "P", "element matches");
276 assert.equal(message.content, "bar", "element content matches");
277 this.destroy();
278 doneTest();
279 }
280 }));
282 // event: onclick + content
283 tests.push(function testOnclickEventContent() testSingleWidget({
284 id: "click-content",
285 label: "click test widget - content",
286 content: "<div id='me'>foo</div>",
287 contentScript: "var evt = new MouseEvent('click', {button: 0});" +
288 "document.getElementById('me').dispatchEvent(evt);",
289 contentScriptWhen: "end",
290 onClick: function() {
291 assert.pass("onClick called");
292 this.destroy();
293 doneTest();
294 }
295 }));
297 // event: onmouseover + content
298 tests.push(function testOnmouseoverEventContent() testSingleWidget({
299 id: "mouseover-content",
300 label: "mouseover test widget - content",
301 content: "<div id='me'>foo</div>",
302 contentScript: "var evt = new MouseEvent('mouseover'); " +
303 "document.getElementById('me').dispatchEvent(evt);",
304 contentScriptWhen: "end",
305 onMouseover: function() {
306 assert.pass("onMouseover called");
307 this.destroy();
308 doneTest();
309 }
310 }));
312 // event: onmouseout + content
313 tests.push(function testOnmouseoutEventContent() testSingleWidget({
314 id: "mouseout-content",
315 label: "mouseout test widget - content",
316 content: "<div id='me'>foo</div>",
317 contentScript: "var evt = new MouseEvent('mouseout');" +
318 "document.getElementById('me').dispatchEvent(evt);",
319 contentScriptWhen: "end",
320 onMouseout: function() {
321 assert.pass("onMouseout called");
322 this.destroy();
323 doneTest();
324 }
325 }));
327 // event: onclick + image
328 tests.push(function testOnclickEventImage() testSingleWidget({
329 id: "click-image",
330 label: "click test widget - image",
331 contentURL: fixtures.url("moz_favicon.ico"),
332 contentScript: "var evt = new MouseEvent('click'); " +
333 "document.body.firstElementChild.dispatchEvent(evt);",
334 contentScriptWhen: "end",
335 onClick: function() {
336 assert.pass("onClick called");
337 this.destroy();
338 doneTest();
339 }
340 }));
342 // event: onmouseover + image
343 tests.push(function testOnmouseoverEventImage() testSingleWidget({
344 id: "mouseover-image",
345 label: "mouseover test widget - image",
346 contentURL: fixtures.url("moz_favicon.ico"),
347 contentScript: "var evt = new MouseEvent('mouseover');" +
348 "document.body.firstElementChild.dispatchEvent(evt);",
349 contentScriptWhen: "end",
350 onMouseover: function() {
351 assert.pass("onMouseover called");
352 this.destroy();
353 doneTest();
354 }
355 }));
357 // event: onmouseout + image
358 tests.push(function testOnmouseoutEventImage() testSingleWidget({
359 id: "mouseout-image",
360 label: "mouseout test widget - image",
361 contentURL: fixtures.url("moz_favicon.ico"),
362 contentScript: "var evt = new MouseEvent('mouseout'); " +
363 "document.body.firstElementChild.dispatchEvent(evt);",
364 contentScriptWhen: "end",
365 onMouseout: function() {
366 assert.pass("onMouseout called");
367 this.destroy();
368 doneTest();
369 }
370 }));
372 // test multiple widgets
373 tests.push(function testMultipleWidgets() {
374 let w1 = widgets.Widget({id: "first", label: "first widget", content: "first content"});
375 let w2 = widgets.Widget({id: "second", label: "second widget", content: "second content"});
377 w1.destroy();
378 w2.destroy();
380 doneTest();
381 });
383 // test updating widget content
384 let loads = 0;
385 tests.push(function testUpdatingWidgetContent() testSingleWidget({
386 id: "content-updating",
387 label: "content update test widget",
388 content: "<div id='me'>foo</div>",
389 contentScript: "self.postMessage(1)",
390 contentScriptWhen: "ready",
391 onMessage: function(message) {
392 if (!this.flag) {
393 this.content = "<div id='me'>bar</div>";
394 this.flag = 1;
395 }
396 else {
397 assert.equal(this.content, "<div id='me'>bar</div>", 'content is as expected');
398 this.destroy();
399 doneTest();
400 }
401 }
402 }));
404 // test updating widget contentURL
405 let url1 = "data:text/html;charset=utf-8,<body>foodle</body>";
406 let url2 = "data:text/html;charset=utf-8,<body>nistel</body>";
408 tests.push(function testUpdatingContentURL() testSingleWidget({
409 id: "content-url-updating",
410 label: "content update test widget",
411 contentURL: url1,
412 contentScript: "self.postMessage(document.location.href);",
413 contentScriptWhen: "end",
414 onMessage: function(message) {
415 if (!this.flag) {
416 assert.equal(this.contentURL.toString(), url1);
417 assert.equal(message, url1);
418 this.contentURL = url2;
419 this.flag = 1;
420 }
421 else {
422 assert.equal(this.contentURL.toString(), url2);
423 assert.equal(message, url2);
424 this.destroy();
425 doneTest();
426 }
427 }
428 }));
430 // test tooltip
431 tests.push(function testTooltip() testSingleWidget({
432 id: "text-with-tooltip",
433 label: "text widget",
434 content: "oh yeah",
435 tooltip: "foo",
436 contentScript: "self.postMessage(1)",
437 contentScriptWhen: "ready",
438 onMessage: function(message) {
439 assert.equal(this.tooltip, "foo", "tooltip matches");
440 this.destroy();
441 doneTest();
442 }
443 }));
445 // test tooltip fallback to label
446 tests.push(function testTooltipFallback() testSingleWidget({
447 id: "fallback",
448 label: "fallback",
449 content: "oh yeah",
450 contentScript: "self.postMessage(1)",
451 contentScriptWhen: "ready",
452 onMessage: function(message) {
453 assert.equal(this.tooltip, this.label, "tooltip fallbacks to label");
454 this.destroy();
455 doneTest();
456 }
457 }));
459 // test updating widget tooltip
460 let updated = false;
461 tests.push(function testUpdatingTooltip() testSingleWidget({
462 id: "tooltip-updating",
463 label: "tooltip update test widget",
464 tooltip: "foo",
465 content: "<div id='me'>foo</div>",
466 contentScript: "self.postMessage(1)",
467 contentScriptWhen: "ready",
468 onMessage: function(message) {
469 this.tooltip = "bar";
470 assert.equal(this.tooltip, "bar", "tooltip gets updated");
471 this.destroy();
472 doneTest();
473 }
474 }));
476 // test allow attribute
477 tests.push(function testDefaultAllow() testSingleWidget({
478 id: "allow-default",
479 label: "allow.script attribute",
480 content: "<script>document.title = 'ok';</script>",
481 contentScript: "self.postMessage(document.title)",
482 onMessage: function(message) {
483 assert.equal(message, "ok", "scripts are evaluated by default");
484 this.destroy();
485 doneTest();
486 }
487 }));
489 tests.push(function testExplicitAllow() testSingleWidget({
490 id: "allow-explicit",
491 label: "allow.script attribute",
492 allow: {script: true},
493 content: "<script>document.title = 'ok';</script>",
494 contentScript: "self.postMessage(document.title)",
495 onMessage: function(message) {
496 assert.equal(message, "ok", "scripts are evaluated when we want to");
497 this.destroy();
498 doneTest();
499 }
500 }));
502 tests.push(function testExplicitDisallow() testSingleWidget({
503 id: "allow-explicit-disallow",
504 label: "allow.script attribute",
505 content: "<script>document.title = 'ok';</script>",
506 allow: {script: false},
507 contentScript: "self.postMessage(document.title)",
508 onMessage: function(message) {
509 assert.notEqual(message, "ok", "scripts aren't evaluated when " +
510 "explicitly blocked it");
511 this.destroy();
512 doneTest();
513 }
514 }));
516 // test multiple windows
517 tests.push(function testMultipleWindows() {
518 assert.pass('executing test multiple windows');
519 openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
520 let browserWindow = e.target.defaultView;
521 assert.ok(browserWindow, 'window was opened');
522 let doc = browserWindow.document;
523 let container = () => doc.getElementById("nav-bar");
524 let widgetCount2 = () => container() ? container().querySelectorAll('[id^="widget\:"]').length : 0;
525 let widgetStartCount2 = widgetCount2();
527 let w1Opts = {id:"first-multi-window", label: "first widget", content: "first content"};
528 let w1 = testSingleWidget(w1Opts);
529 assert.equal(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
531 let w2Opts = {id:"second-multi-window", label: "second widget", content: "second content"};
532 let w2 = testSingleWidget(w2Opts);
533 assert.equal(widgetCount2(), widgetStartCount2 + 2, "2nd window has correct number of child elements after second widget");
535 w1.destroy();
536 assert.equal(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first destroy");
537 w2.destroy();
538 assert.equal(widgetCount2(), widgetStartCount2, "2nd window has correct number of child elements after second destroy");
540 close(browserWindow).then(doneTest);
541 }});
542 });
544 // test window closing
545 tests.push(function testWindowClosing() {
546 // 1/ Create a new widget
547 let w1Opts = {
548 id:"first-win-closing",
549 label: "first widget",
550 content: "first content",
551 contentScript: "self.port.on('event', function () self.port.emit('event'))"
552 };
553 let widget = testSingleWidget(w1Opts);
554 let windows = loader0.require("sdk/windows").browserWindows;
556 // 2/ Retrieve a WidgetView for the initial browser window
557 let acceptDetach = false;
558 let mainView = widget.getView(windows.activeWindow);
559 assert.ok(mainView, "Got first widget view");
560 mainView.on("detach", function () {
561 // 8/ End of our test. Accept detach event only when it occurs after
562 // widget.destroy()
563 if (acceptDetach)
564 doneTest();
565 else
566 assert.fail("View on initial window should not be destroyed");
567 });
568 mainView.port.on("event", function () {
569 // 7/ Receive event sent during 6/ and cleanup our test
570 acceptDetach = true;
571 widget.destroy();
572 });
574 // 3/ First: open a new browser window
575 windows.open({
576 url: "about:blank",
577 onOpen: function(window) {
578 // 4/ Retrieve a WidgetView for this new window
579 let view = widget.getView(window);
580 assert.ok(view, "Got second widget view");
581 view.port.on("event", function () {
582 assert.fail("We should not receive event on the detach view");
583 });
584 view.on("detach", function () {
585 // The related view is destroyed
586 // 6/ Send a custom event
587 assert.throws(function () {
588 view.port.emit("event");
589 },
590 /^The widget has been destroyed and can no longer be used.$/,
591 "emit on a destroyed view should throw");
592 widget.port.emit("event");
593 });
595 // 5/ Destroy this window
596 window.close();
597 }
598 });
599 });
601 if (false) {
602 tests.push(function testAddonBarHide() {
603 // Hide the addon-bar
604 browserWindow.setToolbarVisibility(container(), false);
605 assert.ok(container().collapsed,
606 "1st window starts with an hidden addon-bar");
608 // Then open a browser window and verify that the addon-bar remains hidden
609 openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
610 let browserWindow2 = e.target.defaultView;
611 let doc2 = browserWindow2.document;
612 function container2() doc2.getElementById("addon-bar");
613 function widgetCount2() container2() ? container2().childNodes.length : 0;
614 let widgetStartCount2 = widgetCount2();
615 assert.ok(container2().collapsed,
616 "2nd window starts with an hidden addon-bar");
618 let w1Opts = {id:"first-addonbar-hide", label: "first widget", content: "first content"};
619 let w1 = testSingleWidget(w1Opts);
620 assert.equal(widgetCount2(), widgetStartCount2 + 1,
621 "2nd window has correct number of child elements after" +
622 "widget creation");
623 assert.ok(!container().collapsed, "1st window has a visible addon-bar");
624 assert.ok(!container2().collapsed, "2nd window has a visible addon-bar");
625 w1.destroy();
626 assert.equal(widgetCount2(), widgetStartCount2,
627 "2nd window has correct number of child elements after" +
628 "widget destroy");
630 assert.ok(container().collapsed, "1st window has an hidden addon-bar");
631 assert.ok(container2().collapsed, "2nd window has an hidden addon-bar");
633 // Reset addon-bar visibility before exiting this test
634 browserWindow.setToolbarVisibility(container(), true);
636 close(browserWindow2).then(doneTest);
637 }});
638 });
639 }
641 // test widget.width
642 tests.push(function testWidgetWidth() testSingleWidget({
643 id: "text-test-width",
644 label: "test widget.width",
645 content: "test width",
646 width: 64,
647 contentScript: "self.postMessage(1)",
648 contentScriptWhen: "ready",
649 onMessage: function(message) {
650 assert.equal(this.width, 64, 'width is 64');
652 let node = widgetNode(0);
653 assert.equal(this.width, node.style.minWidth.replace("px", ""));
654 assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
655 this.width = 48;
656 assert.equal(this.width, node.style.minWidth.replace("px", ""));
657 assert.equal(this.width, node.firstElementChild.style.width.replace("px", ""));
659 this.destroy();
660 doneTest();
661 }
662 }));
664 // test click handler not respond to right-click
665 let clickCount = 0;
666 tests.push(function testNoRightClick() testSingleWidget({
667 id: "right-click-content",
668 label: "click test widget - content",
669 content: "<div id='me'>foo</div>",
670 contentScript: // Left click
671 "var evt = new MouseEvent('click', {button: 0});" +
672 "document.getElementById('me').dispatchEvent(evt); " +
673 // Middle click
674 "evt = new MouseEvent('click', {button: 1});" +
675 "document.getElementById('me').dispatchEvent(evt); " +
676 // Right click
677 "evt = new MouseEvent('click', {button: 2});" +
678 "document.getElementById('me').dispatchEvent(evt); " +
679 // Mouseover
680 "evt = new MouseEvent('mouseover');" +
681 "document.getElementById('me').dispatchEvent(evt);",
682 contentScriptWhen: "end",
683 onClick: function() clickCount++,
684 onMouseover: function() {
685 assert.equal(clickCount, 1, "only left click was sent to click handler");
686 this.destroy();
687 doneTest();
688 }
689 }));
691 // kick off test execution
692 doneTest();
693 };
695 exports.testWidgetWithValidPanel = function(assert, done) {
696 let { loader } = LoaderWithHookedConsole(module);
697 const { Widget } = loader.require("sdk/widget");
698 const { Panel } = loader.require("sdk/panel");
700 let widget1 = Widget({
701 id: "testWidgetWithValidPanel",
702 label: "panel widget 1",
703 content: "<div id='me'>foo</div>",
704 contentScript: "var evt = new MouseEvent('click', {button: 0});" +
705 "document.body.dispatchEvent(evt);",
706 contentScriptWhen: "end",
707 panel: Panel({
708 contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>",
709 onShow: function() {
710 let { document } = getMostRecentBrowserWindow();
711 let widgetEle = document.getElementById("widget:" + jetpackID + "-" + widget1.id);
712 let panelEle = document.getElementById('mainPopupSet').lastChild;
713 // See bug https://bugzilla.mozilla.org/show_bug.cgi?id=859592
714 assert.equal(panelEle.getAttribute("type"), "arrow", 'the panel is a arrow type');
715 assert.strictEqual(panelEle.anchorNode, widgetEle, 'the panel is properly anchored to the widget');
717 widget1.destroy();
718 loader.unload();
719 assert.pass("panel displayed on click");
720 done();
721 }
722 })
723 });
724 };
726 exports.testWidgetWithInvalidPanel = function(assert) {
727 let { loader } = LoaderWithHookedConsole(module);
728 const widgets = loader.require("sdk/widget");
730 assert.throws(
731 function() {
732 widgets.Widget({
733 id: "panel2",
734 label: "panel widget 2",
735 panel: {}
736 });
737 },
738 /^The option \"panel\" must be one of the following types: null, undefined, object$/,
739 "widget.panel must be a Panel object");
740 loader.unload();
741 };
743 exports.testPanelWidget3 = function testPanelWidget3(assert, done) {
744 let { loader } = LoaderWithHookedConsole(module);
745 const widgets = loader.require("sdk/widget");
746 const { Panel } = loader.require("sdk/panel");
748 let onClickCalled = false;
749 let widget3 = widgets.Widget({
750 id: "panel3",
751 label: "panel widget 3",
752 content: "<div id='me'>foo</div>",
753 contentScript: "var evt = new MouseEvent('click', {button: 0});" +
754 "document.body.firstElementChild.dispatchEvent(evt);",
755 contentScriptWhen: "end",
756 onClick: function() {
757 onClickCalled = true;
758 this.panel.show();
759 },
760 panel: Panel({
761 contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>",
762 onShow: function() {
763 assert.ok(
764 onClickCalled,
765 "onClick called on click for widget with both panel and onClick");
766 widget3.destroy();
767 loader.unload();
768 done();
769 }
770 })
771 });
772 };
774 exports.testWidgetWithPanelInMenuPanel = function(assert, done) {
775 const { CustomizableUI } = Cu.import("resource:///modules/CustomizableUI.jsm", {});
776 let { loader } = LoaderWithHookedConsole(module);
777 const widgets = loader.require("sdk/widget");
778 const { Panel } = loader.require("sdk/panel");
780 let widget1 = widgets.Widget({
781 id: "panel1",
782 label: "panel widget 1",
783 content: "<div id='me'>foo</div>",
784 contentScript: "new " + function() {
785 self.port.on('click', () => {
786 let evt = new MouseEvent('click', {button: 0});
787 document.body.dispatchEvent(evt);
788 });
789 },
790 contentScriptWhen: "end",
791 panel: Panel({
792 contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>",
793 onShow: function() {
794 let { document } = getMostRecentBrowserWindow();
795 let { anchorNode } = document.getElementById('mainPopupSet').lastChild;
796 let panelButtonNode = document.getElementById("PanelUI-menu-button");
798 assert.strictEqual(anchorNode, panelButtonNode,
799 'the panel is anchored to the panel menu button instead of widget');
801 widget1.destroy();
802 loader.unload();
803 done();
804 }
805 })
806 });
808 let widgetId = "widget:" + jetpackID + "-" + widget1.id;
810 CustomizableUI.addListener({
811 onWidgetAdded: function(id) {
812 if (id !== widgetId) return;
814 let { document, PanelUI } = getMostRecentBrowserWindow();
816 PanelUI.panel.addEventListener('popupshowing', function onshow({type}) {
817 this.removeEventListener(type, onshow);
818 widget1.port.emit('click');
819 });
821 document.getElementById("PanelUI-menu-button").click()
822 }
823 });
825 CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
826 };
828 exports.testWidgetMessaging = function testWidgetMessaging(assert, done) {
829 let { loader } = LoaderWithHookedConsole(module);
830 const widgets = loader.require("sdk/widget");
832 let origMessage = "foo";
833 let widget = widgets.Widget({
834 id: "widget-messaging",
835 label: "foo",
836 content: "<bar>baz</bar>",
837 contentScriptWhen: "end",
838 contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
839 onMessage: function(message) {
840 if (message == "ready")
841 widget.postMessage(origMessage);
842 else {
843 assert.equal(origMessage, message);
844 widget.destroy();
845 loader.unload();
846 done();
847 }
848 }
849 });
850 };
852 exports.testWidgetViews = function testWidgetViews(assert, done) {
853 let { loader } = LoaderWithHookedConsole(module);
854 const widgets = loader.require("sdk/widget");
856 let widget = widgets.Widget({
857 id: "widget-views",
858 label: "foo",
859 content: "<bar>baz</bar>",
860 contentScriptWhen: "ready",
861 contentScript: "self.on('message', function(data) self.postMessage(data)); self.postMessage('ready')",
862 onAttach: function(view) {
863 assert.pass("WidgetView created");
864 view.on("message", function () {
865 assert.pass("Got message in WidgetView");
866 widget.destroy();
867 });
868 view.on("detach", function () {
869 assert.pass("WidgetView destroyed");
870 loader.unload();
871 done();
872 });
873 }
874 });
875 };
877 exports.testWidgetViewsUIEvents = function testWidgetViewsUIEvents(assert, done) {
878 let { loader } = LoaderWithHookedConsole(module);
879 const widgets = loader.require("sdk/widget");
880 const { browserWindows } = loader.require("sdk/windows");
882 let view = null;
883 let widget = widgets.Widget({
884 id: "widget-view-ui-events",
885 label: "foo",
886 content: "<div id='me'>foo</div>",
887 contentScript: "var evt = new MouseEvent('click', {button: 0});" +
888 "document.getElementById('me').dispatchEvent(evt);",
889 contentScriptWhen: "ready",
890 onAttach: function(attachView) {
891 view = attachView;
892 assert.pass("Got attach event");
893 },
894 onClick: function (eventView) {
895 assert.equal(view, eventView,
896 "event first argument is equal to the WidgetView");
897 let view2 = widget.getView(browserWindows.activeWindow);
898 assert.equal(view, view2,
899 "widget.getView return the same WidgetView");
900 widget.destroy();
901 loader.unload();
902 done();
903 }
904 });
905 };
907 exports.testWidgetViewsCustomEvents = function testWidgetViewsCustomEvents(assert, done) {
908 let { loader } = LoaderWithHookedConsole(module);
909 const widgets = loader.require("sdk/widget");
911 let widget = widgets.Widget({
912 id: "widget-view-custom-events",
913 label: "foo",
914 content: "<div id='me'>foo</div>",
915 contentScript: "self.port.emit('event', 'ok');",
916 contentScriptWhen: "ready",
917 onAttach: function(view) {
918 view.port.on("event", function (data) {
919 assert.equal(data, "ok",
920 "event argument is valid on WidgetView");
921 });
922 },
923 });
924 widget.port.on("event", function (data) {
925 assert.equal(data, "ok", "event argument is valid on Widget");
926 widget.destroy();
927 loader.unload();
928 done();
929 });
930 };
932 exports.testWidgetViewsTooltip = function testWidgetViewsTooltip(assert, done) {
933 let { loader } = LoaderWithHookedConsole(module);
934 const widgets = loader.require("sdk/widget");
935 const { browserWindows } = loader.require("sdk/windows");
937 let widget = new widgets.Widget({
938 id: "widget-views-tooltip",
939 label: "foo",
940 content: "foo"
941 });
942 let view = widget.getView(browserWindows.activeWindow);
943 widget.tooltip = null;
944 assert.equal(view.tooltip, "foo",
945 "view tooltip defaults to base widget label");
946 assert.equal(widget.tooltip, "foo",
947 "tooltip defaults to base widget label");
948 widget.destroy();
949 loader.unload();
950 done();
951 };
953 exports.testWidgetMove = function testWidgetMove(assert, done) {
954 let { loader } = LoaderWithHookedConsole(module);
955 const widgets = loader.require("sdk/widget");
957 let browserWindow = getMostRecentBrowserWindow();
958 let doc = browserWindow.document;
960 let label = "unique-widget-label";
961 let origMessage = "message after node move";
962 let gotFirstReady = false;
964 let widget = widgets.Widget({
965 id: "widget-move",
966 label: label,
967 content: "<bar>baz</bar>",
968 contentScriptWhen: "ready",
969 contentScript: "self.on('message', function(data) { self.postMessage(data); }); self.postMessage('ready');",
970 onMessage: function(message) {
971 if (message == "ready") {
972 if (!gotFirstReady) {
973 assert.pass("Got first ready event");
974 let widgetNode = doc.querySelector('toolbaritem[label="' + label + '"]');
975 let parent = widgetNode.parentNode;
976 parent.insertBefore(widgetNode, parent.firstChild);
977 gotFirstReady = true;
978 }
979 else {
980 assert.pass("Got second ready event");
981 widget.postMessage(origMessage);
982 }
983 }
984 else {
985 assert.equal(origMessage, message, "Got message after node move");
986 widget.destroy();
987 loader.unload();
988 done();
989 }
990 }
991 });
992 };
994 /*
995 The bug is exhibited when a widget with HTML content has it's content
996 changed to new HTML content with a pound in it. Because the src of HTML
997 content is converted to a data URI, the underlying iframe doesn't
998 consider the content change a navigation change, so doesn't load
999 the new content.
1000 */
1001 exports.testWidgetWithPound = function testWidgetWithPound(assert, done) {
1002 let { loader } = LoaderWithHookedConsole(module);
1003 const widgets = loader.require("sdk/widget");
1005 function getWidgetContent(widget) {
1006 let browserWindow = getMostRecentBrowserWindow();
1007 let doc = browserWindow.document;
1008 let widgetNode = doc.querySelector('toolbaritem[label="' + widget.label + '"]');
1009 assert.ok(widgetNode, 'found widget node in the front-end');
1010 return widgetNode.firstChild.contentDocument.body.innerHTML;
1011 }
1013 let count = 0;
1014 let widget = widgets.Widget({
1015 id: "1",
1016 label: "foo",
1017 content: "foo",
1018 contentScript: "window.addEventListener('load', self.postMessage, false);",
1019 onMessage: function() {
1020 count++;
1021 if (count == 1) {
1022 widget.content = "foo#";
1023 }
1024 else {
1025 assert.equal(getWidgetContent(widget), "foo#", "content updated to pound?");
1026 widget.destroy();
1027 loader.unload();
1028 done();
1029 }
1030 }
1031 });
1032 };
1034 exports.testContentScriptOptionsOption = function(assert, done) {
1035 let { loader } = LoaderWithHookedConsole(module);
1036 const { Widget } = loader.require("sdk/widget");
1038 let widget = Widget({
1039 id: "widget-script-options",
1040 label: "fooz",
1041 content: "fooz",
1042 contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
1043 contentScriptWhen: "end",
1044 contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
1045 onMessage: function(msg) {
1046 assert.equal( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
1047 assert.equal( typeof msg[1], 'object', 'object as contentScriptOptions' );
1048 assert.equal( msg[1].a, true, 'boolean in contentScriptOptions' );
1049 assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
1050 assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' );
1051 widget.destroy();
1052 loader.unload();
1053 done();
1054 }
1055 });
1056 };
1058 exports.testOnAttachWithoutContentScript = function(assert, done) {
1059 let { loader } = LoaderWithHookedConsole(module);
1060 const { Widget } = loader.require("sdk/widget");
1062 let widget = Widget({
1063 id: "onAttachNoCS",
1064 label: "onAttachNoCS",
1065 content: "onAttachNoCS",
1066 onAttach: function (view) {
1067 assert.pass("received attach event");
1068 widget.destroy();
1069 loader.unload();
1070 done();
1071 }
1072 });
1073 };
1075 exports.testPostMessageOnAttach = function(assert, done) {
1076 let { loader } = LoaderWithHookedConsole(module);
1077 const { Widget } = loader.require("sdk/widget");
1079 let widget = Widget({
1080 id: "onAttach",
1081 label: "onAttach",
1082 content: "onAttach",
1083 // 1) Send a message immediatly after `attach` event
1084 onAttach: function (view) {
1085 view.postMessage("ok");
1086 },
1087 // 2) Listen to it and forward it back to the widget
1088 contentScript: "self.on('message', self.postMessage);",
1089 // 3) Listen to this forwarded message
1090 onMessage: function (msg) {
1091 assert.equal( msg, "ok", "postMessage works on `attach` event");
1092 widget.destroy();
1093 loader.unload();
1094 done();
1095 }
1096 });
1097 };
1099 exports.testPostMessageOnLocationChange = function(assert, done) {
1100 let { loader } = LoaderWithHookedConsole(module);
1101 const { Widget } = loader.require("sdk/widget");
1103 let attachEventCount = 0;
1104 let messagesCount = 0;
1105 let widget = Widget({
1106 id: "onLocationChange",
1107 label: "onLocationChange",
1108 content: "onLocationChange",
1109 contentScript: "new " + function ContentScriptScope() {
1110 // Emit an event when content script is applied in order to know when
1111 // the first document is loaded so that we can load the 2nd one
1112 self.postMessage("ready");
1113 // And forward any incoming message back to the widget to see if
1114 // messaging is working on 2nd document
1115 self.on("message", self.postMessage);
1116 },
1117 onMessage: function (msg) {
1118 messagesCount++;
1119 if (messagesCount == 1) {
1120 assert.equal(msg, "ready", "First document is loaded");
1121 widget.content = "location changed";
1122 }
1123 else if (messagesCount == 2) {
1124 assert.equal(msg, "ready", "Second document is loaded");
1125 widget.postMessage("ok");
1126 }
1127 else if (messagesCount == 3) {
1128 assert.equal(msg, "ok",
1129 "We receive the message sent to the 2nd document");
1130 widget.destroy();
1131 loader.unload();
1132 done();
1133 }
1134 }
1135 });
1136 };
1138 exports.testSVGWidget = function(assert, done) {
1139 let { loader } = LoaderWithHookedConsole(module);
1140 const { Widget } = loader.require("sdk/widget");
1142 // use of capital SVG here is intended, that was failing..
1143 let SVG_URL = fixtures.url("mofo_logo.SVG");
1145 let widget = Widget({
1146 id: "mozilla-svg-logo",
1147 label: "moz foundation logo",
1148 contentURL: SVG_URL,
1149 contentScript: "self.postMessage({count: window.document.images.length, src: window.document.images[0].src});",
1150 onMessage: function(data) {
1151 widget.destroy();
1152 assert.equal(data.count, 1, 'only one image');
1153 assert.equal(data.src, SVG_URL, 'only one image');
1154 loader.unload();
1155 done();
1156 }
1157 });
1158 };
1160 exports.testReinsertion = function(assert, done) {
1161 let { loader } = LoaderWithHookedConsole(module);
1162 const { Widget } = loader.require("sdk/widget");
1163 const WIDGETID = "test-reinsertion";
1164 let browserWindow = getMostRecentBrowserWindow();
1166 let widget = Widget({
1167 id: "test-reinsertion",
1168 label: "test reinsertion",
1169 content: "Test",
1170 });
1171 let realWidgetId = "widget:" + jetpackID + "-" + WIDGETID;
1172 // Remove the widget:
1174 browserWindow.CustomizableUI.removeWidgetFromArea(realWidgetId);
1176 openNewWindowTab("about:blank", { inNewWindow: true, onLoad: function(e) {
1177 assert.equal(e.target.defaultView.document.getElementById(realWidgetId), null);
1178 close(e.target.defaultView).then(_ => {
1179 loader.unload();
1180 done();
1181 });
1182 }});
1183 };
1185 exports.testWideWidget = function testWideWidget(assert) {
1186 let { loader } = LoaderWithHookedConsole(module);
1187 const widgets = loader.require("sdk/widget");
1188 const { document, CustomizableUI, gCustomizeMode, setTimeout } = getMostRecentBrowserWindow();
1190 let wideWidget = widgets.Widget({
1191 id: "my-wide-widget",
1192 label: "wide-wdgt",
1193 content: "foo",
1194 width: 200
1195 });
1197 let widget = widgets.Widget({
1198 id: "my-regular-widget",
1199 label: "reg-wdgt",
1200 content: "foo"
1201 });
1203 let wideWidgetNode = document.querySelector("toolbaritem[label=wide-wdgt]");
1204 let widgetNode = document.querySelector("toolbaritem[label=reg-wdgt]");
1206 assert.equal(wideWidgetNode, null,
1207 "Wide Widget are not added to UI");
1209 assert.notEqual(widgetNode, null,
1210 "regular size widget are in the UI");
1211 };
1213 require("sdk/test").run(exports);