capture.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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 file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const {utils: Cu} = Components;
  6. Cu.importGlobalProperties(["crypto"]);
  7. this.EXPORTED_SYMBOLS = ["capture"];
  8. const CONTEXT_2D = "2d";
  9. const BG_COLOUR = "rgb(255,255,255)";
  10. const PNG_MIME = "image/png";
  11. const XHTML_NS = "http://www.w3.org/1999/xhtml";
  12. /** Provides primitives to capture screenshots. */
  13. this.capture = {};
  14. capture.Format = {
  15. Base64: 0,
  16. Hash: 1,
  17. };
  18. /**
  19. * Take a screenshot of a single element.
  20. *
  21. * @param {Node} node
  22. * The node to take a screenshot of.
  23. * @param {Array.<Node>=} highlights
  24. * Optional array of nodes, around which a border will be marked to
  25. * highlight them in the screenshot.
  26. *
  27. * @return {HTMLCanvasElement}
  28. * The canvas element where the element has been painted on.
  29. */
  30. capture.element = function (node, highlights = []) {
  31. let win = node.ownerDocument.defaultView;
  32. let rect = node.getBoundingClientRect();
  33. return capture.canvas(
  34. win,
  35. rect.left,
  36. rect.top,
  37. rect.width,
  38. rect.height,
  39. highlights);
  40. };
  41. /**
  42. * Take a screenshot of the window's viewport by taking into account
  43. * the current offsets.
  44. *
  45. * @param {DOMWindow} win
  46. * The DOM window providing the document element to capture,
  47. * and the offsets for the viewport.
  48. * @param {Array.<Node>=} highlights
  49. * Optional array of nodes, around which a border will be marked to
  50. * highlight them in the screenshot.
  51. *
  52. * @return {HTMLCanvasElement}
  53. * The canvas element where the viewport has been painted on.
  54. */
  55. capture.viewport = function (win, highlights = []) {
  56. let rootNode = win.document.documentElement;
  57. return capture.canvas(
  58. win,
  59. win.pageXOffset,
  60. win.pageYOffset,
  61. rootNode.clientWidth,
  62. rootNode.clientHeight,
  63. highlights);
  64. };
  65. /**
  66. * Low-level interface to draw a rectangle off the framebuffer.
  67. *
  68. * @param {DOMWindow} win
  69. * The DOM window used for the framebuffer, and providing the interfaces
  70. * for creating an HTMLCanvasElement.
  71. * @param {number} left
  72. * The left, X axis offset of the rectangle.
  73. * @param {number} top
  74. * The top, Y axis offset of the rectangle.
  75. * @param {number} width
  76. * The width dimension of the rectangle to paint.
  77. * @param {number} height
  78. * The height dimension of the rectangle to paint.
  79. * @param {Array.<Node>=} highlights
  80. * Optional array of nodes, around which a border will be marked to
  81. * highlight them in the screenshot.
  82. *
  83. * @return {HTMLCanvasElement}
  84. * The canvas on which the selection from the window's framebuffer
  85. * has been painted on.
  86. */
  87. capture.canvas = function (win, left, top, width, height, highlights = []) {
  88. let scale = win.devicePixelRatio;
  89. let canvas = win.document.createElementNS(XHTML_NS, "canvas");
  90. canvas.width = width * scale;
  91. canvas.height = height * scale;
  92. let ctx = canvas.getContext(CONTEXT_2D);
  93. let flags = ctx.DRAWWINDOW_DRAW_CARET;
  94. // Disabled in bug 1243415 for webplatform-test failures due to out of view elements.
  95. // Needs https://github.com/w3c/web-platform-tests/issues/4383 fixed.
  96. // ctx.DRAWWINDOW_DRAW_VIEW;
  97. // Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
  98. // ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
  99. ctx.scale(scale, scale);
  100. ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
  101. ctx = capture.highlight_(ctx, highlights, top, left);
  102. return canvas;
  103. };
  104. capture.highlight_ = function (context, highlights, top = 0, left = 0) {
  105. if (!highlights) {
  106. return;
  107. }
  108. context.lineWidth = "2";
  109. context.strokeStyle = "red";
  110. context.save();
  111. for (let el of highlights) {
  112. let rect = el.getBoundingClientRect();
  113. let oy = -top;
  114. let ox = -left;
  115. context.strokeRect(
  116. rect.left + ox,
  117. rect.top + oy,
  118. rect.width,
  119. rect.height);
  120. }
  121. return context;
  122. };
  123. /**
  124. * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
  125. *
  126. * @param {HTMLCanvasElement} canvas
  127. * The canvas to encode.
  128. *
  129. * @return {string}
  130. * A Base64 encoded string.
  131. */
  132. capture.toBase64 = function (canvas) {
  133. let u = canvas.toDataURL(PNG_MIME);
  134. return u.substring(u.indexOf(",") + 1);
  135. };
  136. /**
  137. * Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
  138. *
  139. * @param {HTMLCanvasElement} canvas
  140. * The canvas to encode.
  141. *
  142. * @return {string}
  143. * A hex digest of the SHA-256 hash of the base64 encoded string.
  144. */
  145. capture.toHash = function (canvas) {
  146. let u = capture.toBase64(canvas);
  147. let buffer = new TextEncoder("utf-8").encode(u);
  148. return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
  149. };
  150. /**
  151. * Convert buffer into to hex.
  152. *
  153. * @param {ArrayBuffer} buffer
  154. * The buffer containing the data to convert to hex.
  155. *
  156. * @return {string}
  157. * A hex digest of the input buffer.
  158. */
  159. function hex(buffer) {
  160. let hexCodes = [];
  161. let view = new DataView(buffer);
  162. for (let i = 0; i < view.byteLength; i += 4) {
  163. let value = view.getUint32(i);
  164. let stringValue = value.toString(16);
  165. let padding = '00000000';
  166. let paddedValue = (padding + stringValue).slice(-padding.length);
  167. hexCodes.push(paddedValue);
  168. }
  169. return hexCodes.join("");
  170. };