OLD | NEW |
---|---|
1 /** | 1 /** |
2 * HTML Serializer that takes a document and synchronously stores it as an array | 2 * HTML Serializer that takes a document and synchronously stores it as an array |
3 * of strings, then asynchronously retrieves data URLs for same-origin images. | 3 * of strings, then asynchronously retrieves data URLs for same-origin images. |
4 * It stores enough state to later be converted to an html text file. | 4 * It stores enough state to later be converted to an html text file. |
5 */ | 5 */ |
6 var HTMLSerializer = class { | 6 var HTMLSerializer = class { |
7 constructor() { | 7 constructor() { |
8 | 8 |
9 /** | 9 /** |
10 * @private {Set<string>} Contains the tag names that should be | 10 * @private {Set<string>} Contains the tag names that should be |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
63 * text. | 63 * text. |
64 * @const | 64 * @const |
65 */ | 65 */ |
66 this.INPUT_TEXT_TYPE = { | 66 this.INPUT_TEXT_TYPE = { |
67 HTML : 0, | 67 HTML : 0, |
68 CSS : 1 | 68 CSS : 1 |
69 }; | 69 }; |
70 | 70 |
71 /** | 71 /** |
72 * @public {Array<string>} This array represents the serialized html that | 72 * @public {Array<string>} This array represents the serialized html that |
73 * makes up a node or document. | 73 * makes up a node or document. |
74 */ | 74 */ |
75 this.html = []; | 75 this.html = []; |
76 | 76 |
77 /** | 77 /** |
78 * @public {Object<number, string>} The keys represent an index in | 78 * @public {Object<number, string>} The keys represent an index in |
79 * |this.html|. The value is a url at which the resource that belongs at | 79 * |this.html|. The value is a url at which the resource that belongs at |
80 * that index can be retrieved. The resource will eventually be | 80 * that index can be retrieved. The resource will eventually be |
81 * converted to a data url. | 81 * converted to a data url. |
82 */ | 82 */ |
83 this.srcHoles = {}; | 83 this.srcHoles = {}; |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
180 * @private {string} The assigned id of the html element. | 180 * @private {string} The assigned id of the html element. |
181 */ | 181 */ |
182 this.rootId; | 182 this.rootId; |
183 } | 183 } |
184 | 184 |
185 /** | 185 /** |
186 * Takes an html document, and populates this objects fields such that it can | 186 * Takes an html document, and populates this objects fields such that it can |
187 * eventually be converted into an html file. | 187 * eventually be converted into an html file. |
188 * | 188 * |
189 * @param {Document} doc The Document to serialize. | 189 * @param {Document} doc The Document to serialize. |
190 */ | 190 */ |
191 processDocument(doc) { | 191 processDocument(doc) { |
192 this.windowHeight = doc.defaultView.innerHeight; | 192 this.windowHeight = doc.defaultView.innerHeight; |
193 this.windowWidth = doc.defaultView.innerWidth; | 193 this.windowWidth = doc.defaultView.innerWidth; |
194 | 194 |
195 if (doc.doctype) { | 195 if (doc.doctype) { |
196 this.html.push('<!DOCTYPE html>\n'); | 196 this.html.push('<!DOCTYPE html>\n'); |
197 } | 197 } |
198 | 198 |
199 if (this.iframeFullyQualifiedName(doc.defaultView) == '0') { | 199 if (this.iframeFullyQualifiedName(doc.defaultView) == '0') { |
200 this.html.push( | 200 this.html.push( |
(...skipping 23 matching lines...) Expand all Loading... | |
224 style = style.replace(/"/g, escapedQuote); | 224 style = style.replace(/"/g, escapedQuote); |
225 this.html[this.pseudoElementTestingStyleIndex] = style; | 225 this.html[this.pseudoElementTestingStyleIndex] = style; |
226 } | 226 } |
227 | 227 |
228 /** | 228 /** |
229 * Takes an html node, and populates this object's fields such that it can | 229 * Takes an html node, and populates this object's fields such that it can |
230 * eventually be converted into an html text file. | 230 * eventually be converted into an html text file. |
231 * | 231 * |
232 * @param {Node} node The Node to serialize. | 232 * @param {Node} node The Node to serialize. |
233 * @private | 233 * @private |
234 */ | 234 */ |
235 processTree(node) { | 235 processTree(node) { |
236 var tagName = node.tagName; | 236 var tagName = node.tagName; |
237 if (!tagName && node.nodeType != Node.TEXT_NODE) { | 237 if (!tagName && node.nodeType != Node.TEXT_NODE) { |
238 // Ignore nodes that don't have tags and are not text. | 238 // Ignore nodes that don't have tags and are not text. |
239 } else if (tagName && this.FILTERED_TAGS.has(tagName)) { | 239 } else if (tagName && this.FILTERED_TAGS.has(tagName)) { |
240 // Filter out nodes that are in filteredTags. | 240 // Filter out nodes that are in filteredTags. |
241 } else if (node.nodeType == Node.TEXT_NODE) { | 241 } else if (node.nodeType == Node.TEXT_NODE) { |
242 this.processText(node); | 242 this.processText(node); |
243 } else { | 243 } else { |
244 this.html.push(`<${tagName.toLowerCase()} `); | 244 this.html.push(`<${tagName.toLowerCase()} `); |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
329 this.html.push(text); | 329 this.html.push(text); |
330 } | 330 } |
331 | 331 |
332 /** | 332 /** |
333 * Takes an html element, and populates this object's fields with the | 333 * Takes an html element, and populates this object's fields with the |
334 * appropriate attribute names and values. | 334 * appropriate attribute names and values. |
335 * | 335 * |
336 * @param {Element} element The Element to serialize. | 336 * @param {Element} element The Element to serialize. |
337 * @param {string} id The id of the Element being serialized. | 337 * @param {string} id The id of the Element being serialized. |
338 * @private | 338 * @private |
339 */ | 339 */ |
340 processAttributes(element, id) { | 340 processAttributes(element, id) { |
341 var win = element.ownerDocument.defaultView; | 341 var win = element.ownerDocument.defaultView; |
342 var style = win.getComputedStyle(element, null); | 342 var style = win.getComputedStyle(element, null); |
343 var styleMap = {}; | 343 var styleMap = {}; |
344 for (var i = 0; i < style.length; i++) { | 344 for (var i = 0; i < style.length; i++) { |
345 var propertyName = style.item(i); | 345 var propertyName = style.item(i); |
346 styleMap[propertyName] = style.getPropertyValue(propertyName); | 346 styleMap[propertyName] = style.getPropertyValue(propertyName); |
347 } | 347 } |
348 this.idToStyleMap[id] = styleMap; | 348 this.idToStyleMap[id] = styleMap; |
349 this.idToStyleIndex[id] = this.html.length; | 349 this.idToStyleIndex[id] = this.html.length; |
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
576 * | 576 * |
577 * @param {Window} win The window to use in the calculation. | 577 * @param {Window} win The window to use in the calculation. |
578 * @return {string} The full path. | 578 * @return {string} The full path. |
579 */ | 579 */ |
580 iframeFullyQualifiedName(win) { | 580 iframeFullyQualifiedName(win) { |
581 if (this.iframeIndex(win) < 0) { | 581 if (this.iframeIndex(win) < 0) { |
582 return '0'; | 582 return '0'; |
583 } else { | 583 } else { |
584 var fullyQualifiedName = this.iframeFullyQualifiedName(win.parent); | 584 var fullyQualifiedName = this.iframeFullyQualifiedName(win.parent); |
585 var index = this.iframeIndex(win); | 585 var index = this.iframeIndex(win); |
586 return fullyQualifiedName + '.' + index; | 586 return fullyQualifiedName + '.' + index; |
587 } | 587 } |
588 } | 588 } |
589 | 589 |
590 /** | 590 /** |
591 * Calculate the correct encoding of a character that should be used given the | 591 * Calculate the correct encoding of a character that should be used given the |
592 * nesting depth of the window in the frame tree. | 592 * nesting depth of the window in the frame tree. |
593 * | 593 * |
594 * @param {string} char The character that should be escaped. | 594 * @param {string} char The character that should be escaped. |
595 * @param {number} depth The nesting depth of the appropriate window in the | 595 * @param {number} depth The nesting depth of the appropriate window in the |
596 * frame tree. | 596 * frame tree. |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
717 serializer.processCSSFonts(doc.defaultView, styleSheetSrc, css); | 717 serializer.processCSSFonts(doc.defaultView, styleSheetSrc, css); |
718 serializer.fillFontHoles(doc, callback); | 718 serializer.fillFontHoles(doc, callback); |
719 }).catch(function(error) { | 719 }).catch(function(error) { |
720 console.log(error); | 720 console.log(error); |
721 serializer.fillFontHoles(doc, callback); | 721 serializer.fillFontHoles(doc, callback); |
722 }); | 722 }); |
723 } | 723 } |
724 } | 724 } |
725 | 725 |
726 /** | 726 /** |
727 * Return a JSONizable dictionary that contain all the data of this object. | |
728 */ | |
729 asDict() { | |
wkorman
2017/09/20 20:11:04
Add a unit test for this? May as well while we're
nednguyen
2017/09/26 13:01:35
Acknowledged. I filed a tracking bug here https://
| |
730 return { | |
731 'html': htmlSerializer.html, | |
732 'frameHoles': htmlSerializer.frameHoles, | |
733 'idToStyleIndex': htmlSerializer.idToStyleIndex, | |
734 'idToStyleMap': htmlSerializer.idToStyleMap, | |
735 'windowHeight': htmlSerializer.windowHeight, | |
736 'windowWidth': htmlSerializer.windowWidth, | |
737 'rootId': htmlSerializer.rootId, | |
738 'rootStyleIndex': htmlSerializer.rootStyleIndex, | |
739 'pseudoElementSelectorToCSSMap': | |
740 htmlSerializer.pseudoElementSelectorToCSSMap, | |
741 'pseudoElementPlaceHolderIndex': | |
742 htmlSerializer.pseudoElementPlaceHolderIndex, | |
743 'pseudoElementTestingStyleIndex': | |
744 htmlSerializer.pseudoElementStyleTestingIndex, | |
745 'pseudoElementTestingStyleId': htmlSerializer.pseudoElementTestingStyleId, | |
746 'unusedId': htmlSerializer.generateId(document), | |
747 'frameIndex': htmlSerializer.iframeFullyQualifiedName(window) | |
748 }; | |
749 } | |
750 | |
751 /** | |
727 * Take all of the srcHoles and create data urls for the resources, placing | 752 * Take all of the srcHoles and create data urls for the resources, placing |
728 * them in |this.html|. Calls the callback when complete. | 753 * them in |this.html|. Calls the callback when complete. |
729 * | 754 * |
730 * @param {Function} callback The callback function. | 755 * @param {Function} callback The callback function. |
731 */ | 756 */ |
732 fillSrcHoles(callback) { | 757 fillSrcHoles(callback) { |
733 if (Object.keys(this.srcHoles).length == 0) { | 758 if (Object.keys(this.srcHoles).length == 0) { |
734 callback(this); | 759 callback(this); |
735 } else { | 760 } else { |
736 var index = Object.keys(this.srcHoles)[0]; | 761 var index = Object.keys(this.srcHoles)[0]; |
737 var src = this.srcHoles[index]; | 762 var src = this.srcHoles[index]; |
738 delete this.srcHoles[index]; | 763 delete this.srcHoles[index]; |
739 var serializer = this; | 764 var serializer = this; |
740 fetch(src).then(function(response) { | 765 fetch(src).then(function(response) { |
741 return response.blob(); | 766 return response.blob(); |
742 }).then(function(blob) { | 767 }).then(function(blob) { |
743 var reader = new FileReader(); | 768 var reader = new FileReader(); |
744 reader.onload = function(e) { | 769 reader.onload = function(e) { |
745 serializer.html[index] = e.target.result; | 770 serializer.html[index] = e.target.result; |
746 serializer.fillSrcHoles(callback); | 771 serializer.fillSrcHoles(callback); |
747 } | 772 } |
748 reader.readAsDataURL(blob); | 773 reader.readAsDataURL(blob); |
749 }).catch(function(error) { | 774 }).catch(function(error) { |
750 console.log(error); | 775 console.log(error); |
751 serializer.fillSrcHoles(callback); | 776 serializer.fillSrcHoles(callback); |
752 }); | 777 }); |
753 } | 778 } |
754 } | 779 } |
755 } | 780 } |
OLD | NEW |