browser_cmd_screenshot.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /* Any copyright is dedicated to the Public Domain.
  2. * http://creativecommons.org/publicdomain/zero/1.0/ */
  3. /* global helpers, btoa, whenDelayedStartupFinished, OpenBrowserWindow */
  4. // Test that screenshot command works properly
  5. "use strict";
  6. const TEST_URI = "http://example.com/browser/devtools/client/commandline/" +
  7. "test/browser_cmd_screenshot.html";
  8. var FileUtils = (Cu.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils;
  9. function test() {
  10. // This test gets bombarded by a cascade of GCs and often takes 50s so lets be
  11. // safe and give the test 90s to run.
  12. requestLongerTimeout(3);
  13. return Task.spawn(spawnTest).then(finish, helpers.handleError);
  14. }
  15. function* spawnTest() {
  16. waitForExplicitFinish();
  17. info("RUN TEST: non-private window");
  18. let normWin = yield addWindow({ private: false });
  19. yield addTabWithToolbarRunTests(normWin);
  20. normWin.close();
  21. info("RUN TEST: private window");
  22. let pbWin = yield addWindow({ private: true });
  23. yield addTabWithToolbarRunTests(pbWin);
  24. pbWin.close();
  25. }
  26. function* addTabWithToolbarRunTests(win) {
  27. let options = yield helpers.openTab(TEST_URI, { chromeWindow: win });
  28. let browser = options.browser;
  29. yield helpers.openToolbar(options);
  30. // Test input status
  31. yield helpers.audit(options, [
  32. {
  33. setup: "screenshot",
  34. check: {
  35. input: "screenshot",
  36. markup: "VVVVVVVVVV",
  37. status: "VALID",
  38. args: {
  39. }
  40. },
  41. },
  42. {
  43. setup: "screenshot abc.png",
  44. check: {
  45. input: "screenshot abc.png",
  46. markup: "VVVVVVVVVVVVVVVVVV",
  47. status: "VALID",
  48. args: {
  49. filename: { value: "abc.png"},
  50. }
  51. },
  52. },
  53. {
  54. setup: "screenshot --fullpage",
  55. check: {
  56. input: "screenshot --fullpage",
  57. markup: "VVVVVVVVVVVVVVVVVVVVV",
  58. status: "VALID",
  59. args: {
  60. fullpage: { value: true},
  61. }
  62. },
  63. },
  64. {
  65. setup: "screenshot abc --delay 5",
  66. check: {
  67. input: "screenshot abc --delay 5",
  68. markup: "VVVVVVVVVVVVVVVVVVVVVVVV",
  69. status: "VALID",
  70. args: {
  71. filename: { value: "abc"},
  72. delay: { value: 5 },
  73. }
  74. },
  75. },
  76. {
  77. setup: "screenshot --selector img#testImage",
  78. check: {
  79. input: "screenshot --selector img#testImage",
  80. markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
  81. status: "VALID",
  82. },
  83. },
  84. ]);
  85. // Test capture to file
  86. let file = FileUtils.getFile("TmpD", [ "TestScreenshotFile.png" ]);
  87. yield helpers.audit(options, [
  88. {
  89. setup: "screenshot " + file.path,
  90. check: {
  91. args: {
  92. filename: { value: "" + file.path },
  93. fullpage: { value: false },
  94. clipboard: { value: false },
  95. },
  96. },
  97. exec: {
  98. output: new RegExp("^Saved to "),
  99. },
  100. post: function () {
  101. // Bug 849168: screenshot command tests fail in try but not locally
  102. // ok(file.exists(), "Screenshot file exists");
  103. if (file.exists()) {
  104. file.remove(false);
  105. }
  106. }
  107. },
  108. ]);
  109. // Test capture to clipboard
  110. yield helpers.audit(options, [
  111. {
  112. setup: "screenshot --clipboard",
  113. check: {
  114. args: {
  115. clipboard: { value: true },
  116. },
  117. },
  118. exec: {
  119. output: new RegExp("^Copied to clipboard.$"),
  120. },
  121. post: Task.async(function* () {
  122. let imgSize1 = yield getImageSizeFromClipboard();
  123. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  124. Assert.equal(imgSize.width, content.innerWidth,
  125. "Image width matches window size");
  126. Assert.equal(imgSize.height, content.innerHeight,
  127. "Image height matches window size");
  128. });
  129. })
  130. },
  131. {
  132. setup: "screenshot --fullpage --clipboard",
  133. check: {
  134. args: {
  135. fullpage: { value: true },
  136. clipboard: { value: true },
  137. },
  138. },
  139. exec: {
  140. output: new RegExp("^Copied to clipboard.$"),
  141. },
  142. post: Task.async(function* () {
  143. let imgSize1 = yield getImageSizeFromClipboard();
  144. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  145. Assert.equal(imgSize.width,
  146. content.innerWidth + content.scrollMaxX - content.scrollMinX,
  147. "Image width matches page size");
  148. Assert.equal(imgSize.height,
  149. content.innerHeight + content.scrollMaxY - content.scrollMinY,
  150. "Image height matches page size");
  151. });
  152. })
  153. },
  154. {
  155. setup: "screenshot --selector img#testImage --clipboard",
  156. check: {
  157. args: {
  158. clipboard: { value: true },
  159. },
  160. },
  161. exec: {
  162. output: new RegExp("^Copied to clipboard.$"),
  163. },
  164. post: Task.async(function* () {
  165. let imgSize1 = yield getImageSizeFromClipboard();
  166. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  167. let img = content.document.querySelector("img#testImage");
  168. Assert.equal(imgSize.width, img.clientWidth,
  169. "Image width matches element size");
  170. Assert.equal(imgSize.height, img.clientHeight,
  171. "Image height matches element size");
  172. });
  173. })
  174. },
  175. ]);
  176. // Trigger scrollbars by forcing document to overflow
  177. // This only affects results on OSes with scrollbars that reduce document size
  178. // (non-floating scrollbars). With default OS settings, this means Windows
  179. // and Linux are affected, but Mac is not. For Mac to exhibit this behavior,
  180. // change System Preferences -> General -> Show scroll bars to Always.
  181. yield ContentTask.spawn(browser, {}, function* () {
  182. content.document.body.classList.add("overflow");
  183. });
  184. let scrollbarSize = yield ContentTask.spawn(browser, {}, function* () {
  185. const winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
  186. .getInterface(Ci.nsIDOMWindowUtils);
  187. let scrollbarHeight = {};
  188. let scrollbarWidth = {};
  189. winUtils.getScrollbarSize(true, scrollbarWidth, scrollbarHeight);
  190. return {
  191. width: scrollbarWidth.value,
  192. height: scrollbarHeight.value,
  193. };
  194. });
  195. info(`Scrollbar size: ${scrollbarSize.width}x${scrollbarSize.height}`);
  196. // Test capture to clipboard in presence of scrollbars
  197. yield helpers.audit(options, [
  198. {
  199. setup: "screenshot --clipboard",
  200. check: {
  201. args: {
  202. clipboard: { value: true },
  203. },
  204. },
  205. exec: {
  206. output: new RegExp("^Copied to clipboard.$"),
  207. },
  208. post: Task.async(function* () {
  209. let imgSize1 = yield getImageSizeFromClipboard();
  210. imgSize1.scrollbarWidth = scrollbarSize.width;
  211. imgSize1.scrollbarHeight = scrollbarSize.height;
  212. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  213. Assert.equal(imgSize.width, content.innerWidth - imgSize.scrollbarWidth,
  214. "Image width matches window size minus scrollbar size");
  215. Assert.equal(imgSize.height, content.innerHeight - imgSize.scrollbarHeight,
  216. "Image height matches window size minus scrollbar size");
  217. });
  218. })
  219. },
  220. {
  221. setup: "screenshot --fullpage --clipboard",
  222. check: {
  223. args: {
  224. fullpage: { value: true },
  225. clipboard: { value: true },
  226. },
  227. },
  228. exec: {
  229. output: new RegExp("^Copied to clipboard.$"),
  230. },
  231. post: Task.async(function* () {
  232. let imgSize1 = yield getImageSizeFromClipboard();
  233. imgSize1.scrollbarWidth = scrollbarSize.width;
  234. imgSize1.scrollbarHeight = scrollbarSize.height;
  235. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  236. Assert.equal(imgSize.width,
  237. (content.innerWidth + content.scrollMaxX -
  238. content.scrollMinX) - imgSize.scrollbarWidth,
  239. "Image width matches page size minus scrollbar size");
  240. Assert.equal(imgSize.height,
  241. (content.innerHeight + content.scrollMaxY -
  242. content.scrollMinY) - imgSize.scrollbarHeight,
  243. "Image height matches page size minus scrollbar size");
  244. });
  245. })
  246. },
  247. {
  248. setup: "screenshot --selector img#testImage --clipboard",
  249. check: {
  250. args: {
  251. clipboard: { value: true },
  252. },
  253. },
  254. exec: {
  255. output: new RegExp("^Copied to clipboard.$"),
  256. },
  257. post: Task.async(function* () {
  258. let imgSize1 = yield getImageSizeFromClipboard();
  259. yield ContentTask.spawn(browser, imgSize1, function* (imgSize) {
  260. let img = content.document.querySelector("img#testImage");
  261. Assert.equal(imgSize.width, img.clientWidth,
  262. "Image width matches element size");
  263. Assert.equal(imgSize.height, img.clientHeight,
  264. "Image height matches element size");
  265. });
  266. })
  267. },
  268. ]);
  269. yield helpers.closeToolbar(options);
  270. yield helpers.closeTab(options);
  271. }
  272. function addWindow(windowOptions) {
  273. return new Promise(resolve => {
  274. let win = OpenBrowserWindow(windowOptions);
  275. // This feels hacky, we should refactor it
  276. whenDelayedStartupFinished(win, () => {
  277. // Would like to get rid of this executeSoon, but without it the url
  278. // (TEST_URI) provided in addTabWithToolbarRunTests hasn't loaded
  279. executeSoon(() => {
  280. resolve(win);
  281. });
  282. });
  283. });
  284. }
  285. let getImageSizeFromClipboard = Task.async(function* () {
  286. let clipid = Ci.nsIClipboard;
  287. let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid);
  288. let trans = Cc["@mozilla.org/widget/transferable;1"]
  289. .createInstance(Ci.nsITransferable);
  290. let flavor = "image/png";
  291. trans.init(null);
  292. trans.addDataFlavor(flavor);
  293. clip.getData(trans, clipid.kGlobalClipboard);
  294. let data = new Object();
  295. let dataLength = new Object();
  296. trans.getTransferData(flavor, data, dataLength);
  297. ok(data.value, "screenshot exists");
  298. ok(dataLength.value > 0, "screenshot has length");
  299. let image = data.value;
  300. let dataURI = `data:${flavor};base64,`;
  301. // Due to the differences in how images could be stored in the clipboard the
  302. // checks below are needed. The clipboard could already provide the image as
  303. // byte streams, but also as pointer, or as image container. If it's not
  304. // possible obtain a byte stream, the function returns `null`.
  305. if (image instanceof Ci.nsISupportsInterfacePointer) {
  306. image = image.data;
  307. }
  308. if (image instanceof Ci.imgIContainer) {
  309. image = Cc["@mozilla.org/image/tools;1"]
  310. .getService(Ci.imgITools)
  311. .encodeImage(image, flavor);
  312. }
  313. if (image instanceof Ci.nsIInputStream) {
  314. let binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
  315. .createInstance(Ci.nsIBinaryInputStream);
  316. binaryStream.setInputStream(image);
  317. let rawData = binaryStream.readBytes(binaryStream.available());
  318. let charCodes = Array.from(rawData, c => c.charCodeAt(0) & 0xff);
  319. let encodedData = String.fromCharCode(...charCodes);
  320. encodedData = btoa(encodedData);
  321. dataURI = dataURI + encodedData;
  322. } else {
  323. throw new Error("Unable to read image data");
  324. }
  325. let img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
  326. let loaded = new Promise(resolve => {
  327. img.addEventListener("load", function onLoad() {
  328. img.removeEventListener("load", onLoad);
  329. resolve();
  330. });
  331. });
  332. img.src = dataURI;
  333. document.documentElement.appendChild(img);
  334. yield loaded;
  335. img.remove();
  336. return {
  337. width: img.width,
  338. height: img.height,
  339. };
  340. });