@atlaskit/react-ufo 3.1.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +11 -0
  3. package/dist/cjs/create-experimental-interaction-metrics-payload/index.js +52 -25
  4. package/dist/cjs/create-payload/index.js +290 -231
  5. package/dist/cjs/interaction-metrics/index.js +61 -23
  6. package/dist/cjs/interaction-metrics/post-interaction-log.js +63 -34
  7. package/dist/cjs/interaction-metrics-init/index.js +26 -7
  8. package/dist/cjs/segment/schedule-on-paint.js +35 -0
  9. package/dist/cjs/segment/segment.js +10 -1
  10. package/dist/cjs/vc/index.js +105 -1
  11. package/dist/cjs/vc/types.js +5 -0
  12. package/dist/cjs/vc/vc-observer/index.js +198 -208
  13. package/dist/cjs/vc/vc-observer-new/entries-timeline/index.js +56 -0
  14. package/dist/cjs/vc/vc-observer-new/get-element-name.js +68 -0
  15. package/dist/cjs/vc/vc-observer-new/index.js +132 -0
  16. package/dist/cjs/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +75 -0
  17. package/dist/cjs/vc/vc-observer-new/metric-calculator/fy25_03/index.js +60 -0
  18. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +274 -0
  19. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +151 -0
  20. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +367 -0
  21. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +397 -0
  22. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +5 -0
  23. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +61 -0
  24. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +151 -0
  25. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +107 -0
  26. package/dist/cjs/vc/vc-observer-new/metric-calculator/percentile-calc/types.js +5 -0
  27. package/dist/cjs/vc/vc-observer-new/metric-calculator/types.js +5 -0
  28. package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/get-viewport-height.js +16 -0
  29. package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/get-viewport-width.js +16 -0
  30. package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/is-viewport-entry-data.js +16 -0
  31. package/dist/cjs/vc/vc-observer-new/metric-calculator/utils/task-yield.js +45 -0
  32. package/dist/cjs/vc/vc-observer-new/types.js +5 -0
  33. package/dist/cjs/vc/vc-observer-new/viewport-observer/index.js +195 -0
  34. package/dist/cjs/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +71 -0
  35. package/dist/cjs/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +65 -0
  36. package/dist/cjs/vc/vc-observer-new/viewport-observer/performance-observer/index.js +58 -0
  37. package/dist/cjs/vc/vc-observer-new/viewport-observer/types.js +1 -0
  38. package/dist/cjs/vc/vc-observer-new/window-event-observer/index.js +54 -0
  39. package/dist/es2019/create-experimental-interaction-metrics-payload/index.js +2 -2
  40. package/dist/es2019/create-payload/index.js +8 -7
  41. package/dist/es2019/interaction-metrics/index.js +3 -3
  42. package/dist/es2019/interaction-metrics/post-interaction-log.js +5 -5
  43. package/dist/es2019/interaction-metrics-init/index.js +26 -7
  44. package/dist/es2019/segment/schedule-on-paint.js +29 -0
  45. package/dist/es2019/segment/segment.js +9 -1
  46. package/dist/es2019/vc/index.js +56 -1
  47. package/dist/es2019/vc/types.js +1 -0
  48. package/dist/es2019/vc/vc-observer/index.js +1 -4
  49. package/dist/es2019/vc/vc-observer-new/entries-timeline/index.js +34 -0
  50. package/dist/es2019/vc/vc-observer-new/get-element-name.js +62 -0
  51. package/dist/es2019/vc/vc-observer-new/index.js +98 -0
  52. package/dist/es2019/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +38 -0
  53. package/dist/es2019/vc/vc-observer-new/metric-calculator/fy25_03/index.js +39 -0
  54. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +194 -0
  55. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +69 -0
  56. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +248 -0
  57. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +261 -0
  58. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +1 -0
  59. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +19 -0
  60. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +98 -0
  61. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +58 -0
  62. package/dist/es2019/vc/vc-observer-new/metric-calculator/percentile-calc/types.js +1 -0
  63. package/dist/es2019/vc/vc-observer-new/metric-calculator/types.js +1 -0
  64. package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/get-viewport-height.js +9 -0
  65. package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/get-viewport-width.js +9 -0
  66. package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/is-viewport-entry-data.js +10 -0
  67. package/dist/es2019/vc/vc-observer-new/metric-calculator/utils/task-yield.js +17 -0
  68. package/dist/es2019/vc/vc-observer-new/types.js +1 -0
  69. package/dist/es2019/vc/vc-observer-new/viewport-observer/index.js +168 -0
  70. package/dist/es2019/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +65 -0
  71. package/dist/es2019/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +48 -0
  72. package/dist/es2019/vc/vc-observer-new/viewport-observer/performance-observer/index.js +41 -0
  73. package/dist/es2019/vc/vc-observer-new/viewport-observer/types.js +0 -0
  74. package/dist/es2019/vc/vc-observer-new/window-event-observer/index.js +36 -0
  75. package/dist/esm/create-experimental-interaction-metrics-payload/index.js +52 -25
  76. package/dist/esm/create-payload/index.js +290 -231
  77. package/dist/esm/interaction-metrics/index.js +61 -23
  78. package/dist/esm/interaction-metrics/post-interaction-log.js +63 -34
  79. package/dist/esm/interaction-metrics-init/index.js +26 -7
  80. package/dist/esm/segment/schedule-on-paint.js +29 -0
  81. package/dist/esm/segment/segment.js +10 -1
  82. package/dist/esm/vc/index.js +104 -1
  83. package/dist/esm/vc/types.js +1 -0
  84. package/dist/esm/vc/vc-observer/index.js +198 -208
  85. package/dist/esm/vc/vc-observer-new/entries-timeline/index.js +50 -0
  86. package/dist/esm/vc/vc-observer-new/get-element-name.js +62 -0
  87. package/dist/esm/vc/vc-observer-new/index.js +126 -0
  88. package/dist/esm/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.js +69 -0
  89. package/dist/esm/vc/vc-observer-new/metric-calculator/fy25_03/index.js +54 -0
  90. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.js +268 -0
  91. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.js +143 -0
  92. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.js +361 -0
  93. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.js +390 -0
  94. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.js +1 -0
  95. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/index.js +54 -0
  96. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.js +144 -0
  97. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.js +100 -0
  98. package/dist/esm/vc/vc-observer-new/metric-calculator/percentile-calc/types.js +1 -0
  99. package/dist/esm/vc/vc-observer-new/metric-calculator/types.js +1 -0
  100. package/dist/esm/vc/vc-observer-new/metric-calculator/utils/get-viewport-height.js +10 -0
  101. package/dist/esm/vc/vc-observer-new/metric-calculator/utils/get-viewport-width.js +10 -0
  102. package/dist/esm/vc/vc-observer-new/metric-calculator/utils/is-viewport-entry-data.js +10 -0
  103. package/dist/esm/vc/vc-observer-new/metric-calculator/utils/task-yield.js +38 -0
  104. package/dist/esm/vc/vc-observer-new/types.js +1 -0
  105. package/dist/esm/vc/vc-observer-new/viewport-observer/index.js +189 -0
  106. package/dist/esm/vc/vc-observer-new/viewport-observer/intersection-observer/index.js +65 -0
  107. package/dist/esm/vc/vc-observer-new/viewport-observer/mutation-observer/index.js +59 -0
  108. package/dist/esm/vc/vc-observer-new/viewport-observer/performance-observer/index.js +51 -0
  109. package/dist/esm/vc/vc-observer-new/viewport-observer/types.js +0 -0
  110. package/dist/esm/vc/vc-observer-new/window-event-observer/index.js +48 -0
  111. package/dist/types/common/index.d.ts +1 -0
  112. package/dist/types/create-experimental-interaction-metrics-payload/index.d.ts +3 -2
  113. package/dist/types/create-payload/index.d.ts +4 -4
  114. package/dist/types/interaction-metrics/post-interaction-log.d.ts +3 -2
  115. package/dist/types/segment/schedule-on-paint.d.ts +2 -0
  116. package/dist/types/vc/index.d.ts +3 -3
  117. package/dist/types/vc/types.d.ts +34 -0
  118. package/dist/types/vc/vc-observer/index.d.ts +5 -21
  119. package/dist/types/vc/vc-observer-new/entries-timeline/index.d.ts +13 -0
  120. package/dist/types/vc/vc-observer-new/get-element-name.d.ts +8 -0
  121. package/dist/types/vc/vc-observer-new/index.d.ts +18 -0
  122. package/dist/types/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +9 -0
  123. package/dist/types/vc/vc-observer-new/metric-calculator/fy25_03/index.d.ts +7 -0
  124. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.d.ts +91 -0
  125. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +4 -0
  126. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.d.ts +39 -0
  127. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.d.ts +8 -0
  128. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.d.ts +43 -0
  129. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/index.d.ts +3 -0
  130. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.d.ts +11 -0
  131. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.d.ts +23 -0
  132. package/dist/types/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +9 -0
  133. package/dist/types/vc/vc-observer-new/metric-calculator/types.d.ts +20 -0
  134. package/dist/types/vc/vc-observer-new/metric-calculator/utils/get-viewport-height.d.ts +1 -0
  135. package/dist/types/vc/vc-observer-new/metric-calculator/utils/get-viewport-width.d.ts +1 -0
  136. package/dist/types/vc/vc-observer-new/metric-calculator/utils/is-viewport-entry-data.d.ts +2 -0
  137. package/dist/types/vc/vc-observer-new/metric-calculator/utils/task-yield.d.ts +1 -0
  138. package/dist/types/vc/vc-observer-new/types.d.ts +21 -0
  139. package/dist/types/vc/vc-observer-new/viewport-observer/index.d.ts +22 -0
  140. package/dist/types/vc/vc-observer-new/viewport-observer/intersection-observer/index.d.ts +30 -0
  141. package/dist/types/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +14 -0
  142. package/dist/types/vc/vc-observer-new/viewport-observer/performance-observer/index.d.ts +14 -0
  143. package/dist/types/vc/vc-observer-new/viewport-observer/types.d.ts +10 -0
  144. package/dist/types/vc/vc-observer-new/window-event-observer/index.d.ts +17 -0
  145. package/dist/types-ts4.5/common/index.d.ts +1 -0
  146. package/dist/types-ts4.5/create-experimental-interaction-metrics-payload/index.d.ts +3 -2
  147. package/dist/types-ts4.5/create-payload/index.d.ts +4 -4
  148. package/dist/types-ts4.5/interaction-metrics/post-interaction-log.d.ts +3 -2
  149. package/dist/types-ts4.5/segment/schedule-on-paint.d.ts +2 -0
  150. package/dist/types-ts4.5/vc/index.d.ts +3 -3
  151. package/dist/types-ts4.5/vc/types.d.ts +34 -0
  152. package/dist/types-ts4.5/vc/vc-observer/index.d.ts +5 -21
  153. package/dist/types-ts4.5/vc/vc-observer-new/entries-timeline/index.d.ts +13 -0
  154. package/dist/types-ts4.5/vc/vc-observer-new/get-element-name.d.ts +8 -0
  155. package/dist/types-ts4.5/vc/vc-observer-new/index.d.ts +18 -0
  156. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/abstract-base-vc-calculator.d.ts +9 -0
  157. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/fy25_03/index.d.ts +7 -0
  158. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/canvas-pixel.d.ts +91 -0
  159. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/canvas-heatmap/index.d.ts +4 -0
  160. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/heatmap.d.ts +39 -0
  161. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/index.d.ts +8 -0
  162. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/heatmap/types.d.ts +43 -0
  163. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/index.d.ts +3 -0
  164. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/calc-union-area.d.ts +11 -0
  165. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/rect-sweeping-line/index.d.ts +23 -0
  166. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/percentile-calc/types.d.ts +9 -0
  167. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/types.d.ts +20 -0
  168. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/get-viewport-height.d.ts +1 -0
  169. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/get-viewport-width.d.ts +1 -0
  170. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/is-viewport-entry-data.d.ts +2 -0
  171. package/dist/types-ts4.5/vc/vc-observer-new/metric-calculator/utils/task-yield.d.ts +1 -0
  172. package/dist/types-ts4.5/vc/vc-observer-new/types.d.ts +21 -0
  173. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/index.d.ts +22 -0
  174. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/intersection-observer/index.d.ts +30 -0
  175. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/mutation-observer/index.d.ts +14 -0
  176. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/performance-observer/index.d.ts +14 -0
  177. package/dist/types-ts4.5/vc/vc-observer-new/viewport-observer/types.d.ts +10 -0
  178. package/dist/types-ts4.5/vc/vc-observer-new/window-event-observer/index.d.ts +17 -0
  179. package/package.json +13 -1
