[HTMLTemplateElement] Change template.dat serialization format
[WebKit-https.git] / LayoutTests / resources / dump-as-markup.js
1 /**
2  * There are three basic use cases of dumpAsMarkup
3  *
4  * 1. Dump the entire DOM when the page is loaded
5  *    When this script is included but no method of Markup is called,
6  *    it dumps the DOM of each frame loaded.
7  *
8  * 2. Dump the content of a specific element when the page is loaded
9  *    When Markup.setNodeToDump is called with some element or the id of some element,
10  *    it dumps the content of the specified element as supposed to the entire DOM tree.
11  *
12  * 3. Dump the content of a specific element multiple times while the page is loading
13  *    Calling Markup.dump would dump the content of the element set by setNodeToDump or the entire DOM.
14  *    Optionally specify the node to dump and the description for each call of dump.
15  */
16
17 if (window.testRunner)
18     testRunner.dumpAsText();
19
20 // Namespace
21 // FIXME: Rename dump-as-markup.js to dump-dom.js and Markup to DOM.
22 var Markup = {};
23
24 // The description of what this test is testing. Gets prepended to the dumped markup.
25 Markup.description = function(description)
26 {
27     Markup._test_description = description;
28 }
29
30 // Dumps the markup for the given node (HTML element if no node is given).
31 // Over-writes the body's content with the markup in layout test mode. Appends
32 // a pre element when loaded manually, in order to aid debugging.
33 Markup.dump = function(opt_node, opt_description)
34 {
35     if (typeof opt_node == 'string')
36         opt_node = document.getElementById(opt_node);
37
38     var node = opt_node || document
39     var markup = "";
40
41     Markup._dumpCalls++;
42
43     if (Markup._dumpCalls > 1 || opt_description) {
44         if (!opt_description)
45             opt_description = "Dump of markup " + Markup._dumpCalls
46         if (Markup._dumpCalls > 1)
47             markup += '\n';
48         markup += '\n' + opt_description + ':\n';
49     } else
50         Markup._firstCallDidNotHaveDescription = true;
51
52     markup += Markup.get(node);
53
54     if (!Markup._container) {
55         Markup._container = document.createElement('pre');
56         Markup._container.style.width = '100%';
57     }
58
59     if (Markup._dumpCalls == 2 && Markup._firstCallDidNotHaveDescription) {
60         var wrapper = Markup._container.getElementsByClassName('dump-as-markup-span')[0];
61         wrapper.insertBefore(document.createTextNode('\nDump of markup 1:\n'), wrapper.firstChild);
62     }
63
64     // FIXME: Have this respect testRunner.dumpChildFramesAsText?
65     // FIXME: Should we care about framesets?
66     // DocumentFragment doesn't have a getElementsByTagName method.
67     if (node.getElementsByTagName) {
68         var iframes = node.getElementsByTagName('iframe');
69         for (var i = 0; i < iframes.length; i++) {
70             markup += '\n\nFRAME ' + i + ':\n'
71             try {
72                 markup += Markup.get(iframes[i].contentDocument.body.parentElement);
73             } catch (e) {
74                 markup += 'FIXME: Add method to layout test controller to get access to cross-origin frames.';
75             }
76         }
77     }
78
79     if (Markup._test_description && Markup._dumpCalls == 1)
80         Markup._container.appendChild(document.createTextNode(Markup._test_description + '\n'))
81
82     var wrapper = document.createElement('span');
83     wrapper.className = 'dump-as-markup-span';
84     wrapper.appendChild(document.createTextNode(markup));
85     Markup._container.appendChild(wrapper);
86 }
87
88 Markup.noAutoDump = function()
89 {
90     window.removeEventListener('load', Markup.notifyDone, false);
91 }
92
93 Markup.waitUntilDone = function()
94 {
95     if (window.testRunner)
96         testRunner.waitUntilDone();
97     Markup.noAutoDump();
98 }
99
100 Markup.notifyDone = function()
101 {
102     // Need to waitUntilDone or some tests won't finish appending the markup before the text is dumped.
103     if (window.testRunner)
104         testRunner.waitUntilDone();
105
106     // If dump has already been called, don't bother to dump again
107     if (!Markup._dumpCalls)
108         Markup.dump();
109
110     // In non-layout test mode, append the results in a pre so that we don't
111     // clobber the test itself. But when in layout test mode, we don't want
112     // side effects from the test to be included in the results.
113     if (window.testRunner)
114         document.body.innerHTML = '';
115
116     document.body.appendChild(Markup._container);
117
118     if (window.testRunner)
119         testRunner.notifyDone();
120 }
121
122 Markup.useHTML5libOutputFormat = function()
123 {
124     Markup._useHTML5libOutputFormat = true;
125 }
126
127 Markup.get = function(node)
128 {
129     var shadowRootList = {};
130     var markup = Markup._getShadowHostIfPossible(node, 0, shadowRootList);
131     if (markup)
132         return markup.substring(1);
133
134     if (!node.firstChild)
135         return '| ';
136
137     // Don't print any markup for the root node.
138     for (var i = 0, len = node.childNodes.length; i < len; i++)
139         markup += Markup._get(node.childNodes[i], 0, shadowRootList);
140     return markup.substring(1);
141 }
142
143 // Returns the markup for the given node. To be used for cases where a test needs
144 // to get the markup but not clobber the whole page.
145 Markup._get = function(node, depth, shadowRootList)
146 {
147     var str = Markup._indent(depth);
148
149     switch (node.nodeType) {
150     case Node.DOCUMENT_TYPE_NODE:
151         str += '<!DOCTYPE ' + node.nodeName;
152         if (node.publicId || node.systemId) {
153             str += ' "' + node.publicId + '"';
154             str += ' "' + node.systemId + '"';
155         }
156         str += '>';
157         break;
158
159     case Node.COMMENT_NODE:
160         try {
161             str += '<!-- ' + node.nodeValue + ' -->';
162         } catch (e) {
163             str += '<!--  -->';
164         }
165          break;
166
167     case Node.PROCESSING_INSTRUCTION_NODE:
168         str += '<?' + node.nodeName + node.nodeValue + '>';
169         break;
170
171     case Node.CDATA_SECTION_NODE:
172         str += '<![CDATA[ ' + node.nodeValue + ' ]]>';
173         break;
174
175     case Node.TEXT_NODE:
176         str += '"' + Markup._getMarkupForTextNode(node) + '"';
177         break;
178
179     case Node.ELEMENT_NODE:
180         str += "<";
181         str += Markup._namespace(node)
182
183         if (node.localName && node.namespaceURI && node.namespaceURI != null)
184             str += node.localName;
185         else
186             str += Markup._toAsciiLowerCase(node.nodeName);
187
188         str += '>';
189
190         if (node.attributes) {
191             var attrNames = [];
192             var attrPos = {};
193             for (var j = 0; j < node.attributes.length; j += 1) {
194                 if (node.attributes[j].specified) {
195                     var name = Markup._namespace(node.attributes[j])
196                     name += node.attributes[j].localName || node.attributes[j].nodeName;
197                     attrNames.push(name);
198                     attrPos[name] = j;
199                 }
200             }
201             if (attrNames.length > 0) {
202               attrNames.sort();
203               for (var j = 0; j < attrNames.length; j += 1) {
204                 str += Markup._indent(depth + 1) + attrNames[j];
205                 str += '="' + node.attributes[attrPos[attrNames[j]]].nodeValue + '"';
206               }
207             }
208         }
209
210         if (!Markup._useHTML5libOutputFormat && window.internals) {
211             var pseudoId = window.internals.shadowPseudoId(node);
212             if (pseudoId)
213                 str += Markup._indent(depth + 1) + 'shadow:pseudoId="' + pseudoId + '"';
214         }
215
216         if (!Markup._useHTML5libOutputFormat)
217             if (node.nodeName == "INPUT" || node.nodeName == "TEXTAREA")
218                 str += Markup._indent(depth + 1) + 'this.value="' + node.value + '"';
219
220         break;
221     case Node.DOCUMENT_FRAGMENT_NODE:
222         if (shadowRootList && internals.address(node) in shadowRootList)
223           str += "<shadow:root>";
224         else
225           str += "content";
226     }
227
228     if (node.namespaceURI = 'http://www.w3.org/1999/xhtml' && node.tagName == 'TEMPLATE')
229         str += Markup._get(node.content, depth + 1, shadowRootList);
230
231     for (var i = 0, len = node.childNodes.length; i < len; i++) {
232         var selection = Markup._getSelectionMarker(node, i);
233         if (selection)
234             str += Markup._indent(depth + 1) + selection;
235
236         str += Markup._get(node.childNodes[i], depth + 1, shadowRootList);
237     }
238     
239     str += Markup._getShadowHostIfPossible(node, depth, shadowRootList);
240     
241     var selection = Markup._getSelectionMarker(node, i);
242     if (selection)
243         str += Markup._indent(depth + 1) + selection;
244
245     return str;
246 }
247
248 Markup._getShadowHostIfPossible = function (node, depth, shadowRootList)
249 {
250     if (!Markup._useHTML5libOutputFormat && node.nodeType == Node.ELEMENT_NODE && window.internals) {
251         var root = window.internals.shadowRoot(node);
252         if (root) {
253             shadowRootList[internals.address(root)] = true;
254             return Markup._get(root, depth + 1, shadowRootList);
255         }
256     }
257     return '';
258 }
259
260 Markup._namespace = function(node)
261 {
262     if (Markup._NAMESPACE_URI_MAP[node.namespaceURI])
263         return Markup._NAMESPACE_URI_MAP[node.namespaceURI] + ' ';
264     return '';
265 }
266
267 Markup._dumpCalls = 0
268
269 Markup._indent = function(depth)
270 {
271     return "\n| " + new Array(depth * 2 + 1).join(' ');
272 }
273
274 Markup._toAsciiLowerCase = function (str) {
275   var output = "";
276   for (var i = 0, len = this.length; i < len; ++i) {
277     if (str.charCodeAt(i) >= 0x41 && str.charCodeAt(i) <= 0x5A)
278       output += String.fromCharCode(str.charCodeAt(i) + 0x20)
279     else
280       output += str.charAt(i);
281   }
282   return output;
283 }
284
285 Markup._NAMESPACE_URI_MAP = {
286     "http://www.w3.org/2000/svg": "svg",
287     "http://www.w3.org/1998/Math/MathML": "math",
288     "http://www.w3.org/XML/1998/namespace": "xml",
289     "http://www.w3.org/2000/xmlns/": "xmlns",
290     "http://www.w3.org/1999/xlink": "xlink"
291 }
292
293 Markup._getSelectionFromNode = function(node)
294 {
295     return node.ownerDocument.defaultView ? node.ownerDocument.defaultView.getSelection() : null;
296 }
297
298 Markup._SELECTION_FOCUS = '<#selection-focus>';
299 Markup._SELECTION_ANCHOR = '<#selection-anchor>';
300 Markup._SELECTION_CARET = '<#selection-caret>';
301
302 Markup._getMarkupForTextNode = function(node)
303 {
304     innerMarkup = node.nodeValue;
305     var startOffset, endOffset, startText, endText;
306
307     var sel = Markup._getSelectionFromNode(node);
308     // Firefox doesn't have a sel in a display:none iframe.
309     // https://bugs.webkit.org/show_bug.cgi?id=43655
310     if (sel) {
311         if (node == sel.anchorNode && node == sel.focusNode) {
312             if (sel.isCollapsed) {
313                 startOffset = sel.anchorOffset;
314                 startText = Markup._SELECTION_CARET;
315             } else {
316                 if (sel.focusOffset > sel.anchorOffset) {
317                     startOffset = sel.anchorOffset;
318                     endOffset = sel.focusOffset;
319                     startText = Markup._SELECTION_ANCHOR;
320                     endText = Markup._SELECTION_FOCUS;
321                 } else {
322                     startOffset = sel.focusOffset;
323                     endOffset = sel.anchorOffset;
324                     startText = Markup._SELECTION_FOCUS;
325                     endText = Markup._SELECTION_ANCHOR;
326                 }
327             }
328         } else if (node == sel.focusNode) {
329             startOffset = sel.focusOffset;
330             startText = Markup._SELECTION_FOCUS;
331         } else if (node == sel.anchorNode) {
332             startOffset = sel.anchorOffset;
333             startText = Markup._SELECTION_ANCHOR;
334         }
335     }
336     
337     if (startText && endText)
338         innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset, endOffset) + endText + innerMarkup.substring(endOffset);                       
339     else if (startText)
340         innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset);
341
342     return innerMarkup;
343 }
344
345 Markup._getSelectionMarker = function(node, index)
346 {
347     if (node.nodeType != 1)
348         return '';
349
350     var sel = Markup._getSelectionFromNode(node);;
351
352     // Firefox doesn't have a sel in a display:none iframe.
353     // https://bugs.webkit.org/show_bug.cgi?id=43655
354     if (!sel)
355         return '';
356
357     if (index == sel.anchorOffset && node == sel.anchorNode) {
358         if (sel.isCollapsed)
359             return Markup._SELECTION_CARET;
360         else
361             return Markup._SELECTION_ANCHOR;
362     } else if (index == sel.focusOffset && node == sel.focusNode)
363         return Markup._SELECTION_FOCUS;
364
365     return '';
366 }
367
368 window.addEventListener('load', Markup.notifyDone, false);