test-tabs-common.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  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. const { Loader, LoaderWithHookedConsole } = require("sdk/test/loader");
  6. const { browserWindows } = require('sdk/windows');
  7. const tabs = require('sdk/tabs');
  8. const { isPrivate } = require('sdk/private-browsing');
  9. const { openDialog } = require('sdk/window/utils');
  10. const { isWindowPrivate } = require('sdk/window/utils');
  11. const { setTimeout } = require('sdk/timers');
  12. const { openWebpage } = require('./private-browsing/helper');
  13. const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
  14. const app = require("sdk/system/xul-app");
  15. const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
  16. // TEST: tab count
  17. exports.testTabCounts = function(assert, done) {
  18. tabs.open({
  19. url: 'about:blank',
  20. onReady: function(tab) {
  21. let count1 = 0,
  22. count2 = 0;
  23. for each(let window in browserWindows) {
  24. count1 += window.tabs.length;
  25. for each(let tab in window.tabs) {
  26. count2 += 1;
  27. }
  28. }
  29. assert.ok(tabs.length > 1, 'tab count is > 1');
  30. assert.equal(count1, tabs.length, 'tab count by length is correct');
  31. assert.equal(count2, tabs.length, 'tab count by iteration is correct');
  32. // end test
  33. tab.close(done);
  34. }
  35. });
  36. };
  37. // TEST: tabs.activeTab getter
  38. exports.testActiveTab_getter = function(assert, done) {
  39. let evtCount = 0;
  40. let activeTab = null;
  41. function endTest(type, tab) {
  42. if (type == 'activate') {
  43. assert.strictEqual(tabs.activeTab, tab, 'the active tab is the opened tab');
  44. activeTab = tabs.activeTab;
  45. }
  46. else {
  47. assert.equal(tab.url, url, 'the opened tab has the correct url');
  48. }
  49. if (++evtCount != 2)
  50. return;
  51. assert.strictEqual(activeTab, tab, 'the active tab is the ready tab');
  52. assert.strictEqual(tabs.activeTab, tab, 'the active tab is the ready tab');
  53. tab.close(done);
  54. }
  55. let url = URL.replace("#title#", "testActiveTab_getter");
  56. tabs.open({
  57. url: url,
  58. onReady: endTest.bind(null, 'ready'),
  59. onActivate: endTest.bind(null, 'activate')
  60. });
  61. };
  62. // TEST: tab.activate()
  63. exports.testActiveTab_setter = function(assert, done) {
  64. let url = URL.replace("#title#", "testActiveTab_setter");
  65. let tab1URL = URL.replace("#title#", "tab1");
  66. tabs.open({
  67. url: tab1URL,
  68. onReady: function(activeTab) {
  69. let activeTabURL = tabs.activeTab.url;
  70. tabs.open({
  71. url: url,
  72. inBackground: true,
  73. onReady: function onReady(tab) {
  74. assert.equal(tabs.activeTab.url, activeTabURL, "activeTab url has not changed");
  75. assert.equal(tab.url, url, "url of new background tab matches");
  76. tab.once('activate', function onActivate(eventTab) {
  77. assert.equal(tabs.activeTab.url, url, "url after activeTab setter matches");
  78. assert.equal(eventTab, tab, "event argument is the activated tab");
  79. assert.equal(eventTab, tabs.activeTab, "the tab is the active one");
  80. activeTab.close(function() {
  81. tab.close(done);
  82. });
  83. });
  84. tab.activate();
  85. }
  86. });
  87. }
  88. });
  89. };
  90. // TEST: tab.close()
  91. exports.testTabClose_alt = function(assert, done) {
  92. let url = URL.replace('#title#', 'TabClose_alt');
  93. let tab1URL = URL.replace('#title#', 'tab1');
  94. tabs.open({
  95. url: tab1URL,
  96. onReady: function(tab1) {
  97. // make sure that our tab is not active first
  98. assert.notEqual(tabs.activeTab.url, url, "tab is not the active tab");
  99. tabs.open({
  100. url: url,
  101. onReady: function(tab) {
  102. assert.equal(tab.url, url, "tab is now the active tab");
  103. assert.equal(tabs.activeTab.url, url, "tab is now the active tab");
  104. // another tab should be activated on close
  105. tabs.once('activate', function() {
  106. assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
  107. // end test
  108. tab1.close(done);
  109. });
  110. tab.close();
  111. }
  112. });
  113. }
  114. });
  115. };
  116. exports.testAttachOnOpen_alt = function (assert, done) {
  117. // Take care that attach has to be called on tab ready and not on tab open.
  118. tabs.open({
  119. url: "data:text/html;charset=utf-8,foobar",
  120. onOpen: function (tab) {
  121. let worker = tab.attach({
  122. contentScript: 'self.postMessage(document.location.href); ',
  123. onMessage: function (msg) {
  124. assert.equal(msg, "about:blank",
  125. "Worker document url is about:blank on open");
  126. worker.destroy();
  127. tab.close(done);
  128. }
  129. });
  130. }
  131. });
  132. };
  133. exports.testAttachOnMultipleDocuments_alt = function (assert, done) {
  134. // Example of attach that process multiple tab documents
  135. let firstLocation = "data:text/html;charset=utf-8,foobar";
  136. let secondLocation = "data:text/html;charset=utf-8,bar";
  137. let thirdLocation = "data:text/html;charset=utf-8,fox";
  138. let onReadyCount = 0;
  139. let worker1 = null;
  140. let worker2 = null;
  141. let detachEventCount = 0;
  142. tabs.open({
  143. url: firstLocation,
  144. onReady: function (tab) {
  145. onReadyCount++;
  146. if (onReadyCount == 1) {
  147. worker1 = tab.attach({
  148. contentScript: 'self.on("message", ' +
  149. ' function () self.postMessage(document.location.href)' +
  150. ');',
  151. onMessage: function (msg) {
  152. assert.equal(msg, firstLocation,
  153. "Worker url is equal to the 1st document");
  154. tab.url = secondLocation;
  155. },
  156. onDetach: function () {
  157. detachEventCount++;
  158. assert.pass("Got worker1 detach event");
  159. assert.throws(function () {
  160. worker1.postMessage("ex-1");
  161. },
  162. /Couldn't find the worker/,
  163. "postMessage throw because worker1 is destroyed");
  164. checkEnd();
  165. }
  166. });
  167. worker1.postMessage("new-doc-1");
  168. }
  169. else if (onReadyCount == 2) {
  170. worker2 = tab.attach({
  171. contentScript: 'self.on("message", ' +
  172. ' function () self.postMessage(document.location.href)' +
  173. ');',
  174. onMessage: function (msg) {
  175. assert.equal(msg, secondLocation,
  176. "Worker url is equal to the 2nd document");
  177. tab.url = thirdLocation;
  178. },
  179. onDetach: function () {
  180. detachEventCount++;
  181. assert.pass("Got worker2 detach event");
  182. assert.throws(function () {
  183. worker2.postMessage("ex-2");
  184. },
  185. /Couldn't find the worker/,
  186. "postMessage throw because worker2 is destroyed");
  187. checkEnd(tab);
  188. }
  189. });
  190. worker2.postMessage("new-doc-2");
  191. }
  192. else if (onReadyCount == 3) {
  193. tab.close();
  194. }
  195. }
  196. });
  197. function checkEnd(tab) {
  198. if (detachEventCount != 2)
  199. return;
  200. assert.pass("Got all detach events");
  201. done();
  202. }
  203. };
  204. exports.testAttachWrappers_alt = function (assert, done) {
  205. // Check that content script has access to wrapped values by default
  206. let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " +
  207. " document.getElementById = 3;</script>";
  208. let count = 0;
  209. tabs.open({
  210. url: document,
  211. onReady: function (tab) {
  212. let worker = tab.attach({
  213. contentScript: 'try {' +
  214. ' self.postMessage(!("globalJSVar" in window));' +
  215. ' self.postMessage(typeof window.globalJSVar == "undefined");' +
  216. '} catch(e) {' +
  217. ' self.postMessage(e.message);' +
  218. '}',
  219. onMessage: function (msg) {
  220. assert.equal(msg, true, "Worker has wrapped objects ("+count+")");
  221. if (count++ == 1)
  222. tab.close(function() done());
  223. }
  224. });
  225. }
  226. });
  227. };
  228. // TEST: activeWindow getter and activeTab getter on tab 'activate' event
  229. exports.testActiveWindowActiveTabOnActivate_alt = function(assert, done) {
  230. let activateCount = 0;
  231. let newTabs = [];
  232. let tabs = browserWindows.activeWindow.tabs;
  233. tabs.on('activate', function onActivate(tab) {
  234. assert.equal(tabs.activeTab, tab,
  235. "the active window's active tab is the tab provided");
  236. if (++activateCount == 2) {
  237. tabs.removeListener('activate', onActivate);
  238. newTabs.forEach(function(tab) {
  239. tab.close(function() {
  240. if (--activateCount == 0) {
  241. done();
  242. }
  243. });
  244. });
  245. }
  246. else if (activateCount > 2) {
  247. assert.fail("activateCount is greater than 2 for some reason..");
  248. }
  249. });
  250. tabs.open({
  251. url: URL.replace("#title#", "tabs.open1"),
  252. onOpen: function(tab) newTabs.push(tab)
  253. });
  254. tabs.open({
  255. url: URL.replace("#title#", "tabs.open2"),
  256. onOpen: function(tab) newTabs.push(tab)
  257. });
  258. };
  259. // TEST: tab properties
  260. exports.testTabContentTypeAndReload = function(assert, done) {
  261. let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
  262. let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
  263. tabs.open({
  264. url: url,
  265. onReady: function(tab) {
  266. if (tab.url === url) {
  267. assert.equal(tab.contentType, "text/html");
  268. tab.url = urlXML;
  269. }
  270. else {
  271. assert.equal(tab.contentType, "text/xml");
  272. tab.close(done);
  273. }
  274. }
  275. });
  276. };
  277. // test that it isn't possible to open a private tab without the private permission
  278. exports.testTabOpenPrivate = function(assert, done) {
  279. let url = 'about:blank';
  280. tabs.open({
  281. url: url,
  282. isPrivate: true,
  283. onReady: function(tab) {
  284. assert.equal(tab.url, url, 'opened correct tab');
  285. assert.equal(isPrivate(tab), false, 'private tabs are not supported by default');
  286. tab.close(done);
  287. }
  288. });
  289. }
  290. // We need permission flag in order to see private window's tabs
  291. exports.testPrivateAreNotListed = function (assert, done) {
  292. let originalTabCount = tabs.length;
  293. let page = openWebpage("about:blank", true);
  294. if (!page) {
  295. assert.pass("Private browsing isn't supported in this release");
  296. return;
  297. }
  298. page.ready.then(function (win) {
  299. if (isTabPBSupported || isWindowPBSupported) {
  300. assert.ok(isWindowPrivate(win), "the window is private");
  301. assert.equal(tabs.length, originalTabCount,
  302. 'but the tab is *not* visible in tabs list');
  303. }
  304. else {
  305. assert.ok(!isWindowPrivate(win), "the window isn't private");
  306. assert.equal(tabs.length, originalTabCount + 1,
  307. 'so that the tab is visible is tabs list');
  308. }
  309. page.close().then(done);
  310. });
  311. }
  312. // If we close the tab while being in `onOpen` listener,
  313. // we end up synchronously consuming TabOpen, closing the tab and still
  314. // synchronously consuming the related TabClose event before the second
  315. // loader have a change to process the first TabOpen event!
  316. exports.testImmediateClosing = function (assert, done) {
  317. let tabURL = 'data:text/html,foo';
  318. let { loader, messages } = LoaderWithHookedConsole(module, onMessage);
  319. let concurrentTabs = loader.require("sdk/tabs");
  320. concurrentTabs.on("open", function (tab) {
  321. // On Firefox, It shouldn't receive such event as the other loader will just
  322. // open and destroy the tab without giving a chance to other loader to even
  323. // know about the existance of this tab.
  324. if (app.is("Firefox")) {
  325. assert.fail("Concurrent loader received a tabs `open` event");
  326. }
  327. else {
  328. // On mobile, we can still receive an open event,
  329. // but not the related ready event
  330. tab.on("ready", function () {
  331. assert.fail("Concurrent loader received a tabs `ready` event");
  332. });
  333. }
  334. });
  335. function onMessage(type, msg) {
  336. assert.fail("Unexpected mesage on concurrent loader: " + msg);
  337. }
  338. tabs.open({
  339. url: tabURL,
  340. onOpen: function(tab) {
  341. tab.close(function () {
  342. assert.pass("Tab succesfully removed");
  343. // Let a chance to the concurrent loader to receive a TabOpen event
  344. // on the next event loop turn
  345. setTimeout(function () {
  346. loader.unload();
  347. done();
  348. }, 0);
  349. });
  350. }
  351. });
  352. }
  353. // TEST: tab.reload()
  354. exports.testTabReload = function(assert, done) {
  355. let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>";
  356. tabs.open({
  357. url: url,
  358. onReady: function onReady(tab) {
  359. tab.removeListener('ready', onReady);
  360. tab.once(
  361. 'ready',
  362. function onReload() {
  363. assert.pass("the tab was loaded again");
  364. assert.equal(tab.url, url, "the tab has the same URL");
  365. tab.close(function() done());
  366. }
  367. );
  368. tab.reload();
  369. }
  370. });
  371. };
  372. exports.testOnPageShowEvent = function (assert, done) {
  373. let events = [];
  374. let firstUrl = 'data:text/html;charset=utf-8,First';
  375. let secondUrl = 'data:text/html;charset=utf-8,Second';
  376. let counter = 0;
  377. function onPageShow (tab, persisted) {
  378. events.push('pageshow');
  379. counter++;
  380. if (counter === 1) {
  381. assert.equal(persisted, false, 'page should not be cached on initial load');
  382. tab.url = secondUrl;
  383. }
  384. else if (counter === 2) {
  385. assert.equal(persisted, false, 'second test page should not be cached either');
  386. tab.attach({
  387. contentScript: 'setTimeout(function () { window.history.back(); }, 0)'
  388. });
  389. }
  390. else {
  391. assert.equal(persisted, true, 'when we get back to the fist page, it has to' +
  392. 'come from cache');
  393. tabs.removeListener('pageshow', onPageShow);
  394. tabs.removeListener('open', onOpen);
  395. tabs.removeListener('ready', onReady);
  396. tab.close(() => {
  397. ['open', 'ready', 'pageshow', 'ready',
  398. 'pageshow', 'pageshow'].map((type, i) => {
  399. assert.equal(type, events[i], 'correct ordering of events');
  400. });
  401. done()
  402. });
  403. }
  404. }
  405. function onOpen () events.push('open');
  406. function onReady () events.push('ready');
  407. tabs.on('pageshow', onPageShow);
  408. tabs.on('open', onOpen);
  409. tabs.on('ready', onReady);
  410. tabs.open({
  411. url: firstUrl
  412. });
  413. };
  414. exports.testOnPageShowEventDeclarative = function (assert, done) {
  415. let events = [];
  416. let firstUrl = 'data:text/html;charset=utf-8,First';
  417. let secondUrl = 'data:text/html;charset=utf-8,Second';
  418. let counter = 0;
  419. function onPageShow (tab, persisted) {
  420. events.push('pageshow');
  421. counter++;
  422. if (counter === 1) {
  423. assert.equal(persisted, false, 'page should not be cached on initial load');
  424. tab.url = secondUrl;
  425. }
  426. else if (counter === 2) {
  427. assert.equal(persisted, false, 'second test page should not be cached either');
  428. tab.attach({
  429. contentScript: 'setTimeout(function () { window.history.back(); }, 0)'
  430. });
  431. }
  432. else {
  433. assert.equal(persisted, true, 'when we get back to the fist page, it has to' +
  434. 'come from cache');
  435. tabs.removeListener('pageshow', onPageShow);
  436. tabs.removeListener('open', onOpen);
  437. tabs.removeListener('ready', onReady);
  438. tab.close(() => {
  439. ['open', 'ready', 'pageshow', 'ready',
  440. 'pageshow', 'pageshow'].map((type, i) => {
  441. assert.equal(type, events[i], 'correct ordering of events');
  442. });
  443. done()
  444. });
  445. }
  446. }
  447. function onOpen () events.push('open');
  448. function onReady () events.push('ready');
  449. tabs.open({
  450. url: firstUrl,
  451. onPageShow: onPageShow,
  452. onOpen: onOpen,
  453. onReady: onReady
  454. });
  455. };
  456. require('sdk/test').run(exports);