@percy/dom 1.14.0 → 1.15.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 (2) hide show
  1. package/dist/bundle.js +102 -24
  2. package/package.json +2 -2
package/dist/bundle.js CHANGED
@@ -18,10 +18,15 @@
18
18
  elem.setAttribute('data-percy-element-id', uid());
19
19
  }
20
20
  }
21
+ return dom;
21
22
  }
22
23
 
23
24
  // Translates JavaScript properties of inputs into DOM attributes.
24
- function serializeInputElements(dom, clone) {
25
+ function serializeInputElements(_ref) {
26
+ let {
27
+ dom,
28
+ clone
29
+ } = _ref;
25
30
  for (let elem of dom.querySelectorAll('input, textarea, select')) {
26
31
  let inputId = elem.getAttribute('data-percy-element-id');
27
32
  let cloneEl = clone.querySelector(`[data-percy-element-id="${inputId}"]`);
@@ -61,8 +66,12 @@
61
66
  }
62
67
 
63
68
  // Recursively serializes iframe documents into srcdoc attributes.
64
- function serializeFrames(dom, clone, _ref) {
69
+ function serializeFrames(_ref) {
65
70
  let {
71
+ dom,
72
+ clone,
73
+ warnings,
74
+ resources,
66
75
  enableJavaScript
67
76
  } = _ref;
68
77
  for (let frame of dom.querySelectorAll('iframe')) {
@@ -90,8 +99,13 @@
90
99
  enableJavaScript
91
100
  });
92
101
 
93
- // assign to srcdoc and remove src
94
- cloneEl.setAttribute('srcdoc', serialized);
102
+ // append serialized warnings and resources
103
+ /* istanbul ignore next: warnings not implemented yet */
104
+ for (let w of serialized.warnings) warnings.add(w);
105
+ for (let r of serialized.resources) resources.add(r);
106
+
107
+ // assign serialized html to srcdoc and remove src
108
+ cloneEl.setAttribute('srcdoc', serialized.html);
95
109
  cloneEl.removeAttribute('src');
96
110
 
97
111
  // delete inaccessible frames built with js when js is disabled because they
@@ -120,7 +134,11 @@
120
134
  }
121
135
 
122
136
  // Outputs in-memory CSSOM into their respective DOM nodes.
123
- function serializeCSSOM(dom, clone) {
137
+ function serializeCSSOM(_ref) {
138
+ let {
139
+ dom,
140
+ clone
141
+ } = _ref;
124
142
  for (let styleSheet of dom.styleSheets) {
125
143
  if (isCSSOM(styleSheet)) {
126
144
  let styleId = styleSheet.ownerNode.getAttribute('data-percy-element-id');
@@ -137,8 +155,33 @@
137
155
  }
138
156
  }
139
157
 
158
+ // Creates a resource object from an element's unique ID and data URL
159
+ function resourceFromDataURL(uid, dataURL) {
160
+ // split dataURL into desired parts
161
+ let [data, content] = dataURL.split(',');
162
+ let [, mimetype] = data.split(':');
163
+ [mimetype] = mimetype.split(';');
164
+
165
+ // build a URL for the serialized asset
166
+ let [, ext] = mimetype.split('/');
167
+ let path = `/__serialized__/${uid}.${ext}`;
168
+ let url = new URL(path, document.URL).toString();
169
+
170
+ // return the url, base64 content, and mimetype
171
+ return {
172
+ url,
173
+ content,
174
+ mimetype
175
+ };
176
+ }
177
+
140
178
  // Serialize in-memory canvas elements into images.
141
- function serializeCanvas(dom, clone) {
179
+ function serializeCanvas(_ref) {
180
+ let {
181
+ dom,
182
+ clone,
183
+ resources
184
+ } = _ref;
142
185
  for (let canvas of dom.querySelectorAll('canvas')) {
143
186
  // Note: the `.toDataURL` API requires WebGL canvas elements to use
144
187
  // `preserveDrawingBuffer: true`. This is because `.toDataURL` uses the
@@ -148,9 +191,15 @@
148
191
  // skip empty canvases
149
192
  if (!dataUrl || dataUrl === 'data:,') continue;
150
193
 
194
+ // get the element's percy id and create a resource for it
195
+ let percyElementId = canvas.getAttribute('data-percy-element-id');
196
+ let resource = resourceFromDataURL(percyElementId, dataUrl);
197
+ resources.add(resource);
198
+
151
199
  // create an image element in the cloned dom
152
200
  let img = clone.createElement('img');
153
- img.src = dataUrl;
201
+ // use a data attribute to avoid making a real request
202
+ img.setAttribute('data-percy-serialized-attribute-src', resource.url);
154
203
 
155
204
  // copy canvas element attributes to the image element such as style, class,
156
205
  // or data attributes that may be targeted by CSS
@@ -167,7 +216,6 @@
167
216
  img.style.maxWidth = img.style.maxWidth || '100%';
168
217
 
169
218
  // insert the image into the cloned DOM and remove the cloned canvas element
170
- let percyElementId = canvas.getAttribute('data-percy-element-id');
171
219
  let cloneEl = clone.querySelector(`[data-percy-element-id=${percyElementId}]`);
172
220
  cloneEl.parentElement.insertBefore(img, cloneEl);
173
221
  cloneEl.remove();
@@ -175,9 +223,14 @@
175
223
  }
176
224
 
177
225
  // Captures the current frame of videos and sets the poster image
178
- function serializeVideos(dom, clone) {
226
+ function serializeVideos(_ref) {
227
+ let {
228
+ dom,
229
+ clone,
230
+ resources
231
+ } = _ref;
179
232
  for (let video of dom.querySelectorAll('video')) {
180
- // If the video already has a poster image, no work for us to do
233
+ // if the video already has a poster image, no work for us to do
181
234
  if (video.getAttribute('poster')) continue;
182
235
  let videoId = video.getAttribute('data-percy-element-id');
183
236
  let cloneEl = clone.querySelector(`[data-percy-element-id="${videoId}"]`);
@@ -190,9 +243,15 @@
190
243
  dataUrl = canvas.toDataURL();
191
244
  } catch {}
192
245
 
193
- // If the canvas produces a blank image, skip
246
+ // if the canvas produces a blank image, skip
194
247
  if (!dataUrl || dataUrl === 'data:,') continue;
195
- cloneEl.setAttribute('poster', dataUrl);
248
+
249
+ // create a resource from the serialized data url
250
+ let resource = resourceFromDataURL(videoId, dataUrl);
251
+ resources.add(resource);
252
+
253
+ // use a data attribute to avoid making a real request
254
+ cloneEl.setAttribute('data-percy-serialized-attribute-poster', resource.url);
196
255
  }
197
256
  }
198
257
 
@@ -214,34 +273,53 @@
214
273
  return `<!DOCTYPE ${name}${deprecated}>`;
215
274
  }
216
275
 
276
+ // Serializes and returns the cloned DOM as an HTML string
277
+ function serializeHTML(ctx) {
278
+ let html = ctx.clone.documentElement.outerHTML;
279
+ // replace serialized data attributes with real attributes
280
+ html = html.replace(/ data-percy-serialized-attribute-(\w+?)=/ig, ' $1=');
281
+ // include the doctype with the html string
282
+ return doctype(ctx.dom) + html;
283
+ }
284
+
217
285
  // Serializes a document and returns the resulting DOM string.
218
286
  function serializeDOM(options) {
219
287
  let {
220
288
  dom = document,
221
289
  // allow snake_case or camelCase
222
290
  enableJavaScript = options === null || options === void 0 ? void 0 : options.enable_javascript,
223
- domTransformation = options === null || options === void 0 ? void 0 : options.dom_transformation
291
+ domTransformation = options === null || options === void 0 ? void 0 : options.dom_transformation,
292
+ stringifyResponse = options === null || options === void 0 ? void 0 : options.stringify_response
224
293
  } = options || {};
225
- prepareDOM(dom);
226
- let clone = dom.cloneNode(true);
227
- serializeInputElements(dom, clone);
228
- serializeFrames(dom, clone, {
294
+
295
+ // keep certain records throughout serialization
296
+ let ctx = {
297
+ resources: new Set(),
298
+ warnings: new Set(),
229
299
  enableJavaScript
230
- });
231
- serializeVideos(dom, clone);
300
+ };
301
+ ctx.dom = prepareDOM(dom);
302
+ ctx.clone = ctx.dom.cloneNode(true);
303
+ serializeInputElements(ctx);
304
+ serializeFrames(ctx);
305
+ serializeVideos(ctx);
232
306
  if (!enableJavaScript) {
233
- serializeCSSOM(dom, clone);
234
- serializeCanvas(dom, clone);
307
+ serializeCSSOM(ctx);
308
+ serializeCanvas(ctx);
235
309
  }
236
- let doc = clone.documentElement;
237
310
  if (domTransformation) {
238
311
  try {
239
- domTransformation(doc);
312
+ domTransformation(ctx.clone.documentElement);
240
313
  } catch (err) {
241
314
  console.error('Could not transform the dom:', err.message);
242
315
  }
243
316
  }
244
- return doctype(dom) + doc.outerHTML;
317
+ let result = {
318
+ html: serializeHTML(ctx),
319
+ warnings: Array.from(ctx.warnings),
320
+ resources: Array.from(ctx.resources)
321
+ };
322
+ return stringifyResponse ? JSON.stringify(result) : result;
245
323
  }
246
324
 
247
325
  exports["default"] = serializeDOM;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/dom",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,5 +34,5 @@
34
34
  "devDependencies": {
35
35
  "interactor.js": "^2.0.0-beta.10"
36
36
  },
37
- "gitHead": "fd72688e449d6dd3eafd346fc07879cb3bb01a4e"
37
+ "gitHead": "383ce2888d2e4fe6972368f9bbe8580b23431a98"
38
38
  }