@@ -0,0 +1,194 @@
1
+ import taskYield from '../../utils/task-yield';
2
+
3
+ // 24-bit color value
4
+
5
+ /**
6
+ * Class responsible for managing a scaled canvas and tracking pixel drawing operations.
7
+ * It uses an OffscreenCanvas for better performance and maintains a mapping between
8
+ * colors and timestamps for pixel counting purposes.
9
+ */
10
+ export class ViewportCanvas {
11
+ /** The underlying OffscreenCanvas instance */
12
+
13
+ /** The 2D rendering context of the canvas */
14
+
15
+ /** Scale factor applied to the canvas (affects final pixel counts) */
16
+
17
+ /** Maps unique colors to their corresponding timestamps */
18
+
19
+ /** Counter used to generate unique colors */
20
+
21
+ /**
22
+ * Creates a new ViewportCanvas instance.
23
+ * @param viewport - The dimensions of the viewport
24
+ * @param scaleFactor - Scale factor to apply to the canvas (default: 0.5)
25
+ * @throws {Error} If canvas context cannot be obtained
26
+ */
27
+ constructor(viewport, scaleFactor = 1) {
28
+ this.scaleFactor = scaleFactor;
29
+ this.colorCounter = 1;
30
+ this.colorTimeMap = new Map();
31
+
32
+ // Calculate scaled dimensions
33
+ this.scaledWidth = Math.ceil(viewport.width * scaleFactor);
34
+ this.scaledHeight = Math.ceil(viewport.height * scaleFactor);
35
+ this.scaleX = this.scaledWidth / viewport.width;
36
+ this.scaleY = this.scaledHeight / viewport.height;
37
+
38
+ // Initialize OffscreenCanvas with scaled dimensions
39
+ this.canvas = document.createElement('canvas');
40
+ this.canvas.width = this.scaledWidth;
41
+ this.canvas.height = this.scaledHeight;
42
+ const ctx = this.canvas.getContext('2d', {
43
+ alpha: false,
44
+ // Disable alpha channel for better performance
45
+ willReadFrequently: true,
46
+ // Optimize for frequent pixel reading
47
+ colorSpace: 'srgb' // Use standard RGB color space
48
+ });
49
+ if (!ctx) {
50
+ throw new Error('Could not get canvas context');
51
+ }
52
+ this.ctx = ctx;
53
+ this.ctx.globalCompositeOperation = 'source-over';
54
+ this.clear();
55
+ }
56
+ getScaledDimensions() {
57
+ return {
58
+ width: this.scaledWidth,
59
+ height: this.scaledHeight
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Clears the entire canvas by removing all drawn content.
65
+ * @private
66
+ */
67
+ clear() {
68
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
69
+ }
70
+
71
+ /**
72
+ * Generates a unique RGB color from an incrementing counter.
73
+ * Uses a 24-bit color space (16,777,216 possible colors).
74
+ *
75
+ * @private
76
+ * @returns A unique RGB color string
77
+ *
78
+ * @example
79
+ * // Example bit operations for color 0x00FF8040:
80
+ * // Red: (0x00FF8040 >> 16) & 0xFF = 0xFF = 255
81
+ * // Green: (0x00FF8040 >> 8) & 0xFF = 0x80 = 128
82
+ * // Blue: 0x00FF8040 & 0xFF = 0x40 = 64
83
+ */
84
+ generateColor() {
85
+ // Wrap around at 16,777,215 (0x00FFFFFF) to stay within 24-bit color space
86
+ const rgbColor = this.colorCounter++ % 0x00ffffff;
87
+ return getRGBComponents(rgbColor);
88
+ }
89
+
90
+ /**
91
+ * Draws a rectangle on the canvas with a unique color and associates it with a timestamp.
92
+ * Each drawn rectangle gets a unique color which is mapped to the provided timestamp.
93
+ *
94
+ * @param rect - The rectangle dimensions to draw
95
+ * @param timestamp - The timestamp to associate with this drawing operation
96
+ */
97
+ drawRect(rect, timestamp) {
98
+ const color = this.generateColor();
99
+ this.colorTimeMap.set(color, timestamp);
100
+ this.ctx.fillStyle = color;
101
+ if (this.scaleFactor === 1) {
102
+ return this.ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
103
+ }
104
+ const scaledX = rect.x * this.scaleX;
105
+ const scaledY = rect.y * this.scaleY;
106
+ const scaledWidth = rect.width * this.scaleX;
107
+ const scaledHeight = rect.height * this.scaleY;
108
+ this.ctx.fillRect(Math.floor(scaledX), Math.ceil(scaledY), Math.max(scaledWidth, 1), Math.max(scaledHeight, 1));
109
+ }
110
+
111
+ /**
112
+ * Calculates the number of pixels drawn for each timestamp.
113
+ * This method:
114
+ * 1. Reads the pixel data from the canvas
115
+ * 2. Counts pixels of each unique color
116
+ * 3. Maps colors back to their timestamps
117
+ * 4. Adjusts counts based on the scale factor
118
+ *
119
+ * @returns A Map containing timestamp to pixel count mappings
120
+ */
121
+ async getPixelCounts() {
122
+ const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
123
+ const timestampsAmount = this.colorTimeMap.size;
124
+ const pixelCounts = await calculateDrawnPixelsRaw(imageData, this.scaleFactor, timestampsAmount);
125
+ // Convert color counts to timestamp counts
126
+ const timePixelCounts = new Map();
127
+ for (let i = 0; i < pixelCounts.length; i++) {
128
+ const count = pixelCounts[i];
129
+ if (typeof count !== 'number') {
130
+ continue;
131
+ }
132
+ const color = i + 1;
133
+ const rgbColor = getRGBComponents(color);
134
+ const timestamp = this.colorTimeMap.get(rgbColor);
135
+ if (timestamp !== undefined) {
136
+ timePixelCounts.set(timestamp, (timePixelCounts.get(timestamp) || 0) + count);
137
+ }
138
+ }
139
+ return timePixelCounts;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Converts a number into RGB components in such a way that they can be recombined
145
+ * to form the original number using bitwise operations.
146
+ * @param number - The input number to be split into RGB components.
147
+ * @returns The RGB color string in the format "rgb(r, g, b)".
148
+ */
149
+ export function getRGBComponents(n) {
150
+ // Ensure the input is within the valid range for a 24-bit color
151
+ if (n < 0 || n > 0xffffff) {
152
+ throw new Error('Input number must be between 0 and 16777215 (inclusive).');
153
+ }
154
+
155
+ // Extract blue component (bits 0-7)
156
+ const blue = n & 0xff;
157
+
158
+ // Extract green component (bits 8-15)
159
+ const green = n >> 8 & 0xff;
160
+
161
+ // Extract red component (bits 16-23)
162
+ const red = n >> 16 & 0xff;
163
+ return `rgb(${red}, ${green}, ${blue})`;
164
+ }
165
+
166
+ /**
167
+ * Calculates the number of pixels drawn for each color in the image data.
168
+ * @param imageData - The image data to analyze.
169
+ * @param scaleFactor - The scale factor applied to the canvas.
170
+ * @param arraySize - The amount of timestamps that were drawn in the viewport
171
+ * @returns A Map containing color to pixel count mappings.
172
+ */
173
+ export async function calculateDrawnPixelsRaw(imageData, scaleFactor, arraySize) {
174
+ const data = imageData.data;
175
+ const scaleCompensation = Math.round(1 / (scaleFactor * scaleFactor));
176
+ const arr = new Uint32Array(arraySize);
177
+ for (let i = 0; i < data.length; i += 4) {
178
+ // Check alpha
179
+ if (data[i + 3] !== 0) {
180
+ // Combine RGB components into a single 24-bit color value:
181
+ // (data[i] << 16) - Shift red component left 16 bits (bits 16-23)
182
+ // (data[i + 1] << 8) - Shift green component left 8 bits (bits 8-15)
183
+ // data[i + 2] - Blue component stays as is (bits 0-7)
184
+ // The | operator combines all bits together
185
+ const color = data[i] << 16 | data[i + 1] << 8 | data[i + 2];
186
+ const colorIndex = color - 1;
187
+ arr[colorIndex] = (arr[colorIndex] || 0) + scaleCompensation;
188
+ }
189
+ if (i % 10000 === 0) {
190
+ await taskYield();
191
+ }
192
+ }
193
+ return arr;
194
+ }
@@ -0,0 +1,69 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+ import { ViewportCanvas } from './canvas-pixel';
3
+ export default async function calculateTTVCPercentiles({
4
+ viewport,
5
+ orderedEntries,
6
+ percentiles
7
+ }) {
8
+ const canvas = new ViewportCanvas(viewport, fg('platform_ufo_canvas_heatmap_full_precision') ? 1 : 0.25);
9
+ const elementMap = new Map();
10
+ for (const entry of orderedEntries) {
11
+ if (!('rect' in entry.data)) {
12
+ continue;
13
+ }
14
+ const rect = entry.data.rect;
15
+ const elementName = entry.data.elementName;
16
+ canvas.drawRect(rect, entry.time);
17
+ if (!elementMap.has(entry.time)) {
18
+ elementMap.set(entry.time, new Set());
19
+ }
20
+ elementMap.get(entry.time).add(elementName);
21
+ }
22
+
23
+ // Get pixel counts
24
+ const timePixelCounts = await canvas.getPixelCounts();
25
+ const viewportTotalPixels = viewport.width * viewport.height;
26
+ return calculatePercentiles(timePixelCounts, elementMap, percentiles, viewportTotalPixels);
27
+ }
28
+ export function calculatePercentiles(timePixelCounts, elementMap, unorderedPercentiles, totalPixels) {
29
+ const results = {};
30
+ let cumulativePixels = 0;
31
+ const percentiles = unorderedPercentiles.sort((a, b) => a - b);
32
+
33
+ // Sort entries by timestamp for consistent processing
34
+ const sortedEntries = Array.from(timePixelCounts.entries()).sort(([timeA], [timeB]) => Number(timeA) - Number(timeB));
35
+ let percentileIndex = 0;
36
+ let domElementsBuffer = new Set();
37
+ for (const [time, pixelCount] of sortedEntries) {
38
+ cumulativePixels += pixelCount;
39
+ const percentCovered = cumulativePixels / totalPixels * 100;
40
+ const elementNames = elementMap.get(time) || new Set();
41
+ elementNames.forEach(elName => domElementsBuffer.add(elName));
42
+ let matchesAnyCheckpoints = false;
43
+ while (percentileIndex < percentiles.length && percentCovered >= percentiles[percentileIndex]) {
44
+ results[`${percentiles[percentileIndex]}`] = {
45
+ t: Number(time),
46
+ e: Array.from(domElementsBuffer)
47
+ };
48
+ percentileIndex++;
49
+ matchesAnyCheckpoints = true;
50
+ }
51
+ if (matchesAnyCheckpoints) {
52
+ domElementsBuffer.clear();
53
+ }
54
+ if (percentileIndex >= percentiles.length) {
55
+ break;
56
+ }
57
+ }
58
+
59
+ // Fill in any missing percentiles
60
+ for (const percentile of percentiles) {
61
+ if (!(percentile in results)) {
62
+ results[`${percentile}`] = {
63
+ t: 0,
64
+ e: []
65
+ };
66
+ }
67
+ }
68
+ return results;
69
+ }
@@ -0,0 +1,248 @@
1
+ import isViewportEntryData from '../../utils/is-viewport-entry-data';
2
+ import taskYield from '../../utils/task-yield';
3
+ const MAX_HEATMAP_SIZE = 1000;
4
+ function createEmptyHeatmapEntry() {
5
+ return {
6
+ head: null,
7
+ previousEntries: []
8
+ };
9
+ }
10
+ function createEmptyMap(heatmapWidth, heatmapHeight) {
11
+ return Array.from({
12
+ length: heatmapHeight
13
+ }).map(() => Array.from({
14
+ length: heatmapWidth
15
+ }).map(createEmptyHeatmapEntry));
16
+ }
17
+ function isRectInside(a, b) {
18
+ if (!a || !b) {
19
+ return false;
20
+ }
21
+
22
+ // Check if all corners of rectangle a are within the bounds of rectangle b
23
+ return a.left >= b.left && a.right <= b.right && a.top >= b.top && a.bottom <= b.bottom;
24
+ }
25
+ export default class Heatmap {
26
+ /**
27
+ * Heatmap Width
28
+ */
29
+
30
+ /**
31
+ * Heatmap Height
32
+ */
33
+
34
+ /**
35
+ * Heatmap Area (width * height)
36
+ */
37
+
38
+ constructor({
39
+ viewport,
40
+ heatmapSize
41
+ }) {
42
+ // TODO timeOrigin? do we need? for SSR??
43
+ this.viewport = viewport;
44
+ const safeSize = Math.min(heatmapSize, MAX_HEATMAP_SIZE);
45
+ if (viewport.width === 0 || viewport.height === 0) {
46
+ this.width = safeSize;
47
+ this.height = safeSize;
48
+ this.scaleX = 1;
49
+ this.scaleY = 1;
50
+ this.heatmapAreaSize = 0;
51
+ this.map = createEmptyMap(safeSize, safeSize);
52
+ return;
53
+ }
54
+ const aspectRatio = viewport.width / viewport.height;
55
+ if (aspectRatio > 1) {
56
+ // Landscape orientation
57
+ this.width = safeSize;
58
+ this.height = Math.round(safeSize / aspectRatio);
59
+ } else {
60
+ // Portrait orientation
61
+ this.width = safeSize;
62
+ this.height = Math.round(safeSize * aspectRatio);
63
+ }
64
+ this.scaleX = this.width / viewport.width;
65
+ this.scaleY = this.height / viewport.height;
66
+ this.heatmapAreaSize = this.width * this.height;
67
+ this.map = createEmptyMap(this.width, this.height);
68
+ }
69
+ getHeatmap() {
70
+ return this.map;
71
+ }
72
+ getCell(row, col) {
73
+ var _this$map$row;
74
+ return (_this$map$row = this.map[row]) === null || _this$map$row === void 0 ? void 0 : _this$map$row[col];
75
+ }
76
+
77
+ /**
78
+ * Map Dom Rect to Heatmap Rect, rounded up to occupy full cell.
79
+ * @param rect DOM Rect
80
+ * @returns
81
+ */
82
+ mapDOMRectToHeatmap(rect) {
83
+ const scaledX = rect.x * this.scaleX;
84
+ const scaledY = rect.y * this.scaleY;
85
+ const scaledWidth = rect.width * this.scaleX;
86
+ const scaledHeight = rect.height * this.scaleY;
87
+ return {
88
+ left: Math.floor(scaledX),
89
+ right: Math.ceil(scaledX + scaledWidth),
90
+ top: Math.floor(scaledY),
91
+ bottom: Math.ceil(scaledY + scaledHeight)
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Calculate the ratio of a HeatmapRect compared to the full heatmap
97
+ *
98
+ * This function determines what fraction of the heatmap is covered by the given heatmap rectangle.
99
+ *
100
+ * @param rect
101
+ */
102
+ getRatio(rect) {
103
+ if (this.viewport.width === 0 || this.viewport.height === 0) {
104
+ return 0;
105
+ }
106
+ const {
107
+ right,
108
+ left,
109
+ bottom,
110
+ top
111
+ } = rect;
112
+ const rectWidth = right - left;
113
+ const rectHeight = bottom - top;
114
+ const rectArea = rectWidth * rectHeight;
115
+ const ratio = rectArea / this.heatmapAreaSize;
116
+ if (ratio > 1) {
117
+ return 1;
118
+ }
119
+ return ratio;
120
+ }
121
+ async applyEntriesToHeatmap(entries) {
122
+ for (let i = 0; i < entries.length; i++) {
123
+ const entry = entries[i];
124
+ const {
125
+ time,
126
+ type,
127
+ data
128
+ } = entry;
129
+ if (isViewportEntryData(data)) {
130
+ const rect = this.mapDOMRectToHeatmap(data.rect);
131
+ const ratio = this.getRatio(rect);
132
+ const heatmapEntryData = {
133
+ time,
134
+ elementName: data.elementName,
135
+ ratio: ratio !== null && ratio !== void 0 ? ratio : null,
136
+ rect,
137
+ source: type
138
+ };
139
+ const roundedTop = Math.floor(rect.top);
140
+ const roundedBottom = Math.min(rect.bottom, this.height);
141
+ const roundedLeft = Math.floor(rect.left);
142
+ const roundedRight = Math.min(rect.right, this.width);
143
+ for (let row = roundedTop; row < roundedBottom; row++) {
144
+ for (let col = roundedLeft; col < roundedRight; col++) {
145
+ const cell = this.getCell(row, col);
146
+ if (!cell) {
147
+ continue;
148
+ }
149
+ const previousEntry = cell.head;
150
+
151
+ // When elements are added at the same time
152
+ // we try to keep the inner element changes as the head
153
+ if ((previousEntry === null || previousEntry === void 0 ? void 0 : previousEntry.time) === entry.time && isRectInside(previousEntry.rect, heatmapEntryData.rect)) {
154
+ cell.previousEntries.push({
155
+ ...heatmapEntryData,
156
+ source: 'mutation:parent-mounted'
157
+ });
158
+ continue;
159
+ }
160
+ cell.head = {
161
+ ...heatmapEntryData,
162
+ source: heatmapEntryData.source || null
163
+ };
164
+ if (previousEntry !== null) {
165
+ cell.previousEntries.push(previousEntry);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ // Every 100 events processed
171
+ // we give the browser the power
172
+ // to process any other high priority task
173
+ if (i % 100 === 0) {
174
+ await taskYield();
175
+ }
176
+ }
177
+ }
178
+ async getVCPercentMetrics(vcPercentCheckpoint) {
179
+ const sortedCheckpoints = [...vcPercentCheckpoint].sort((a, b) => a - b);
180
+ const flattenHeatmap = this.map.flat();
181
+ const totalCells = flattenHeatmap.length;
182
+ const timestampMap = new Map();
183
+ for (let i = 0; i < flattenHeatmap.length; i++) {
184
+ var _cellHead$time, _timestampMap$get;
185
+ const cell = flattenHeatmap[i];
186
+ const cellHead = cell.head;
187
+ const timestamp = Math.trunc((_cellHead$time = cellHead === null || cellHead === void 0 ? void 0 : cellHead.time) !== null && _cellHead$time !== void 0 ? _cellHead$time : 0);
188
+ const elementName = cellHead === null || cellHead === void 0 ? void 0 : cellHead.elementName;
189
+ const curr = (_timestampMap$get = timestampMap.get(timestamp)) !== null && _timestampMap$get !== void 0 ? _timestampMap$get : {
190
+ cellCount: 0,
191
+ domElements: new Set()
192
+ };
193
+ curr.cellCount += 1;
194
+ if (elementName) {
195
+ curr.domElements.add(elementName);
196
+ }
197
+ timestampMap.set(timestamp, curr);
198
+
199
+ // Every 10000 heatmap entries processed
200
+ // we give the browser the power
201
+ // to process any other high priority task
202
+ if (i > 10000 && i % 10000 === 0) {
203
+ await taskYield();
204
+ }
205
+ }
206
+ const sortedTimings = [...timestampMap.keys()].sort((a, b) => a - b);
207
+ let totalCellPainted = 0;
208
+ const result = {};
209
+ let domElementsBuffer = new Set();
210
+ for (let i = 0; i < sortedTimings.length; i++) {
211
+ const timestamp = sortedTimings[i];
212
+ const timestampInfo = timestampMap.get(timestamp);
213
+ if (!timestampInfo) {
214
+ throw new Error('unexpected timestampInfo not found');
215
+ }
216
+ const {
217
+ cellCount,
218
+ domElements
219
+ } = timestampInfo;
220
+ totalCellPainted += cellCount;
221
+ const currVCRatio = totalCellPainted / totalCells;
222
+ const currVCPercent = Math.round(currVCRatio * 100);
223
+ domElements.forEach(domElement => {
224
+ domElementsBuffer.add(domElement);
225
+ });
226
+ let matchesAnyCheckpoints = false;
227
+ while (sortedCheckpoints.length > 0 && currVCPercent >= sortedCheckpoints[0]) {
228
+ const checkpoint = sortedCheckpoints.shift();
229
+ const domElements = [...domElementsBuffer];
230
+ if (!checkpoint) {
231
+ break;
232
+ }
233
+ matchesAnyCheckpoints = true;
234
+ result[checkpoint.toString()] = {
235
+ t: timestamp,
236
+ e: domElements
237
+ };
238
+ }
239
+ if (matchesAnyCheckpoints) {
240
+ domElementsBuffer.clear();
241
+ }
242
+ if (i % 500 === 0) {
243
+ await taskYield();
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ }