test-widget.js 44 KB

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