| /** |
| * There are three basic use cases of dumpAsMarkup |
| * |
| * 1. Dump the entire DOM when the page is loaded |
| * When this script is included but no method of Markup is called, |
| * it dumps the DOM of each frame loaded. |
| * |
| * 2. Dump the content of a specific element when the page is loaded |
| * When Markup.setNodeToDump is called with some element or the id of some element, |
| * it dumps the content of the specified element as supposed to the entire DOM tree. |
| * |
| * 3. Dump the content of a specific element multiple times while the page is loading |
| * Calling Markup.dump would dump the content of the element set by setNodeToDump or the entire DOM. |
| * Optionally specify the node to dump and the description for each call of dump. |
| */ |
| |
| if (window.testRunner) |
| testRunner.dumpAsText(); |
| |
| // Namespace |
| // FIXME: Rename dump-as-markup.js to dump-dom.js and Markup to DOM. |
| var Markup = {}; |
| |
| // The description of what this test is testing. Gets prepended to the dumped markup. |
| Markup.description = function(description) |
| { |
| Markup._test_description = description; |
| } |
| |
| // Dumps the markup for the given node (HTML element if no node is given). |
| // Over-writes the body's content with the markup in layout test mode. Appends |
| // a pre element when loaded manually, in order to aid debugging. |
| Markup.dump = function(opt_node, opt_description) |
| { |
| if (typeof opt_node == 'string') |
| opt_node = document.getElementById(opt_node); |
| |
| var node = opt_node || document |
| var markup = ""; |
| |
| Markup._dumpCalls++; |
| |
| if (Markup._dumpCalls > 1 || opt_description) { |
| if (!opt_description) |
| opt_description = "Dump of markup " + Markup._dumpCalls |
| if (Markup._dumpCalls > 1) |
| markup += '\n'; |
| markup += '\n' + opt_description + ':\n'; |
| } else |
| Markup._firstCallDidNotHaveDescription = true; |
| |
| markup += Markup.get(node); |
| |
| if (!Markup._container) { |
| Markup._container = document.createElement('pre'); |
| Markup._container.style.width = '100%'; |
| } |
| |
| if (Markup._dumpCalls == 2 && Markup._firstCallDidNotHaveDescription) { |
| var wrapper = Markup._container.getElementsByClassName('dump-as-markup-span')[0]; |
| wrapper.insertBefore(document.createTextNode('\nDump of markup 1:\n'), wrapper.firstChild); |
| } |
| |
| // FIXME: Have this respect testRunner.dumpChildFramesAsText? |
| // FIXME: Should we care about framesets? |
| // DocumentFragment doesn't have a getElementsByTagName method. |
| if (node.getElementsByTagName) { |
| var iframes = node.getElementsByTagName('iframe'); |
| for (var i = 0; i < iframes.length; i++) { |
| markup += '\n\nFRAME ' + i + ':\n' |
| try { |
| markup += Markup.get(iframes[i].contentDocument.body.parentElement); |
| } catch (e) { |
| markup += 'FIXME: Add method to layout test controller to get access to cross-origin frames.'; |
| } |
| } |
| } |
| |
| if (Markup._test_description && Markup._dumpCalls == 1) |
| Markup._container.appendChild(document.createTextNode(Markup._test_description + '\n')) |
| |
| var wrapper = document.createElement('span'); |
| wrapper.className = 'dump-as-markup-span'; |
| wrapper.appendChild(document.createTextNode(markup)); |
| Markup._container.appendChild(wrapper); |
| } |
| |
| Markup.noAutoDump = function() |
| { |
| window.removeEventListener('load', Markup.notifyDone, false); |
| } |
| |
| Markup.waitUntilDone = function() |
| { |
| if (window.testRunner) |
| testRunner.waitUntilDone(); |
| Markup.noAutoDump(); |
| } |
| |
| Markup.notifyDone = function() |
| { |
| // Need to waitUntilDone or some tests won't finish appending the markup before the text is dumped. |
| if (window.testRunner) |
| testRunner.waitUntilDone(); |
| |
| // If dump has already been called, don't bother to dump again |
| if (!Markup._dumpCalls) |
| Markup.dump(); |
| |
| // In non-layout test mode, append the results in a pre so that we don't |
| // clobber the test itself. But when in layout test mode, we don't want |
| // side effects from the test to be included in the results. |
| if (window.testRunner) |
| document.body.innerHTML = ''; |
| |
| document.body.appendChild(Markup._container); |
| |
| if (window.testRunner) |
| testRunner.notifyDone(); |
| } |
| |
| Markup.useHTML5libOutputFormat = function() |
| { |
| Markup._useHTML5libOutputFormat = true; |
| } |
| |
| Markup.get = function(node) |
| { |
| var shadowRootList = {}; |
| var markup = Markup._getShadowHostIfPossible(node, 0, shadowRootList); |
| if (markup) |
| return markup.substring(1); |
| |
| if (!node.firstChild) |
| return '| '; |
| |
| // Don't print any markup for the root node. |
| for (var i = 0, len = node.childNodes.length; i < len; i++) |
| markup += Markup._get(node.childNodes[i], 0, shadowRootList); |
| return markup.substring(1); |
| } |
| |
| // Returns the markup for the given node. To be used for cases where a test needs |
| // to get the markup but not clobber the whole page. |
| Markup._get = function(node, depth, shadowRootList) |
| { |
| var str = Markup._indent(depth); |
| |
| switch (node.nodeType) { |
| case Node.DOCUMENT_TYPE_NODE: |
| str += '<!DOCTYPE ' + node.nodeName; |
| // FIXME: The actual MarkupAccumulator in WebKit handles these quite differently. |
| // Should we change to match that format? Would probably need to change test results, |
| // but it could help us distinguish empty strings from missing properties and |
| // would include internalSubset, which this omits. |
| if (node.publicId || node.systemId) { |
| str += ' "' + (node.publicId || '') + '"'; |
| str += ' "' + (node.systemId || '') + '"'; |
| } |
| str += '>'; |
| break; |
| |
| case Node.COMMENT_NODE: |
| try { |
| str += '<!-- ' + node.nodeValue + ' -->'; |
| } catch (e) { |
| str += '<!-- -->'; |
| } |
| break; |
| |
| case Node.PROCESSING_INSTRUCTION_NODE: |
| str += '<?' + node.nodeName + node.nodeValue + '>'; |
| break; |
| |
| case Node.CDATA_SECTION_NODE: |
| str += '<![CDATA[ ' + node.nodeValue + ' ]]>'; |
| break; |
| |
| case Node.TEXT_NODE: |
| str += '"' + Markup._getMarkupForTextNode(node) + '"'; |
| break; |
| |
| case Node.ELEMENT_NODE: |
| str += "<"; |
| str += Markup._namespace(node) |
| |
| if (node.localName && node.namespaceURI && node.namespaceURI != null) |
| str += node.localName; |
| else |
| str += Markup._toAsciiLowerCase(node.nodeName); |
| |
| str += '>'; |
| |
| if (node.attributes) { |
| var attrNames = []; |
| var attrPos = {}; |
| for (var j = 0; j < node.attributes.length; j += 1) { |
| if (node.attributes[j].specified) { |
| var name = Markup._namespace(node.attributes[j]) |
| name += node.attributes[j].localName || node.attributes[j].nodeName; |
| attrNames.push(name); |
| attrPos[name] = j; |
| } |
| } |
| if (attrNames.length > 0) { |
| attrNames.sort(); |
| for (var j = 0; j < attrNames.length; j += 1) { |
| str += Markup._indent(depth + 1) + attrNames[j]; |
| str += '="' + node.attributes[attrPos[attrNames[j]]].nodeValue + '"'; |
| } |
| } |
| } |
| |
| if (!Markup._useHTML5libOutputFormat && window.internals) { |
| var pseudoId = window.internals.shadowPseudoId(node); |
| if (pseudoId) |
| str += Markup._indent(depth + 1) + 'shadow:pseudoId="' + pseudoId + '"'; |
| } |
| |
| if (!Markup._useHTML5libOutputFormat) |
| if (node.nodeName == "INPUT" || node.nodeName == "TEXTAREA") |
| str += Markup._indent(depth + 1) + 'this.value="' + node.value + '"'; |
| |
| break; |
| case Node.DOCUMENT_FRAGMENT_NODE: |
| if (shadowRootList && window.internals && internals.address(node) in shadowRootList) |
| str += "<shadow:root>"; |
| else |
| str += "content"; |
| } |
| |
| if (node.namespaceURI = 'http://www.w3.org/1999/xhtml' && node.tagName == 'TEMPLATE') |
| str += Markup._get(node.content, depth + 1, shadowRootList); |
| |
| for (var i = 0, len = node.childNodes.length; i < len; i++) { |
| var selection = Markup._getSelectionMarker(node, i); |
| if (selection) |
| str += Markup._indent(depth + 1) + selection; |
| |
| str += Markup._get(node.childNodes[i], depth + 1, shadowRootList); |
| } |
| |
| str += Markup._getShadowHostIfPossible(node, depth, shadowRootList); |
| |
| var selection = Markup._getSelectionMarker(node, i); |
| if (selection) |
| str += Markup._indent(depth + 1) + selection; |
| |
| return str; |
| } |
| |
| Markup._getShadowHostIfPossible = function (node, depth, shadowRootList) |
| { |
| if (!Markup._useHTML5libOutputFormat && node.nodeType == Node.ELEMENT_NODE && window.internals) { |
| var root = window.internals.shadowRoot(node); |
| if (root) { |
| shadowRootList[internals.address(root)] = true; |
| return Markup._get(root, depth + 1, shadowRootList); |
| } |
| } |
| return ''; |
| } |
| |
| Markup._namespace = function(node) |
| { |
| if (Markup._NAMESPACE_URI_MAP[node.namespaceURI]) |
| return Markup._NAMESPACE_URI_MAP[node.namespaceURI] + ' '; |
| return ''; |
| } |
| |
| Markup._dumpCalls = 0 |
| |
| Markup._indent = function(depth) |
| { |
| return "\n| " + new Array(depth * 2 + 1).join(' '); |
| } |
| |
| Markup._toAsciiLowerCase = function (str) { |
| var output = ""; |
| for (var i = 0, len = this.length; i < len; ++i) { |
| if (str.charCodeAt(i) >= 0x41 && str.charCodeAt(i) <= 0x5A) |
| output += String.fromCharCode(str.charCodeAt(i) + 0x20) |
| else |
| output += str.charAt(i); |
| } |
| return output; |
| } |
| |
| Markup._NAMESPACE_URI_MAP = { |
| "http://www.w3.org/2000/svg": "svg", |
| "http://www.w3.org/1998/Math/MathML": "math", |
| "http://www.w3.org/XML/1998/namespace": "xml", |
| "http://www.w3.org/2000/xmlns/": "xmlns", |
| "http://www.w3.org/1999/xlink": "xlink" |
| } |
| |
| Markup._getSelectionFromNode = function(node) |
| { |
| return node.ownerDocument.defaultView ? node.ownerDocument.defaultView.getSelection() : null; |
| } |
| |
| Markup._SELECTION_FOCUS = '<#selection-focus>'; |
| Markup._SELECTION_ANCHOR = '<#selection-anchor>'; |
| Markup._SELECTION_CARET = '<#selection-caret>'; |
| |
| Markup._getMarkupForTextNode = function(node) |
| { |
| innerMarkup = node.nodeValue; |
| var startOffset, endOffset, startText, endText; |
| |
| var sel = Markup._getSelectionFromNode(node); |
| // Firefox doesn't have a sel in a display:none iframe. |
| // https://bugs.webkit.org/show_bug.cgi?id=43655 |
| if (sel) { |
| if (node == sel.anchorNode && node == sel.focusNode) { |
| if (sel.isCollapsed) { |
| startOffset = sel.anchorOffset; |
| startText = Markup._SELECTION_CARET; |
| } else { |
| if (sel.focusOffset > sel.anchorOffset) { |
| startOffset = sel.anchorOffset; |
| endOffset = sel.focusOffset; |
| startText = Markup._SELECTION_ANCHOR; |
| endText = Markup._SELECTION_FOCUS; |
| } else { |
| startOffset = sel.focusOffset; |
| endOffset = sel.anchorOffset; |
| startText = Markup._SELECTION_FOCUS; |
| endText = Markup._SELECTION_ANCHOR; |
| } |
| } |
| } else if (node == sel.focusNode) { |
| startOffset = sel.focusOffset; |
| startText = Markup._SELECTION_FOCUS; |
| } else if (node == sel.anchorNode) { |
| startOffset = sel.anchorOffset; |
| startText = Markup._SELECTION_ANCHOR; |
| } |
| } |
| |
| if (startText && endText) |
| innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset, endOffset) + endText + innerMarkup.substring(endOffset); |
| else if (startText) |
| innerMarkup = innerMarkup.substring(0, startOffset) + startText + innerMarkup.substring(startOffset); |
| |
| return innerMarkup; |
| } |
| |
| Markup._getSelectionMarker = function(node, index) |
| { |
| if (node.nodeType != 1) |
| return ''; |
| |
| var sel = Markup._getSelectionFromNode(node);; |
| |
| // Firefox doesn't have a sel in a display:none iframe. |
| // https://bugs.webkit.org/show_bug.cgi?id=43655 |
| if (!sel) |
| return ''; |
| |
| if (index == sel.anchorOffset && node == sel.anchorNode) { |
| if (sel.isCollapsed) |
| return Markup._SELECTION_CARET; |
| else |
| return Markup._SELECTION_ANCHOR; |
| } else if (index == sel.focusOffset && node == sel.focusNode) |
| return Markup._SELECTION_FOCUS; |
| |
| return ''; |
| } |
| |
| window.addEventListener('load', Markup.notifyDone, false); |