Generated should not be supported for things with a shadow
[WebKit-https.git] / Source / WebCore / xml / XMLViewer.js
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17  * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 var nodeParentPairs = [];
30
31 // Script entry point.
32
33 function prepareWebKitXMLViewer(noStyleMessage)
34 {
35     var html = createHTMLElement('html');
36     var head = createHTMLElement('head');
37     html.appendChild(head);
38     var style = createHTMLElement('style');
39     style.id = 'xml-viewer-style';
40     head.appendChild(style);
41     var body = createHTMLElement('body');
42     html.appendChild(body);
43     var sourceXML = createHTMLElement('div');
44     sourceXML.id = 'webkit-xml-viewer-source-xml';
45     body.appendChild(sourceXML);
46
47     var child;
48     while (child = document.firstChild) {
49         document.removeChild(child);
50         if (child.nodeType != Node.DOCUMENT_TYPE_NODE)
51             sourceXML.appendChild(child);
52     }
53     document.appendChild(html);
54
55     var header = createHTMLElement('div');
56     body.appendChild(header);
57     header.classList.add('header');
58     var headerSpan = createHTMLElement('span');
59     header.appendChild(headerSpan);
60     headerSpan.textContent = noStyleMessage;
61     header.appendChild(createHTMLElement('br'));
62
63     var tree = createHTMLElement('div');
64     body.appendChild(tree);
65     tree.classList.add('pretty-print');
66     tree.id = 'tree';
67     window.onload = sourceXMLLoaded;
68 }
69
70 function sourceXMLLoaded()
71 {
72     var sourceXML = document.getElementById('webkit-xml-viewer-source-xml');
73     if (!sourceXML)
74         return; // Stop if some XML tree extension is already processing this document
75     //var style = document.head.firstChild;
76     //document.head.removeChild(style);
77     //document.head.appendChild(style);
78     var root = document.getElementById('tree');
79
80     for (var child = sourceXML.firstChild; child; child = child.nextSibling)
81         nodeParentPairs.push({parentElement: root, node: child});
82
83     for (var i = 0; i < nodeParentPairs.length; i++)
84         processNode(nodeParentPairs[i].parentElement, nodeParentPairs[i].node);
85
86     drawArrows();
87     initButtons();
88
89     if (typeof(onAfterWebkitXMLViewerLoaded) == 'function')
90       onAfterWebkitXMLViewerLoaded();
91 }
92
93 // Tree processing.
94
95 function processNode(parentElement, node)
96 {
97     if (!processNode.processorsMap) {
98         processNode.processorsMap = {};
99         processNode.processorsMap[Node.PROCESSING_INSTRUCTION_NODE] = processProcessingInstruction;
100         processNode.processorsMap[Node.ELEMENT_NODE] = processElement;
101         processNode.processorsMap[Node.COMMENT_NODE] = processComment;
102         processNode.processorsMap[Node.TEXT_NODE] = processText;
103         processNode.processorsMap[Node.CDATA_SECTION_NODE] = processCDATA;
104     }
105     if (processNode.processorsMap[node.nodeType])
106         processNode.processorsMap[node.nodeType].call(this, parentElement, node);
107 }
108
109 function processElement(parentElement, node)
110 {
111     if (!node.firstChild)
112         processEmptyElement(parentElement, node);
113     else {
114         var child = node.firstChild;
115         if (child.nodeType == Node.TEXT_NODE && isShort(child.nodeValue) && !child.nextSibling)
116             processShortTextOnlyElement(parentElement, node);
117         else
118             processComplexElement(parentElement, node);
119     }
120 }
121
122 function processEmptyElement(parentElement, node)
123 {
124     var line = createLine();
125     line.appendChild(createTag(node, false, true));
126     parentElement.appendChild(line);
127 }
128
129 function processShortTextOnlyElement(parentElement, node)
130 {
131     var line = createLine();
132     line.appendChild(createTag(node, false, false));
133     for (var child = node.firstChild; child; child = child.nextSibling)
134         line.appendChild(createText(child.nodeValue));
135     line.appendChild(createTag(node, true, false));
136     parentElement.appendChild(line);
137 }
138
139 function processComplexElement(parentElement, node)
140 {
141     var collapsible = createCollapsible();
142
143     collapsible.expanded.start.appendChild(createTag(node, false, false));
144     for (var child = node.firstChild; child; child = child.nextSibling)
145         nodeParentPairs.push({parentElement: collapsible.expanded.content, node: child});
146     collapsible.expanded.end.appendChild(createTag(node, true, false));
147
148     collapsible.collapsed.content.appendChild(createTag(node, false, false));
149     collapsible.collapsed.content.appendChild(createText('...'));
150     collapsible.collapsed.content.appendChild(createTag(node, true, false));
151     parentElement.appendChild(collapsible);
152 }
153
154 function processComment(parentElement, node)
155 {
156     if (isShort(node.nodeValue)) {
157         var line = createLine();
158         line.appendChild(createComment('<!-- ' + node.nodeValue + ' -->'));
159         parentElement.appendChild(line);
160     } else {
161         var collapsible = createCollapsible();
162
163         collapsible.expanded.start.appendChild(createComment('<!--'));
164         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
165         collapsible.expanded.end.appendChild(createComment('-->'));
166
167         collapsible.collapsed.content.appendChild(createComment('<!--'));
168         collapsible.collapsed.content.appendChild(createComment('...'));
169         collapsible.collapsed.content.appendChild(createComment('-->'));
170         parentElement.appendChild(collapsible);
171     }
172 }
173
174 function processCDATA(parentElement, node)
175 {
176     if (isShort(node.nodeValue)) {
177         var line = createLine();
178         line.appendChild(createText('<![CDATA[ ' + node.nodeValue + ' ]]>'));
179         parentElement.appendChild(line);
180     } else {
181         var collapsible = createCollapsible();
182
183         collapsible.expanded.start.appendChild(createText('<![CDATA['));
184         collapsible.expanded.content.appendChild(createText(node.nodeValue));
185         collapsible.expanded.end.appendChild(createText(']]>'));
186
187         collapsible.collapsed.content.appendChild(createText('<![CDATA['));
188         collapsible.collapsed.content.appendChild(createText('...'));
189         collapsible.collapsed.content.appendChild(createText(']]>'));
190         parentElement.appendChild(collapsible);
191     }
192 }
193
194 function processProcessingInstruction(parentElement, node)
195 {
196     if (isShort(node.nodeValue)) {
197         var line = createLine();
198         line.appendChild(createComment('<?' + node.nodeName + ' ' + node.nodeValue + '?>'));
199         parentElement.appendChild(line);
200     } else {
201         var collapsible = createCollapsible();
202
203         collapsible.expanded.start.appendChild(createComment('<?' + node.nodeName));
204         collapsible.expanded.content.appendChild(createComment(node.nodeValue));
205         collapsible.expanded.end.appendChild(createComment('?>'));
206
207         collapsible.collapsed.content.appendChild(createComment('<?' + node.nodeName));
208         collapsible.collapsed.content.appendChild(createComment('...'));
209         collapsible.collapsed.content.appendChild(createComment('?>'));
210         parentElement.appendChild(collapsible);
211     }
212 }
213
214 function processText(parentElement, node)
215 {
216     parentElement.appendChild(createText(node.nodeValue));
217 }
218
219 // Processing utils.
220
221 function trim(value)
222 {
223     return value.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
224 }
225
226 function isShort(value)
227 {
228     return trim(value).length <= 50;
229 }
230
231 // Tree rendering.
232
233 function createHTMLElement(elementName)
234 {
235     return document.createElementNS('http://www.w3.org/1999/xhtml', elementName)
236 }
237
238 function createCollapsible()
239 {
240     var collapsible = createHTMLElement('div');
241     collapsible.classList.add('collapsible');
242     collapsible.expanded = createHTMLElement('div');
243     collapsible.expanded.classList.add('expanded');
244     collapsible.appendChild(collapsible.expanded);
245
246     collapsible.expanded.start = createLine();
247     collapsible.expanded.start.appendChild(createCollapseButton());
248     collapsible.expanded.appendChild(collapsible.expanded.start);
249
250     collapsible.expanded.content = createHTMLElement('div');
251     collapsible.expanded.content.classList.add('collapsible-content');
252     collapsible.expanded.appendChild(collapsible.expanded.content);
253
254     collapsible.expanded.end = createLine();
255     collapsible.expanded.appendChild(collapsible.expanded.end);
256
257     collapsible.collapsed = createHTMLElement('div');
258     collapsible.collapsed.classList.add('collapsed');
259     collapsible.collapsed.classList.add('hidden');
260     collapsible.appendChild(collapsible.collapsed);
261     collapsible.collapsed.content = createLine();
262     collapsible.collapsed.content.appendChild(createExpandButton());
263     collapsible.collapsed.appendChild(collapsible.collapsed.content);
264
265     return collapsible;
266 }
267
268 function createButton()
269 {
270     var button = createHTMLElement('span');
271     button.classList.add('button');
272     return button;
273 }
274
275 function createCollapseButton(str)
276 {
277     var button = createButton();
278     button.classList.add('collapse-button');
279     return button;
280 }
281
282 function createExpandButton(str)
283 {
284     var button = createButton();
285     button.classList.add('expand-button');
286     return button;
287 }
288
289 function createComment(commentString)
290 {
291     var comment = createHTMLElement('span');
292     comment.classList.add('comment');
293     comment.classList.add('webkit-html-comment');
294     comment.textContent = commentString;
295     return comment;
296 }
297
298 function createText(value)
299 {
300     var text = createHTMLElement('span');
301     text.textContent = trim(value);
302     text.classList.add('text');
303     return text;
304 }
305
306 function createLine()
307 {
308     var line = createHTMLElement('div');
309     line.classList.add('line');
310     return line;
311 }
312
313 function createTag(node, isClosing, isEmpty)
314 {
315     var tag = createHTMLElement('span');
316     tag.classList.add('webkit-html-tag');
317
318     var stringBeforeAttrs = '<';
319     if (isClosing)
320         stringBeforeAttrs += '/';
321     stringBeforeAttrs += node.nodeName;
322     var textBeforeAttrs = document.createTextNode(stringBeforeAttrs);
323     tag.appendChild(textBeforeAttrs);
324
325     if (!isClosing) {
326         for (var i = 0; i < node.attributes.length; i++)
327             tag.appendChild(createAttribute(node.attributes[i]));
328     }
329
330     var stringAfterAttrs = '';
331     if (isEmpty)
332         stringAfterAttrs += '/';
333     stringAfterAttrs += '>';
334     var textAfterAttrs = document.createTextNode(stringAfterAttrs);
335     tag.appendChild(textAfterAttrs);
336
337     return tag;
338 }
339
340 function createAttribute(attributeNode)
341 {
342     var attribute = createHTMLElement('span');
343     attribute.classList.add('webkit-html-attribute');
344
345     var attributeName = createHTMLElement('span');
346     attributeName.classList.add('webkit-html-attribute-name');
347     attributeName.textContent = attributeNode.name;
348
349     var textBefore = document.createTextNode(' ');
350     var textBetween = document.createTextNode('="');
351
352     var attributeValue = createHTMLElement('span');
353     attributeValue.classList.add('webkit-html-attribute-value');
354     attributeValue.textContent = attributeNode.value;
355
356     var textAfter = document.createTextNode('"');
357
358     attribute.appendChild(textBefore);
359     attribute.appendChild(attributeName);
360     attribute.appendChild(textBetween);
361     attribute.appendChild(attributeValue);
362     attribute.appendChild(textAfter);
363     return attribute;
364 }
365
366 // Tree behaviour.
367
368 function drawArrows()
369 {
370     var ctx = document.getCSSCanvasContext("2d", "arrowRight", 10, 11);
371
372     ctx.fillStyle = "rgb(90,90,90)";
373     ctx.beginPath();
374     ctx.moveTo(0, 0);
375     ctx.lineTo(0, 8);
376     ctx.lineTo(7, 4);
377     ctx.lineTo(0, 0);
378     ctx.fill();
379     ctx.closePath();
380
381     var ctx = document.getCSSCanvasContext("2d", "arrowDown", 10, 10);
382
383     ctx.fillStyle = "rgb(90,90,90)";
384     ctx.beginPath();
385     ctx.moveTo(0, 0);
386     ctx.lineTo(8, 0);
387     ctx.lineTo(4, 7);
388     ctx.lineTo(0, 0);
389     ctx.fill();
390     ctx.closePath();
391 }
392
393 function expandFunction(sectionId)
394 {
395     return function()
396     {
397         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded';
398         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed hidden';
399     };
400 }
401
402 function collapseFunction(sectionId)
403 {
404     return function()
405     {
406         document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded hidden';
407         document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed';
408     };
409 }
410
411 function initButtons()
412 {
413     var sections = document.querySelectorAll('.collapsible');
414     for (var i = 0; i < sections.length; i++) {
415         var sectionId = 'collapsible' + i;
416         sections[i].id = sectionId;
417
418         var expandedPart = sections[i].querySelector('#' + sectionId + ' > .expanded');
419         var collapseButton = expandedPart.querySelector('.collapse-button');
420         collapseButton.onclick = collapseFunction(sectionId);
421         collapseButton.onmousedown = handleButtonMouseDown;
422
423         var collapsedPart = sections[i].querySelector('#' + sectionId + ' > .collapsed');
424         var expandButton = collapsedPart.querySelector('.expand-button');
425         expandButton.onclick = expandFunction(sectionId);
426         expandButton.onmousedown = handleButtonMouseDown;
427     }
428
429 }
430
431 function handleButtonMouseDown(e)
432 {
433    // To prevent selection on double click
434    e.preventDefault();
435 }