Move helper files for iOS touch gestures into LayoutTests/resources
[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         // FIXME: The actual MarkupAccumulator in WebKit handles these quite differently.
153         // Should we change to match that format? Would probably need to change test results,
154         // but it could help us distinguish empty strings from missing properties and
155         // would include internalSubset, which this omits.
156         if (node.publicId || node.systemId) {
157             str += ' "' + (node.publicId || '') + '"';
158             str += ' "' + (node.systemId || '') + '"';
159         }
160         str += '>';
161         break;
162
163     case Node.COMMENT_NODE:
164         try {
165             str += '<!-- ' + node.nodeValue + ' -->';
166         } catch (e) {
167             str += '<!--  -->';
168         }
169          break;
170
171     case Node.PROCESSING_INSTRUCTION_NODE:
172         str += '<?' + node.nodeName + node.nodeValue + '>';
173         break;
174
175     case Node.CDATA_SECTION_NODE:
176         str += '<![CDATA[ ' + node.nodeValue + ' ]]>';
177         break;
178
179     case Node.TEXT_NODE:
180         str += '"' + Markup._getMarkupForTextNode(node) + '"';
181         break;
182
183     case Node.ELEMENT_NODE:
184         str += "<";
185         str += Markup._namespace(node)
186
187         if (node.localName && node.namespaceURI && node.namespaceURI != null)
188             str += node.localName;
189         else
190             str += Markup._toAsciiLowerCase(node.nodeName);
191
192         str += '>';
193
194         if (node.attributes) {
195             var attrNames = [];
196             var attrPos = {};
197             for (var j = 0; j < node.attributes.length; j += 1) {
198                 if (node.attributes[j].specified) {
199                     var name = Markup._namespace(node.attributes[j])
200                     name += node.attributes[j].localName || node.attributes[j].nodeName;
201                     attrNames.push(name);
202                     attrPos[name] = j;
203                 }
204             }
205             if (attrNames.length > 0) {
206               attrNames.sort();
207               for (var j = 0; j < attrNames.length; j += 1) {
208                 str += Markup._indent(depth + 1) + attrNames[j];
209                 str += '="' + node.attributes[attrPos[attrNames[j]]].nodeValue + '"';
210               }
211             }
212         }
213
214         if (!Markup._useHTML5libOutputFormat && window.internals) {
215             var pseudoId = window.internals.shadowPseudoId(node);
216             if (pseudoId)
217                 str += Markup._indent(depth + 1) + 'shadow:pseudoId="' + pseudoId + '"';
218         }
219
220         if (!Markup._useHTML5libOutputFormat)
221             if (node.nodeName == "INPUT" || node.nodeName == "TEXTAREA")
222                 str += Markup._indent(depth + 1) + 'this.value="' + node.value + '"';
223
224         break;
225     case Node.DOCUMENT_FRAGMENT_NODE:
226         if (shadowRootList && window.internals && internals.address(node) in shadowRootList)
227           str += "<shadow:root>";
228         else
229           str += "content";
230     }
231
232     if (node.namespaceURI = 'http://www.w3.org/1999/xhtml' && node.tagName == 'TEMPLATE')
233         str += Markup._get(node.content, depth + 1, shadowRootList);
234
235     for (var i = 0, len = node.childNodes.length; i < len; i++) {
236         var selection = Markup._getSelectionMarker(node, i);
237         if (selection)
238             str += Markup._indent(depth + 1) + selection;
239
240         str += Markup._get(node.childNodes[i], depth + 1, shadowRootList);
241     }
242     
243     str += Markup._getShadowHostIfPossible(node, depth, shadowRootList);
244     
245     var selection = Markup._getSelectionMarker(node, i);
246     if (selection)
247         str += Markup._indent(depth + 1) + selection;
248
249     return str;
250 }
251
252 Markup._getShadowHostIfPossible = function (node, depth, shadowRootList)
253 {
254     if (!Markup._useHTML5libOutputFormat && node.nodeType == Node.ELEMENT_NODE && window.internals) {
255         var root = window.internals.shadowRoot(node);
256         if (root) {
257             shadowRootList[internals.address(root)] = true;
258             return Markup._get(root, depth + 1, shadowRootList);
259         }
260     }
261     return '';
262 }
263
264 Markup._namespace = function(node)
265 {
266     if (Markup._NAMESPACE_URI_MAP[node.namespaceURI])
267         return Markup._NAMESPACE_URI_MAP[node.namespaceURI] + ' ';
268     return '';
269 }
270
271 Markup._dumpCalls = 0
272
273 Markup._indent = function(depth)
274 {
275     return "\n| " + new Array(depth * 2 + 1).join(' ');
276 }
277
278 Markup._toAsciiLowerCase = function (str) {
279   var output = "";
280   for (var i = 0, len = this.length; i < len; ++i) {
281     if (str.charCodeAt(i) >= 0x41 && str.charCodeAt(i) <= 0x5A)
282       output += String.fromCharCode(str.charCodeAt(i) + 0x20)
283     else
284       output += str.charAt(i);
285   }
286   return output;
287 }
288
289 Markup._NAMESPACE_URI_MAP = {
290     "http://www.w3.org/2000/svg": "svg",
291     "http://www.w3.org/1998/Math/MathML": "math",
292     "http://www.w3.org/XML/1998/namespace": "xml",
293     "http://www.w3.org/2000/xmlns/": "xmlns",
294     "http://www.w3.org/1999/xlink": "xlink"
295 }
296
297 Markup._getSelectionFromNode = function(node)
298 {
299     return node.ownerDocument.defaultView ? node.ownerDocument.defaultView.getSelection() : null;
300 }
301
302 Markup._SELECTION_FOCUS = '<#selection-focus>';
303 Markup._SELECTION_ANCHOR = '<#selection-anchor>';
304 Markup._SELECTION_CARET = '<#selection-caret>';
305
306 Markup._getMarkupForTextNode = function(node)
307 {
308     innerMarkup = node.nodeValue;
309     var startOffset, endOffset, startText, endText;
310
311     var sel = Markup._getSelectionFromNode(node);
312     // Firefox doesn't have a sel in a display:none iframe.
313     // https://bugs.webkit.org/show_bug.cgi?id=43655
314     if (sel) {
315         if (node == sel.anchorNode && node == sel.focusNode) {
316             if (sel.isCollapsed) {
317                 startOffset = sel.anchorOffset;
318                 startText = Markup._SELECTION_CARET;
319             } else {
320                 if (sel.focusOffset > sel.anchorOffset) {
321                     startOffset = sel.anchorOffset;
322                     endOffset = sel.focusOffset;
323                     startText = Markup._SELECTION_ANCHOR;
324                     endText = Markup._SELECTION_FOCUS;
325                 } else {
326                     startOffset = sel.focusOffset;
327                     endOffset = sel.anchorOffset;
328                     startText = Markup._SELECTION_FOCUS;
329                     endText = Markup._SELECTION_ANCHOR;
330                 }
331             }
332         } else if (node == sel.focusNode) {
333             startOffset = sel.focusOffset;
334             startText = Markup._SELECTION_FOCUS;
335         } else if (node == sel.anchorNode) {
336             startOffset = sel.anchorOffset;
337             startText = Markup._SELECTION_ANCHOR;
338         }
339     }
340     
341     if (startText && endText)
342         innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset, endOffset) + endText + innerMarkup.substring(endOffset);                       
343     else if (startText)
344         innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset);
345
346     return innerMarkup;
347 }
348
349 Markup._getSelectionMarker = function(node, index)
350 {
351     if (node.nodeType != 1)
352         return '';
353
354     var sel = Markup._getSelectionFromNode(node);;
355
356     // Firefox doesn't have a sel in a display:none iframe.
357     // https://bugs.webkit.org/show_bug.cgi?id=43655
358     if (!sel)
359         return '';
360
361     if (index == sel.anchorOffset && node == sel.anchorNode) {
362         if (sel.isCollapsed)
363             return Markup._SELECTION_CARET;
364         else
365             return Markup._SELECTION_ANCHOR;
366     } else if (index == sel.focusOffset && node == sel.focusNode)
367         return Markup._SELECTION_FOCUS;
368
369     return '';
370 }
371
372 window.addEventListener('load', Markup.notifyDone, false);