• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5"use strict";
6
7installClass("Document", function(DocumentPrototype) {
8    // FIXME: The stylesheet should be defined in a separate css file
9    var styleSheet = [
10        "div.header {",
11        "    border-bottom: 2px solid black;",
12        "    padding-bottom: 5px;",
13        "    margin: 10px;",
14        "}",
15        "",
16        "div.collapsible > div.hidden {",
17        "    display:none;",
18        "}",
19        "",
20        ".pretty-print {",
21        "    margin-top: 1em;",
22        "    margin-left: 20px;",
23        "    font-family: monospace;",
24        "    font-size: 13px;",
25        "}",
26        "",
27        "#webkit-xml-viewer-source-xml {",
28        "    display: none;",
29        "}",
30        "",
31        ".collapsible-content {",
32        "    margin-left: 1em;",
33        "}",
34        ".comment {",
35        "    white-space: pre;",
36        "}",
37        "",
38        ".button {",
39        "    -webkit-user-select: none;",
40        "    cursor: pointer;",
41        "    display: inline-block;",
42        "    margin-left: -10px;",
43        "    width: 10px;",
44        "    background-repeat: no-repeat;",
45        "    background-position: left top;",
46        "    vertical-align: bottom;",
47        "}",
48        "",
49        ".collapse-button {",
50        "    background-image: -webkit-canvas(arrowDown);",
51        "    height: 10px;",
52        "}",
53        "",
54        ".expand-button {",
55        "    background-image: -webkit-canvas(arrowRight);",
56        "    height: 11px;",
57        "}"].join('');
58    var nodeParentPairs = [];
59    var tree;
60
61    function prepareWebKitXMLViewer(noStyleMessage)
62    {
63        var html = createHTMLElement('html');
64        var head = createHTMLElement('head');
65        html.appendChild(head);
66        var style = createHTMLElement('style');
67        style.id = 'xml-viewer-style';
68        style.appendChild(document.createTextNode(styleSheet));
69        head.appendChild(style);
70        var body = createHTMLElement('body');
71        html.appendChild(body);
72        var sourceXML = createHTMLElement('div');
73        sourceXML.id = 'webkit-xml-viewer-source-xml';
74        body.appendChild(sourceXML);
75
76        var child;
77        while (child = document.firstChild) {
78            document.removeChild(child);
79            if (child.nodeType != Node.DOCUMENT_TYPE_NODE)
80                sourceXML.appendChild(child);
81        }
82        document.appendChild(html);
83
84        var header = createHTMLElement('div');
85        body.appendChild(header);
86        header.classList.add('header');
87        var headerSpan = createHTMLElement('span');
88        header.appendChild(headerSpan);
89        headerSpan.textContent = noStyleMessage;
90        header.appendChild(createHTMLElement('br'));
91
92        tree = createHTMLElement('div');
93        body.appendChild(tree);
94        tree.classList.add('pretty-print');
95        window.onload = sourceXMLLoaded;
96    }
97
98    function sourceXMLLoaded()
99    {
100        var sourceXML = document.getElementById('webkit-xml-viewer-source-xml');
101        if (!sourceXML)
102            return; // Stop if some XML tree extension is already processing this document
103
104        for (var child = sourceXML.firstChild; child; child = child.nextSibling)
105            nodeParentPairs.push({parentElement: tree, node: child});
106
107        for (var i = 0; i < nodeParentPairs.length; i++)
108            processNode(nodeParentPairs[i].parentElement, nodeParentPairs[i].node);
109
110        drawArrows();
111        initButtons();
112
113        return false;
114    }
115
116    // Tree processing.
117
118    function processNode(parentElement, node)
119    {
120        var map = processNode.processorsMap;
121        if (!map) {
122            map = {};
123            processNode.processorsMap = map;
124            map[Node.PROCESSING_INSTRUCTION_NODE] = processProcessingInstruction;
125            map[Node.ELEMENT_NODE] = processElement;
126            map[Node.COMMENT_NODE] = processComment;
127            map[Node.TEXT_NODE] = processText;
128            map[Node.CDATA_SECTION_NODE] = processCDATA;
129        }
130        if (processNode.processorsMap[node.nodeType])
131            processNode.processorsMap[node.nodeType].call(this, parentElement, node);
132    }
133
134    function processElement(parentElement, node)
135    {
136        if (!node.firstChild)
137            processEmptyElement(parentElement, node);
138        else {
139            var child = node.firstChild;
140            if (child.nodeType == Node.TEXT_NODE && isShort(child.nodeValue) && !child.nextSibling)
141                processShortTextOnlyElement(parentElement, node);
142            else
143                processComplexElement(parentElement, node);
144        }
145    }
146
147    function processEmptyElement(parentElement, node)
148    {
149        var line = createLine();
150        line.appendChild(createTag(node, false, true));
151        parentElement.appendChild(line);
152    }
153
154    function processShortTextOnlyElement(parentElement, node)
155    {
156        var line = createLine();
157        line.appendChild(createTag(node, false, false));
158        for (var child = node.firstChild; child; child = child.nextSibling)
159            line.appendChild(createText(child.nodeValue));
160        line.appendChild(createTag(node, true, false));
161        parentElement.appendChild(line);
162    }
163
164    function processComplexElement(parentElement, node)
165    {
166        var collapsible = createCollapsible();
167
168        collapsible.expanded.start.appendChild(createTag(node, false, false));
169        for (var child = node.firstChild; child; child = child.nextSibling)
170            nodeParentPairs.push({parentElement: collapsible.expanded.content, node: child});
171        collapsible.expanded.end.appendChild(createTag(node, true, false));
172
173        collapsible.collapsed.content.appendChild(createTag(node, false, false));
174        collapsible.collapsed.content.appendChild(createText('...'));
175        collapsible.collapsed.content.appendChild(createTag(node, true, false));
176        parentElement.appendChild(collapsible);
177    }
178
179    function processComment(parentElement, node)
180    {
181        if (isShort(node.nodeValue)) {
182            var line = createLine();
183            line.appendChild(createComment('<!-- ' + node.nodeValue + ' -->'));
184            parentElement.appendChild(line);
185        } else {
186            var collapsible = createCollapsible();
187
188            collapsible.expanded.start.appendChild(createComment('<!--'));
189            collapsible.expanded.content.appendChild(createComment(node.nodeValue));
190            collapsible.expanded.end.appendChild(createComment('-->'));
191
192            collapsible.collapsed.content.appendChild(createComment('<!--'));
193            collapsible.collapsed.content.appendChild(createComment('...'));
194            collapsible.collapsed.content.appendChild(createComment('-->'));
195            parentElement.appendChild(collapsible);
196        }
197    }
198
199    function processCDATA(parentElement, node)
200    {
201        if (isShort(node.nodeValue)) {
202            var line = createLine();
203            line.appendChild(createText('<![CDATA[ ' + node.nodeValue + ' ]]>'));
204            parentElement.appendChild(line);
205        } else {
206            var collapsible = createCollapsible();
207
208            collapsible.expanded.start.appendChild(createText('<![CDATA['));
209            collapsible.expanded.content.appendChild(createText(node.nodeValue));
210            collapsible.expanded.end.appendChild(createText(']]>'));
211
212            collapsible.collapsed.content.appendChild(createText('<![CDATA['));
213            collapsible.collapsed.content.appendChild(createText('...'));
214            collapsible.collapsed.content.appendChild(createText(']]>'));
215            parentElement.appendChild(collapsible);
216        }
217    }
218
219    function processProcessingInstruction(parentElement, node)
220    {
221        if (isShort(node.nodeValue)) {
222            var line = createLine();
223            line.appendChild(createComment('<?' + node.nodeName + ' ' + node.nodeValue + '?>'));
224            parentElement.appendChild(line);
225        } else {
226            var collapsible = createCollapsible();
227
228            collapsible.expanded.start.appendChild(createComment('<?' + node.nodeName));
229            collapsible.expanded.content.appendChild(createComment(node.nodeValue));
230            collapsible.expanded.end.appendChild(createComment('?>'));
231
232            collapsible.collapsed.content.appendChild(createComment('<?' + node.nodeName));
233            collapsible.collapsed.content.appendChild(createComment('...'));
234            collapsible.collapsed.content.appendChild(createComment('?>'));
235            parentElement.appendChild(collapsible);
236        }
237    }
238
239    function processText(parentElement, node)
240    {
241        parentElement.appendChild(createText(node.nodeValue));
242    }
243
244    // Processing utils.
245
246    function trim(value)
247    {
248        return value.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
249    }
250
251    function isShort(value)
252    {
253        return trim(value).length <= 50;
254    }
255
256    // Tree rendering.
257
258    function createHTMLElement(elementName)
259    {
260        return document.createElementNS('http://www.w3.org/1999/xhtml', elementName)
261    }
262
263    function createCollapsible()
264    {
265        var collapsible = createHTMLElement('div');
266        collapsible.classList.add('collapsible');
267        collapsible.expanded = createHTMLElement('div');
268        collapsible.expanded.classList.add('expanded');
269        collapsible.appendChild(collapsible.expanded);
270
271        collapsible.expanded.start = createLine();
272        collapsible.expanded.start.appendChild(createCollapseButton());
273        collapsible.expanded.appendChild(collapsible.expanded.start);
274
275        collapsible.expanded.content = createHTMLElement('div');
276        collapsible.expanded.content.classList.add('collapsible-content');
277        collapsible.expanded.appendChild(collapsible.expanded.content);
278
279        collapsible.expanded.end = createLine();
280        collapsible.expanded.appendChild(collapsible.expanded.end);
281
282        collapsible.collapsed = createHTMLElement('div');
283        collapsible.collapsed.classList.add('collapsed');
284        collapsible.collapsed.classList.add('hidden');
285        collapsible.appendChild(collapsible.collapsed);
286        collapsible.collapsed.content = createLine();
287        collapsible.collapsed.content.appendChild(createExpandButton());
288        collapsible.collapsed.appendChild(collapsible.collapsed.content);
289
290        return collapsible;
291    }
292
293    function createButton()
294    {
295        var button = createHTMLElement('span');
296        button.classList.add('button');
297        return button;
298    }
299
300    function createCollapseButton(str)
301    {
302        var button = createButton();
303        button.classList.add('collapse-button');
304        return button;
305    }
306
307    function createExpandButton(str)
308    {
309        var button = createButton();
310        button.classList.add('expand-button');
311        return button;
312    }
313
314    function createComment(commentString)
315    {
316        var comment = createHTMLElement('span');
317        comment.classList.add('comment');
318        comment.classList.add('html-comment');
319        comment.textContent = commentString;
320        return comment;
321    }
322
323    function createText(value)
324    {
325        var text = createHTMLElement('span');
326        text.textContent = trim(value);
327        text.classList.add('text');
328        return text;
329    }
330
331    function createLine()
332    {
333        var line = createHTMLElement('div');
334        line.classList.add('line');
335        return line;
336    }
337
338    function createTag(node, isClosing, isEmpty)
339    {
340        var tag = createHTMLElement('span');
341        tag.classList.add('html-tag');
342
343        var stringBeforeAttrs = '<';
344        if (isClosing)
345            stringBeforeAttrs += '/';
346        stringBeforeAttrs += node.nodeName;
347        var textBeforeAttrs = document.createTextNode(stringBeforeAttrs);
348        tag.appendChild(textBeforeAttrs);
349
350        if (!isClosing) {
351            for (var i = 0; i < node.attributes.length; i++)
352                tag.appendChild(createAttribute(node.attributes[i]));
353        }
354
355        var stringAfterAttrs = '';
356        if (isEmpty)
357            stringAfterAttrs += '/';
358        stringAfterAttrs += '>';
359        var textAfterAttrs = document.createTextNode(stringAfterAttrs);
360        tag.appendChild(textAfterAttrs);
361
362        return tag;
363    }
364
365    function createAttribute(attributeNode)
366    {
367        var attribute = createHTMLElement('span');
368        attribute.classList.add('html-attribute');
369
370        var attributeName = createHTMLElement('span');
371        attributeName.classList.add('html-attribute-name');
372        attributeName.textContent = attributeNode.name;
373
374        var textBefore = document.createTextNode(' ');
375        var textBetween = document.createTextNode('="');
376
377        var attributeValue = createHTMLElement('span');
378        attributeValue.classList.add('html-attribute-value');
379        attributeValue.textContent = attributeNode.value;
380
381        var textAfter = document.createTextNode('"');
382
383        attribute.appendChild(textBefore);
384        attribute.appendChild(attributeName);
385        attribute.appendChild(textBetween);
386        attribute.appendChild(attributeValue);
387        attribute.appendChild(textAfter);
388        return attribute;
389    }
390
391    // Tree behaviour.
392
393    function drawArrows()
394    {
395        var ctx = document.getCSSCanvasContext("2d", "arrowRight", 10, 11);
396
397        ctx.fillStyle = "rgb(90,90,90)";
398        ctx.beginPath();
399        ctx.moveTo(0, 0);
400        ctx.lineTo(0, 8);
401        ctx.lineTo(7, 4);
402        ctx.lineTo(0, 0);
403        ctx.fill();
404        ctx.closePath();
405
406        var ctx = document.getCSSCanvasContext("2d", "arrowDown", 10, 10);
407
408        ctx.fillStyle = "rgb(90,90,90)";
409        ctx.beginPath();
410        ctx.moveTo(0, 0);
411        ctx.lineTo(8, 0);
412        ctx.lineTo(4, 7);
413        ctx.lineTo(0, 0);
414        ctx.fill();
415        ctx.closePath();
416    }
417
418    function expandFunction(sectionId)
419    {
420        return function()
421        {
422            document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded';
423            document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed hidden';
424        };
425    }
426
427    function collapseFunction(sectionId)
428    {
429        return function()
430        {
431            document.querySelector('#' + sectionId + ' > .expanded').className = 'expanded hidden';
432            document.querySelector('#' + sectionId + ' > .collapsed').className = 'collapsed';
433        };
434    }
435
436    function initButtons()
437    {
438        var sections = document.querySelectorAll('.collapsible');
439        for (var i = 0; i < sections.length; i++) {
440            var sectionId = 'collapsible' + i;
441            sections[i].id = sectionId;
442
443            var expandedPart = sections[i].querySelector('#' + sectionId + ' > .expanded');
444            var collapseButton = expandedPart.querySelector('.collapse-button');
445            collapseButton.onclick = collapseFunction(sectionId);
446            collapseButton.onmousedown = handleButtonMouseDown;
447
448            var collapsedPart = sections[i].querySelector('#' + sectionId + ' > .collapsed');
449            var expandButton = collapsedPart.querySelector('.expand-button');
450            expandButton.onclick = expandFunction(sectionId);
451            expandButton.onmousedown = handleButtonMouseDown;
452        }
453
454    }
455
456    function handleButtonMouseDown(e)
457    {
458       // To prevent selection on double click
459       e.preventDefault();
460    }
461
462    DocumentPrototype.transformDocumentToTreeView = function(noStyleMessage) {
463        prepareWebKitXMLViewer(noStyleMessage);
464    }
465});
466
467