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