Import W3C DOM test suite from github.com/w3c/web-platform-tests
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 30 Aug 2015 05:07:17 +0000 (05:07 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 30 Aug 2015 05:07:17 +0000 (05:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=148546

Reviewed by Alexey Proskuryakov.

Import W3C DOM test suite from github.com/w3c/web-platform-tests
to improve coverage and track progress.

* http/tests/w3c/dom/*: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@189155 268f45cc-cd09-0410-ab3c-d52691b4dbfc

443 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/http/tests/resources/WebIDLParser.js [new file with mode: 0644]
LayoutTests/http/tests/resources/idlharness.js [new file with mode: 0644]
LayoutTests/http/tests/resources/testharness.css [new file with mode: 0644]
LayoutTests/http/tests/resources/testharness.js [new file with mode: 0644]
LayoutTests/http/tests/resources/testharnessreport.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/collections/HTMLCollection-empty-name-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/collections/HTMLCollection-empty-name.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/collections/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/common.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/constants.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-constants-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-constants.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-constructors-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-constructors.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-defaultPrevented-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-defaultPrevented.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-bubbles-false-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-bubbles-false.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-handlers-changed-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-handlers-changed.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-omitted-capture-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-omitted-capture.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-redispatch-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-redispatch.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-reenter-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-reenter.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-target-moved-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-target-moved.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-target-removed-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-dispatch-target-removed.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-initEvent-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-initEvent.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-propagation-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-propagation.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-type-empty-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-type-empty.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-type-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/Event-type.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-addEventListener-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-addEventListener.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-dispatchEvent-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-dispatchEvent-returnvalue-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-dispatchEvent-returnvalue.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-dispatchEvent.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-removeEventListener-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/EventTarget-removeEventListener.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/ProgressEvent-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/ProgressEvent.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/events/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/historical-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/historical.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/interface-objects-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/interface-objects.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/interfaces-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/interfaces.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/lists/DOMTokenList-stringifier-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/lists/DOMTokenList-stringifier.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/lists/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-appendData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-appendData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-data-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-data.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-deleteData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-deleteData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-insertData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-insertData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-remove-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-remove.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-replaceData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-replaceData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-substringData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/CharacterData-substringData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-after-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-after.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-before-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-before.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-remove.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-replaceWith-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ChildNode-replaceWith.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Comment-Text-constructor.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Comment-constructor-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Comment-constructor.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createDocument-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createDocument.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createDocumentType-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createDocumentType.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createHTMLDocument-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createHTMLDocument.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-createHTMLDocument.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-hasFeature-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DOMImplementation-hasFeature.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-Element-getElementsByTagName.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-Element-getElementsByTagNameNS.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-URL.sub-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-URL.sub.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-adoptNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-adoptNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-characterSet-normalization-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-characterSet-normalization.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-constructor-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-constructor.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createAttribute-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createAttribute.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createComment-createTextNode.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createComment-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createComment.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElement-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElement-namespace-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElement.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElementNS-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElementNS.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createElementNS.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createEvent-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createEvent.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createEvent.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createProcessingInstruction-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createProcessingInstruction-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createProcessingInstruction-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createProcessingInstruction.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createProcessingInstruction.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createTextNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createTextNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createTreeWalker-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-createTreeWalker.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-doctype-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-doctype.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementById-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementById.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagName-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagName-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagName-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagName.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagNameNS-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-getElementsByTagNameNS.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-implementation-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-implementation.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-importNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Document-importNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-literal-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-literal-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-literal-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-literal.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-remove-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/DocumentType-remove.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElement-null-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElement-null-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElement-null-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElement-null.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-add-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-add-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-add-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-add.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-remove-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-remove-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-remove-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-dynamic-remove.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-nochild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-nochild-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-nochild-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-nochild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-childElementCount.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-children-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-children.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-classlist-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-classlist.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-closest-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-closest.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-entity-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-entity-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-namespace-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-namespace-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-namespace-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-namespace.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-firstElementChild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByClassName-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByClassName.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByTagName-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByTagName.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByTagNameNS-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-getElementsByTagNameNS.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-lastElementChild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-lastElementChild-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-lastElementChild-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-lastElementChild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-matches-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-matches.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-matches.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-nextElementSibling-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-nextElementSibling-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-nextElementSibling-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-nextElementSibling.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-previousElementSibling-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-previousElementSibling-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-previousElementSibling-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-previousElementSibling.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-remove-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-remove.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-removeAttributeNS-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-removeAttributeNS.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-siblingElement-null-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-siblingElement-null-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-siblingElement-null-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-siblingElement-null.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-tagName-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Element-tagName.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-attributes-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-attributes.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-characterData-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-characterData.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-childList-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-childList.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-disconnect-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-disconnect.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-document-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-document.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-inner-outer-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-inner-outer.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-takeRecords-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/MutationObserver-takeRecords.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-appendChild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-appendChild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-baseURI-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-baseURI.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-childNodes-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-childNodes.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-cloneNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-cloneNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-compareDocumentPosition-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-compareDocumentPosition.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-constants-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-constants.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-contains-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-contains-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-contains-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-contains.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-insertBefore-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-insertBefore.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-isEqualNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-isEqualNode-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-isEqualNode-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-lookupNamespaceURI-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-lookupNamespaceURI.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-lookupPrefix-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-lookupPrefix-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-lookupPrefix-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeName-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeName-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeName-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeName.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeValue-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-nodeValue.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-normalize-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-normalize.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-parentElement-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-parentElement.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-parentNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-parentNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-properties-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-properties.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-removeChild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-removeChild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-replaceChild-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-replaceChild.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-textContent-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Node-textContent.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-append-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-append.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-prepend-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-prepend.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-querySelector-All-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-querySelector-All-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-querySelector-All-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-querySelector-All.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ParentNode-querySelector-All.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-1-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-1-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-1-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-2-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-2-xhtml-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/ProcessingInstruction-literal-2-xhtml.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Text-constructor-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Text-constructor.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Text-splitText-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/Text-splitText.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/append-on-Document-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/append-on-Document.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/attributes-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/attributes.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/attributes.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/case-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/case.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/case.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/creators.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/encoding.py [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-01-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-01.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-02-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-02.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-03-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-03.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-04-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-04.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-05-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-05.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-06-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-06.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-07-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-07.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-08-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-08.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-09-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-09.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-10-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-10.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-11-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-11.xhtml [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-12-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-12.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-13-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-13.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-14-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-14.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-15-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-15.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-16-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-16.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-17-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-17.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-18-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-18.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-19-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-19.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-20-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-20.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-21-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-21.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-22-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-22.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-23-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-23.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-24-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-24.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-25-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-25.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-26-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-26.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-27-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-27.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-28-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-28.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-29-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-29.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-30-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-30.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-31-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/getElementsByClassName-31.htm [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/mutationobservers.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/prepend-on-Document-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/prepend-on-Document.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/productions.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/resources/ParentNode-querySelector-All-content.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/resources/ParentNode-querySelector-All-content.xht [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/selectors.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/nodes/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-attributes-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-attributes.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-cloneContents-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-cloneContents.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-cloneRange-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-cloneRange.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-collapse-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-collapse.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-commonAncestorContainer-2-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-commonAncestorContainer-2.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-commonAncestorContainer-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-commonAncestorContainer.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-compareBoundaryPoints-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-compareBoundaryPoints.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-comparePoint-2-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-comparePoint-2.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-comparePoint-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-comparePoint.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-deleteContents-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-deleteContents.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-detach-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-detach.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-extractContents-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-extractContents.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-insertNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-insertNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-intersectsNode-binding-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-intersectsNode-binding.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-intersectsNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-intersectsNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-isPointInRange-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-isPointInRange.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-mutations-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-mutations.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-selectNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-selectNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-set-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-set.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-surroundContents-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/Range-surroundContents.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/resources/Range-test-iframe.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/ranges/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeFilter-constants-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeFilter-constants.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeIterator-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeIterator-removal-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeIterator-removal.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/NodeIterator.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-acceptNode-filter-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-acceptNode-filter.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-basic-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-basic.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-currentNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-currentNode.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-previousNodeLastChildReject-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-previousNodeLastChildReject.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-previousSiblingLastChildSkip-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-previousSiblingLastChildSkip.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-reject-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-reject.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-skip-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-skip-most-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-skip-most.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-traversal-skip.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-walking-outside-a-tree-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker-walking-outside-a-tree.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/TreeWalker.html [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/traversal-support.js [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/traversal/w3c-import.log [new file with mode: 0644]
LayoutTests/http/tests/w3c/dom/w3c-import.log [new file with mode: 0644]

index c0fe295..fd382a8 100644 (file)
@@ -1,3 +1,15 @@
+2015-08-29  Chris Dumez  <cdumez@apple.com>
+
+        Import W3C DOM test suite from github.com/w3c/web-platform-tests
+        https://bugs.webkit.org/show_bug.cgi?id=148546
+
+        Reviewed by Alexey Proskuryakov.
+
+        Import W3C DOM test suite from github.com/w3c/web-platform-tests
+        to improve coverage and track progress.
+
+        * http/tests/w3c/dom/*: Added.
+
 2015-08-29  Chris Fleizach  <cfleizach@apple.com>
 
         AX: When navigating the elements of a scrollable element with VoiceOver, the scrollTop() position of the element does not permanently change
index 50cb5f9..ea2d966 100644 (file)
@@ -213,7 +213,14 @@ webkit.org/b/136754 css3/flexbox/csswg/ttwf-reftest-flex-wrap.html [ ImageOnlyFa
 webkit.org/b/137149 fast/selectors/nth-child-of-basics.html [ Slow ]
 
 # Promises/A+ 2.3.3 has many tests and it sometimes exceeds the time limit. 
-webkit.org/b/136878 js/promises-tests/promises-tests-2-3-3.html [ Slow ] 
+webkit.org/b/136878 js/promises-tests/promises-tests-2-3-3.html [ Slow ]
+
+# Skip W3C tests that are too slow in debug builds.
+webkit.org/b/148546 [ Debug ] http/tests/w3c/dom/ranges/Range-compareBoundaryPoints.html [ Skip ]
+webkit.org/b/148546 [ Debug ] http/tests/w3c/dom/ranges/Range-comparePoint.html [ Skip ]
+webkit.org/b/148546 [ Debug ] http/tests/w3c/dom/ranges/Range-isPointInRange.html [ Skip ]
+webkit.org/b/148546 [ Debug ] http/tests/w3c/dom/ranges/Range-mutations.html [ Skip ]
+webkit.org/b/148546 [ Debug ] http/tests/w3c/dom/ranges/Range-set.html [ Skip ]
 
 # @supports W3C Failures
 webkit.org/b/137566 css3/conditional/w3c/at-supports-010.html [ ImageOnlyFailure ]
diff --git a/LayoutTests/http/tests/resources/WebIDLParser.js b/LayoutTests/http/tests/resources/WebIDLParser.js
new file mode 100644 (file)
index 0000000..103a7f4
--- /dev/null
@@ -0,0 +1,924 @@
+
+
+(function () {
+    var tokenise = function (str) {
+        var tokens = []
+        ,   re = {
+                "float":        /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/
+            ,   "integer":      /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/
+            ,   "identifier":   /^[A-Z_a-z][0-9A-Z_a-z]*/
+            ,   "string":       /^"[^"]*"/
+            ,   "whitespace":   /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/
+            ,   "other":        /^[^\t\n\r 0-9A-Z_a-z]/
+            }
+        ,   types = []
+        ;
+        for (var k in re) types.push(k);
+        while (str.length > 0) {
+            var matched = false;
+            for (var i = 0, n = types.length; i < n; i++) {
+                var type = types[i];
+                str = str.replace(re[type], function (tok) {
+                    tokens.push({ type: type, value: tok });
+                    matched = true;
+                    return "";
+                });
+                if (matched) break;
+            }
+            if (matched) continue;
+            throw new Error("Token stream not progressing");
+        }
+        return tokens;
+    };
+    
+    var parse = function (tokens, opt) {
+        var line = 1;
+        tokens = tokens.slice();
+        
+        var FLOAT = "float"
+        ,   INT = "integer"
+        ,   ID = "identifier"
+        ,   STR = "string"
+        ,   OTHER = "other"
+        ;
+        
+        var WebIDLParseError = function (str, line, input, tokens) {
+            this.message = str;
+            this.line = line;
+            this.input = input;
+            this.tokens = tokens;
+        };
+        WebIDLParseError.prototype.toString = function () {
+            return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" +
+                   JSON.stringify(this.tokens, null, 4);
+        };
+        
+        var error = function (str) {
+            var tok = "", numTokens = 0, maxTokens = 5;
+            while (numTokens < maxTokens && tokens.length > numTokens) {
+                tok += tokens[numTokens].value;
+                numTokens++;
+            }
+            throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5));
+        };
+        
+        var last_token = null;
+        
+        var consume = function (type, value) {
+            if (!tokens.length || tokens[0].type !== type) return;
+            if (typeof value === "undefined" || tokens[0].value === value) {
+                 last_token = tokens.shift();
+                 if (type === ID) last_token.value = last_token.value.replace(/^_/, "");
+                 return last_token;
+             }
+        };
+        
+        var ws = function () {
+            if (!tokens.length) return;
+            if (tokens[0].type === "whitespace") {
+                var t = tokens.shift();
+                t.value.replace(/\n/g, function (m) { line++; return m; });
+                return t;
+            }
+        };
+        
+        var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types
+            var t = { type: "whitespace", value: "" };
+            while (true) {
+                var w = ws();
+                if (!w) break;
+                t.value += w.value;
+            }
+            if (t.value.length > 0) {
+                if (store) {
+                    var w = t.value
+                    ,   re = {
+                            "ws":                   /^([\t\n\r ]+)/
+                        ,   "line-comment":         /^\/\/(.*)\n?/m
+                        ,   "multiline-comment":    /^\/\*((?:.|\n|\r)*?)\*\//
+                        }
+                    ,   wsTypes = []
+                    ;
+                    for (var k in re) wsTypes.push(k);
+                    while (w.length) {
+                        var matched = false;
+                        for (var i = 0, n = wsTypes.length; i < n; i++) {
+                            var type = wsTypes[i];
+                            w = w.replace(re[type], function (tok, m1) {
+                                store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
+                                matched = true;
+                                return "";
+                            });
+                            if (matched) break;
+                        }
+                        if (matched) continue;
+                        throw new Error("Surprising white space construct."); // this shouldn't happen
+                    }
+                }
+                return t;
+            }
+        };
+        
+        var integer_type = function () {
+            var ret = "";
+            all_ws();
+            if (consume(ID, "unsigned")) ret = "unsigned ";
+            all_ws();
+            if (consume(ID, "short")) return ret + "short";
+            if (consume(ID, "long")) {
+                ret += "long";
+                all_ws();
+                if (consume(ID, "long")) return ret + " long";
+                return ret;
+            }
+            if (ret) error("Failed to parse integer type");
+        };
+        
+        var float_type = function () {
+            var ret = "";
+            all_ws();
+            if (consume(ID, "unrestricted")) ret = "unrestricted ";
+            all_ws();
+            if (consume(ID, "float")) return ret + "float";
+            if (consume(ID, "double")) return ret + "double";
+            if (ret) error("Failed to parse float type");
+        };
+        
+        var primitive_type = function () {
+            var num_type = integer_type() || float_type();
+            if (num_type) return num_type;
+            all_ws();
+            if (consume(ID, "boolean")) return "boolean";
+            if (consume(ID, "byte")) return "byte";
+            if (consume(ID, "octet")) return "octet";
+        };
+        
+        var const_value = function () {
+            if (consume(ID, "true")) return { type: "boolean", value: true };
+            if (consume(ID, "false")) return { type: "boolean", value: false };
+            if (consume(ID, "null")) return { type: "null" };
+            if (consume(ID, "Infinity")) return { type: "Infinity", negative: false };
+            if (consume(ID, "NaN")) return { type: "NaN" };
+            var ret = consume(FLOAT) || consume(INT);
+            if (ret) return { type: "number", value: 1 * ret.value };
+            var tok = consume(OTHER, "-");
+            if (tok) {
+                if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
+                else tokens.unshift(tok);
+            }
+        };
+        
+        var type_suffix = function (obj) {
+            while (true) {
+                all_ws();
+                if (consume(OTHER, "?")) {
+                    if (obj.nullable) error("Can't nullable more than once");
+                    obj.nullable = true;
+                }
+                else if (consume(OTHER, "[")) {
+                    all_ws();
+                    consume(OTHER, "]") || error("Unterminated array type");
+                    if (!obj.array) {
+                        obj.array = 1;
+                        obj.nullableArray = [obj.nullable];
+                    }
+                    else {
+                        obj.array++;
+                        obj.nullableArray.push(obj.nullable);
+                    }
+                    obj.nullable = false;
+                }
+                else return;
+            }
+        };
+        
+        var single_type = function () {
+            var prim = primitive_type()
+            ,   ret = { sequence: false, generic: null, nullable: false, array: false, union: false }
+            ,   name
+            ,   value
+            ;
+            if (prim) {
+                ret.idlType = prim;
+            }
+            else if (name = consume(ID)) {
+                value = name.value;
+                all_ws();
+                // Generic types
+                if (consume(OTHER, "<")) {
+                    // backwards compat
+                    if (value === "sequence") {
+                        ret.sequence = true;
+                    }
+                    ret.generic = value;
+                    ret.idlType = type() || error("Error parsing generic type " + value);
+                    all_ws();
+                    if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
+                    all_ws();
+                    if (consume(OTHER, "?")) ret.nullable = true;
+                    return ret;
+                }
+                else {
+                    ret.idlType = value;
+                }
+            }
+            else {
+                return;
+            }
+            type_suffix(ret);
+            if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable");
+            return ret;
+        };
+        
+        var union_type = function () {
+            all_ws();
+            if (!consume(OTHER, "(")) return;
+            var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] };
+            var fst = type() || error("Union type with no content");
+            ret.idlType.push(fst);
+            while (true) {
+                all_ws();
+                if (!consume(ID, "or")) break;
+                var typ = type() || error("No type after 'or' in union type");
+                ret.idlType.push(typ);
+            }
+            if (!consume(OTHER, ")")) error("Unterminated union type");
+            type_suffix(ret);
+            return ret;
+        };
+        
+        var type = function () {
+            return single_type() || union_type();
+        };
+        
+        var argument = function (store) {
+            var ret = { optional: false, variadic: false };
+            ret.extAttrs = extended_attrs(store);
+            all_ws(store, "pea");
+            var opt_token = consume(ID, "optional");
+            if (opt_token) {
+                ret.optional = true;
+                all_ws();
+            }
+            ret.idlType = type();
+            if (!ret.idlType) {
+                if (opt_token) tokens.unshift(opt_token);
+                return;
+            }
+            var type_token = last_token;
+            if (!ret.optional) {
+                all_ws();
+                if (tokens.length >= 3 &&
+                    tokens[0].type === "other" && tokens[0].value === "." &&
+                    tokens[1].type === "other" && tokens[1].value === "." &&
+                    tokens[2].type === "other" && tokens[2].value === "."
+                    ) {
+                    tokens.shift();
+                    tokens.shift();
+                    tokens.shift();
+                    ret.variadic = true;
+                }
+            }
+            all_ws();
+            var name = consume(ID);
+            if (!name) {
+                if (opt_token) tokens.unshift(opt_token);
+                tokens.unshift(type_token);
+                return;
+            }
+            ret.name = name.value;
+            if (ret.optional) {
+                all_ws();
+                ret["default"] = default_();
+            }
+            return ret;
+        };
+        
+        var argument_list = function (store) {
+            var ret = []
+            ,   arg = argument(store ? ret : null)
+            ;
+            if (!arg) return;
+            ret.push(arg);
+            while (true) {
+                all_ws(store ? ret : null);
+                if (!consume(OTHER, ",")) return ret;
+                var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
+                ret.push(nxt);
+            }
+        };
+        
+        var type_pair = function () {
+            all_ws();
+            var k = type();
+            if (!k) return;
+            all_ws()
+            if (!consume(OTHER, ",")) return;
+            all_ws();
+            var v = type();
+            if (!v) return;
+            return [k, v];
+        };
+        
+        var simple_extended_attr = function (store) {
+            all_ws();
+            var name = consume(ID);
+            if (!name) return;
+            var ret = {
+                name: name.value
+            ,   "arguments": null
+            };
+            all_ws();
+            var eq = consume(OTHER, "=");
+            if (eq) {
+                all_ws();
+                ret.rhs = consume(ID);
+                if (!ret.rhs) return error("No right hand side to extended attribute assignment");
+            }
+            all_ws();
+            if (consume(OTHER, "(")) {
+                var args, pair;
+                // [Constructor(DOMString str)]
+                if (args = argument_list(store)) {
+                    ret["arguments"] = args;
+                }
+                // [MapClass(DOMString, DOMString)]
+                else if (pair = type_pair()) {
+                    ret.typePair = pair;
+                }
+                // [Constructor()]
+                else {
+                    ret["arguments"] = [];
+                }
+                all_ws();
+                consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
+            }
+            return ret;
+        };
+        
+        // Note: we parse something simpler than the official syntax. It's all that ever
+        // seems to be used
+        var extended_attrs = function (store) {
+            var eas = [];
+            all_ws(store);
+            if (!consume(OTHER, "[")) return eas;
+            eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
+            all_ws();
+            while (consume(OTHER, ",")) {
+                eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
+                all_ws();
+            }
+            consume(OTHER, "]") || error("No end of extended attribute");
+            return eas;
+        };
+        
+        var default_ = function () {
+            all_ws();
+            if (consume(OTHER, "=")) {
+                all_ws();
+                var def = const_value();
+                if (def) {
+                    return def;
+                }
+                else {
+                    var str = consume(STR) || error("No value for default");
+                    str.value = str.value.replace(/^"/, "").replace(/"$/, "");
+                    return str;
+                }
+            }
+        };
+        
+        var const_ = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "const")) return;
+            var ret = { type: "const", nullable: false };
+            all_ws();
+            var typ = primitive_type();
+            if (!typ) {
+                typ = consume(ID) || error("No type for const");
+                typ = typ.value;
+            }
+            ret.idlType = typ;
+            all_ws();
+            if (consume(OTHER, "?")) {
+                ret.nullable = true;
+                all_ws();
+            }
+            var name = consume(ID) || error("No name for const");
+            ret.name = name.value;
+            all_ws();
+            consume(OTHER, "=") || error("No value assignment for const");
+            all_ws();
+            var cnt = const_value();
+            if (cnt) ret.value = cnt;
+            else error("No value for const");
+            all_ws();
+            consume(OTHER, ";") || error("Unterminated const");
+            return ret;
+        };
+        
+        var inheritance = function () {
+            all_ws();
+            if (consume(OTHER, ":")) {
+                all_ws();
+                var inh = consume(ID) || error ("No type in inheritance");
+                return inh.value;
+            }
+        };
+        
+        var operation_rest = function (ret, store) {
+            all_ws();
+            if (!ret) ret = {};
+            var name = consume(ID);
+            ret.name = name ? name.value : null;
+            all_ws();
+            consume(OTHER, "(") || error("Invalid operation");
+            ret["arguments"] = argument_list(store) || [];
+            all_ws();
+            consume(OTHER, ")") || error("Unterminated operation");
+            all_ws();
+            consume(OTHER, ";") || error("Unterminated operation");
+            return ret;
+        };
+        
+        var callback = function (store) {
+            all_ws(store, "pea");
+            var ret;
+            if (!consume(ID, "callback")) return;
+            all_ws();
+            var tok = consume(ID, "interface");
+            if (tok) {
+                tokens.unshift(tok);
+                ret = interface_();
+                ret.type = "callback interface";
+                return ret;
+            }
+            var name = consume(ID) || error("No name for callback");
+            ret = { type: "callback", name: name.value };
+            all_ws();
+            consume(OTHER, "=") || error("No assignment in callback");
+            all_ws();
+            ret.idlType = return_type();
+            all_ws();
+            consume(OTHER, "(") || error("No arguments in callback");
+            ret["arguments"] = argument_list(store) || [];
+            all_ws();
+            consume(OTHER, ")") || error("Unterminated callback");
+            all_ws();
+            consume(OTHER, ";") || error("Unterminated callback");
+            return ret;
+        };
+
+        var attribute = function (store) {
+            all_ws(store, "pea");
+            var grabbed = []
+            ,   ret = {
+                type:           "attribute"
+            ,   "static":       false
+            ,   stringifier:    false
+            ,   inherit:        false
+            ,   readonly:       false
+            };
+            if (consume(ID, "static")) {
+                ret["static"] = true;
+                grabbed.push(last_token);
+            }
+            else if (consume(ID, "stringifier")) {
+                ret.stringifier = true;
+                grabbed.push(last_token);
+            }
+            var w = all_ws();
+            if (w) grabbed.push(w);
+            if (consume(ID, "inherit")) {
+                if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit");
+                ret.inherit = true;
+                grabbed.push(last_token);
+                var w = all_ws();
+                if (w) grabbed.push(w);
+            }
+            if (consume(ID, "readonly")) {
+                ret.readonly = true;
+                grabbed.push(last_token);
+                var w = all_ws();
+                if (w) grabbed.push(w);
+            }
+            if (!consume(ID, "attribute")) {
+                tokens = grabbed.concat(tokens);
+                return;
+            }
+            all_ws();
+            ret.idlType = type() || error("No type in attribute");
+            if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
+            all_ws();
+            var name = consume(ID) || error("No name in attribute");
+            ret.name = name.value;
+            all_ws();
+            consume(OTHER, ";") || error("Unterminated attribute");
+            return ret;
+        };
+        
+        var return_type = function () {
+            var typ = type();
+            if (!typ) {
+                if (consume(ID, "void")) {
+                    return "void";
+                }
+                else error("No return type");
+            }
+            return typ;
+        };
+        
+        var operation = function (store) {
+            all_ws(store, "pea");
+            var ret = {
+                type:           "operation"
+            ,   getter:         false
+            ,   setter:         false
+            ,   creator:        false
+            ,   deleter:        false
+            ,   legacycaller:   false
+            ,   "static":       false
+            ,   stringifier:    false
+            };
+            while (true) {
+                all_ws();
+                if (consume(ID, "getter")) ret.getter = true;
+                else if (consume(ID, "setter")) ret.setter = true;
+                else if (consume(ID, "creator")) ret.creator = true;
+                else if (consume(ID, "deleter")) ret.deleter = true;
+                else if (consume(ID, "legacycaller")) ret.legacycaller = true;
+                else break;
+            }
+            if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) {
+                all_ws();
+                ret.idlType = return_type();
+                operation_rest(ret, store);
+                return ret;
+            }
+            if (consume(ID, "static")) {
+                ret["static"] = true;
+                ret.idlType = return_type();
+                operation_rest(ret, store);
+                return ret;
+            }
+            else if (consume(ID, "stringifier")) {
+                ret.stringifier = true;
+                all_ws();
+                if (consume(OTHER, ";")) return ret;
+                ret.idlType = return_type();
+                operation_rest(ret, store);
+                return ret;
+            }
+            ret.idlType = return_type();
+            all_ws();
+            if (consume(ID, "iterator")) {
+                all_ws();
+                ret.type = "iterator";
+                if (consume(ID, "object")) {
+                    ret.iteratorObject = "object";
+                }
+                else if (consume(OTHER, "=")) {
+                    all_ws();
+                    var name = consume(ID) || error("No right hand side in iterator");
+                    ret.iteratorObject = name.value;
+                }
+                all_ws();
+                consume(OTHER, ";") || error("Unterminated iterator");
+                return ret;
+            }
+            else {
+                operation_rest(ret, store);
+                return ret;
+            }
+        };
+        
+        var identifiers = function (arr) {
+            while (true) {
+                all_ws();
+                if (consume(OTHER, ",")) {
+                    all_ws();
+                    var name = consume(ID) || error("Trailing comma in identifiers list");
+                    arr.push(name.value);
+                }
+                else break;
+            }
+        };
+        
+        var serialiser = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "serializer")) return;
+            var ret = { type: "serializer" };
+            all_ws();
+            if (consume(OTHER, "=")) {
+                all_ws();
+                if (consume(OTHER, "{")) {
+                    ret.patternMap = true;
+                    all_ws();
+                    var id = consume(ID);
+                    if (id && id.value === "getter") {
+                        ret.names = ["getter"];
+                    }
+                    else if (id && id.value === "inherit") {
+                        ret.names = ["inherit"];
+                        identifiers(ret.names);
+                    }
+                    else if (id) {
+                        ret.names = [id.value];
+                        identifiers(ret.names);
+                    }
+                    else {
+                        ret.names = [];
+                    }
+                    all_ws();
+                    consume(OTHER, "}") || error("Unterminated serializer pattern map");
+                }
+                else if (consume(OTHER, "[")) {
+                    ret.patternList = true;
+                    all_ws();
+                    var id = consume(ID);
+                    if (id && id.value === "getter") {
+                        ret.names = ["getter"];
+                    }
+                    else if (id) {
+                        ret.names = [id.value];
+                        identifiers(ret.names);
+                    }
+                    else {
+                        ret.names = [];
+                    }
+                    all_ws();
+                    consume(OTHER, "]") || error("Unterminated serializer pattern list");
+                }
+                else {
+                    var name = consume(ID) || error("Invalid serializer");
+                    ret.name = name.value;
+                }
+                all_ws();
+                consume(OTHER, ";") || error("Unterminated serializer");
+                return ret;
+            }
+            else if (consume(OTHER, ";")) {
+                // noop, just parsing
+            }
+            else {
+                ret.idlType = return_type();
+                all_ws();
+                ret.operation = operation_rest(null, store);
+            }
+            return ret;
+        };
+        
+        var interface_ = function (isPartial, store) {
+            all_ws(isPartial ? null : store, "pea");
+            if (!consume(ID, "interface")) return;
+            all_ws();
+            var name = consume(ID) || error("No name for interface");
+            var mems = []
+            ,   ret = {
+                type:   "interface"
+            ,   name:   name.value
+            ,   partial:    false
+            ,   members:    mems
+            };
+            if (!isPartial) ret.inheritance = inheritance() || null;
+            all_ws();
+            consume(OTHER, "{") || error("Bodyless interface");
+            while (true) {
+                all_ws(store ? mems : null);
+                if (consume(OTHER, "}")) {
+                    all_ws();
+                    consume(OTHER, ";") || error("Missing semicolon after interface");
+                    return ret;
+                }
+                var ea = extended_attrs(store ? mems : null);
+                all_ws();
+                var cnt = const_(store ? mems : null);
+                if (cnt) {
+                    cnt.extAttrs = ea;
+                    ret.members.push(cnt);
+                    continue;
+                }
+                var mem = serialiser(store ? mems : null) ||
+                          attribute(store ? mems : null) ||
+                          operation(store ? mems : null) ||
+                          error("Unknown member");
+                mem.extAttrs = ea;
+                ret.members.push(mem);
+            }
+        };
+        
+        var partial = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "partial")) return;
+            var thing = dictionary(true, store) ||
+                        interface_(true, store) ||
+                        error("Partial doesn't apply to anything");
+            thing.partial = true;
+            return thing;
+        };
+        
+        var dictionary = function (isPartial, store) {
+            all_ws(isPartial ? null : store, "pea");
+            if (!consume(ID, "dictionary")) return;
+            all_ws();
+            var name = consume(ID) || error("No name for dictionary");
+            var mems = []
+            ,   ret = {
+                type:   "dictionary"
+            ,   name:   name.value
+            ,   partial:    false
+            ,   members:    mems
+            };
+            if (!isPartial) ret.inheritance = inheritance() || null;
+            all_ws();
+            consume(OTHER, "{") || error("Bodyless dictionary");
+            while (true) {
+                all_ws(store ? mems : null);
+                if (consume(OTHER, "}")) {
+                    all_ws();
+                    consume(OTHER, ";") || error("Missing semicolon after dictionary");
+                    return ret;
+                }
+                var ea = extended_attrs(store ? mems : null);
+                all_ws(store ? mems : null, "pea");
+                var typ = type() || error("No type for dictionary member");
+                all_ws();
+                var name = consume(ID) || error("No name for dictionary member");
+                ret.members.push({
+                    type:       "field"
+                ,   name:       name.value
+                ,   idlType:    typ
+                ,   extAttrs:   ea
+                ,   "default":  default_()
+                });
+                all_ws();
+                consume(OTHER, ";") || error("Unterminated dictionary member");
+            }
+        };
+        
+        var exception = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "exception")) return;
+            all_ws();
+            var name = consume(ID) || error("No name for exception");
+            var mems = []
+            ,   ret = {
+                type:   "exception"
+            ,   name:   name.value
+            ,   members:    mems
+            };
+            ret.inheritance = inheritance() || null;
+            all_ws();
+            consume(OTHER, "{") || error("Bodyless exception");
+            while (true) {
+                all_ws(store ? mems : null);
+                if (consume(OTHER, "}")) {
+                    all_ws();
+                    consume(OTHER, ";") || error("Missing semicolon after exception");
+                    return ret;
+                }
+                var ea = extended_attrs(store ? mems : null);
+                all_ws(store ? mems : null, "pea");
+                var cnt = const_();
+                if (cnt) {
+                    cnt.extAttrs = ea;
+                    ret.members.push(cnt);
+                }
+                else {
+                    var typ = type();
+                    all_ws();
+                    var name = consume(ID);
+                    all_ws();
+                    if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body");
+                    ret.members.push({
+                        type:       "field"
+                    ,   name:       name.value
+                    ,   idlType:    typ
+                    ,   extAttrs:   ea
+                    });
+                }
+            }
+        };
+        
+        var enum_ = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "enum")) return;
+            all_ws();
+            var name = consume(ID) || error("No name for enum");
+            var vals = []
+            ,   ret = {
+                type:   "enum"
+            ,   name:   name.value
+            ,   values: vals
+            };
+            all_ws();
+            consume(OTHER, "{") || error("No curly for enum");
+            var saw_comma = false;
+            while (true) {
+                all_ws(store ? vals : null);
+                if (consume(OTHER, "}")) {
+                    all_ws();
+                    if (saw_comma) error("Trailing comma in enum");
+                    consume(OTHER, ";") || error("No semicolon after enum");
+                    return ret;
+                }
+                var val = consume(STR) || error("Unexpected value in enum");
+                ret.values.push(val.value.replace(/"/g, ""));
+                all_ws(store ? vals : null);
+                if (consume(OTHER, ",")) {
+                    if (store) vals.push({ type: "," });
+                    all_ws(store ? vals : null);
+                    saw_comma = true;
+                }
+                else {
+                    saw_comma = false;
+                }
+            }
+        };
+        
+        var typedef = function (store) {
+            all_ws(store, "pea");
+            if (!consume(ID, "typedef")) return;
+            var ret = {
+                type:   "typedef"
+            };
+            all_ws();
+            ret.typeExtAttrs = extended_attrs();
+            all_ws(store, "tpea");
+            ret.idlType = type() || error("No type in typedef");
+            all_ws();
+            var name = consume(ID) || error("No name in typedef");
+            ret.name = name.value;
+            all_ws();
+            consume(OTHER, ";") || error("Unterminated typedef");
+            return ret;
+        };
+        
+        var implements_ = function (store) {
+            all_ws(store, "pea");
+            var target = consume(ID);
+            if (!target) return;
+            var w = all_ws();
+            if (consume(ID, "implements")) {
+                var ret = {
+                    type:   "implements"
+                ,   target: target.value
+                };
+                all_ws();
+                var imp = consume(ID) || error("Incomplete implements statement");
+                ret["implements"] = imp.value;
+                all_ws();
+                consume(OTHER, ";") || error("No terminating ; for implements statement");
+                return ret;
+            }
+            else {
+                // rollback
+                tokens.unshift(w);
+                tokens.unshift(target);
+            }
+        };
+        
+        var definition = function (store) {
+            return  callback(store)             ||
+                    interface_(false, store)    ||
+                    partial(store)              ||
+                    dictionary(false, store)    ||
+                    exception(store)            ||
+                    enum_(store)                ||
+                    typedef(store)              ||
+                    implements_(store)
+                    ;
+        };
+        
+        var definitions = function (store) {
+            if (!tokens.length) return [];
+            var defs = [];
+            while (true) {
+                var ea = extended_attrs(store ? defs : null)
+                ,   def = definition(store ? defs : null);
+                if (!def) {
+                    if (ea.length) error("Stray extended attributes");
+                    break;
+                }
+                def.extAttrs = ea;
+                defs.push(def);
+            }
+            return defs;
+        };
+        var res = definitions(opt.ws);
+        if (tokens.length) error("Unrecognised tokens");
+        return res;
+    };
+
+    var inNode = typeof module !== "undefined" && module.exports
+    ,   obj = {
+            parse:  function (str, opt) {
+                if (!opt) opt = {};
+                var tokens = tokenise(str);
+                return parse(tokens, opt);
+            }
+    };
+
+    if (inNode) module.exports = obj;
+    else        self.WebIDL2 = obj;
+}());
diff --git a/LayoutTests/http/tests/resources/idlharness.js b/LayoutTests/http/tests/resources/idlharness.js
new file mode 100644 (file)
index 0000000..8e41703
--- /dev/null
@@ -0,0 +1,1706 @@
+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/* For user documentation see docs/idlharness.md */
+
+/**
+ * Notes for people who want to edit this file (not just use it as a library):
+ *
+ * Most of the interesting stuff happens in the derived classes of IdlObject,
+ * especially IdlInterface.  The entry point for all IdlObjects is .test(),
+ * which is called by IdlArray.test().  An IdlObject is conceptually just
+ * "thing we want to run tests on", and an IdlArray is an array of IdlObjects
+ * with some additional data thrown in.
+ *
+ * The object model is based on what WebIDLParser.js produces, which is in turn
+ * based on its pegjs grammar.  If you want to figure out what properties an
+ * object will have from WebIDLParser.js, the best way is to look at the
+ * grammar:
+ *
+ *   https://github.com/darobin/webidl.js/blob/master/lib/grammar.peg
+ *
+ * So for instance:
+ *
+ *   // interface definition
+ *   interface
+ *       =   extAttrs:extendedAttributeList? S? "interface" S name:identifier w herit:ifInheritance? w "{" w mem:ifMember* w "}" w ";" w
+ *           { return { type: "interface", name: name, inheritance: herit, members: mem, extAttrs: extAttrs }; }
+ *
+ * This means that an "interface" object will have a .type property equal to
+ * the string "interface", a .name property equal to the identifier that the
+ * parser found, an .inheritance property equal to either null or the result of
+ * the "ifInheritance" production found elsewhere in the grammar, and so on.
+ * After each grammatical production is a JavaScript function in curly braces
+ * that gets called with suitable arguments and returns some JavaScript value.
+ *
+ * (Note that the version of WebIDLParser.js we use might sometimes be
+ * out-of-date or forked.)
+ *
+ * The members and methods of the classes defined by this file are all at least
+ * briefly documented, hopefully.
+ */
+(function(){
+"use strict";
+/// Helpers ///
+function constValue (cnt) {
+    if (cnt.type === "null") return null;
+    if (cnt.type === "NaN") return NaN;
+    if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity;
+    return cnt.value;
+}
+
+function minOverloadLength(overloads) {
+    if (!overloads.length) {
+        return 0;
+    }
+
+    return overloads.map(function(attr) {
+        return attr.arguments ? attr.arguments.filter(function(arg) {
+            return !arg.optional && !arg.variadic;
+        }).length : 0;
+    })
+    .reduce(function(m, n) { return Math.min(m, n); });
+}
+
+/// IdlArray ///
+// Entry point
+self.IdlArray = function()
+//@{
+{
+    /**
+     * A map from strings to the corresponding named IdlObject, such as
+     * IdlInterface or IdlException.  These are the things that test() will run
+     * tests on.
+     */
+    this.members = {};
+
+    /**
+     * A map from strings to arrays of strings.  The keys are interface or
+     * exception names, and are expected to also exist as keys in this.members
+     * (otherwise they'll be ignored).  This is populated by add_objects() --
+     * see documentation at the start of the file.  The actual tests will be
+     * run by calling this.members[name].test_object(obj) for each obj in
+     * this.objects[name].  obj is a string that will be eval'd to produce a
+     * JavaScript value, which is supposed to be an object implementing the
+     * given IdlObject (interface, exception, etc.).
+     */
+    this.objects = {};
+
+    /**
+     * When adding multiple collections of IDLs one at a time, an earlier one
+     * might contain a partial interface or implements statement that depends
+     * on a later one.  Save these up and handle them right before we run
+     * tests.
+     *
+     * .partials is simply an array of objects from WebIDLParser.js'
+     * "partialinterface" production.  .implements maps strings to arrays of
+     * strings, such that
+     *
+     *   A implements B;
+     *   A implements C;
+     *   D implements E;
+     *
+     * results in { A: ["B", "C"], D: ["E"] }.
+     */
+    this.partials = [];
+    this["implements"] = {};
+};
+
+//@}
+IdlArray.prototype.add_idls = function(raw_idls)
+//@{
+{
+    /** Entry point.  See documentation at beginning of file. */
+    this.internal_add_idls(WebIDL2.parse(raw_idls));
+};
+
+//@}
+IdlArray.prototype.add_untested_idls = function(raw_idls)
+//@{
+{
+    /** Entry point.  See documentation at beginning of file. */
+    var parsed_idls = WebIDL2.parse(raw_idls);
+    for (var i = 0; i < parsed_idls.length; i++)
+    {
+        parsed_idls[i].untested = true;
+        if ("members" in parsed_idls[i])
+        {
+            for (var j = 0; j < parsed_idls[i].members.length; j++)
+            {
+                parsed_idls[i].members[j].untested = true;
+            }
+        }
+    }
+    this.internal_add_idls(parsed_idls);
+};
+
+//@}
+IdlArray.prototype.internal_add_idls = function(parsed_idls)
+//@{
+{
+    /**
+     * Internal helper called by add_idls() and add_untested_idls().
+     * parsed_idls is an array of objects that come from WebIDLParser.js's
+     * "definitions" production.  The add_untested_idls() entry point
+     * additionally sets an .untested property on each object (and its
+     * .members) so that they'll be skipped by test() -- they'll only be
+     * used for base interfaces of tested interfaces, return types, etc.
+     */
+    parsed_idls.forEach(function(parsed_idl)
+    {
+        if (parsed_idl.type == "interface" && parsed_idl.partial)
+        {
+            this.partials.push(parsed_idl);
+            return;
+        }
+
+        if (parsed_idl.type == "implements")
+        {
+            if (!(parsed_idl.target in this["implements"]))
+            {
+                this["implements"][parsed_idl.target] = [];
+            }
+            this["implements"][parsed_idl.target].push(parsed_idl["implements"]);
+            return;
+        }
+
+        parsed_idl.array = this;
+        if (parsed_idl.name in this.members)
+        {
+            throw "Duplicate identifier " + parsed_idl.name;
+        }
+        switch(parsed_idl.type)
+        {
+        case "interface":
+            this.members[parsed_idl.name] =
+                new IdlInterface(parsed_idl, /* is_callback = */ false);
+            break;
+
+        case "dictionary":
+            // Nothing to test, but we need the dictionary info around for type
+            // checks
+            this.members[parsed_idl.name] = new IdlDictionary(parsed_idl);
+            break;
+
+        case "typedef":
+            this.members[parsed_idl.name] = new IdlTypedef(parsed_idl);
+            break;
+
+        case "callback":
+            // TODO
+            console.log("callback not yet supported");
+            break;
+
+        case "enum":
+            this.members[parsed_idl.name] = new IdlEnum(parsed_idl);
+            break;
+
+        case "callback interface":
+            this.members[parsed_idl.name] =
+                new IdlInterface(parsed_idl, /* is_callback = */ true);
+            break;
+
+        default:
+            throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported";
+        }
+    }.bind(this));
+};
+
+//@}
+IdlArray.prototype.add_objects = function(dict)
+//@{
+{
+    /** Entry point.  See documentation at beginning of file. */
+    for (var k in dict)
+    {
+        if (k in this.objects)
+        {
+            this.objects[k] = this.objects[k].concat(dict[k]);
+        }
+        else
+        {
+            this.objects[k] = dict[k];
+        }
+    }
+};
+
+//@}
+IdlArray.prototype.prevent_multiple_testing = function(name)
+//@{
+{
+    /** Entry point.  See documentation at beginning of file. */
+    this.members[name].prevent_multiple_testing = true;
+};
+
+//@}
+IdlArray.prototype.recursively_get_implements = function(interface_name)
+//@{
+{
+    /**
+     * Helper function for test().  Returns an array of things that implement
+     * interface_name, so if the IDL contains
+     *
+     *   A implements B;
+     *   B implements C;
+     *   B implements D;
+     *
+     * then recursively_get_implements("A") should return ["B", "C", "D"].
+     */
+    var ret = this["implements"][interface_name];
+    if (ret === undefined)
+    {
+        return [];
+    }
+    for (var i = 0; i < this["implements"][interface_name].length; i++)
+    {
+        ret = ret.concat(this.recursively_get_implements(ret[i]));
+        if (ret.indexOf(ret[i]) != ret.lastIndexOf(ret[i]))
+        {
+            throw "Circular implements statements involving " + ret[i];
+        }
+    }
+    return ret;
+};
+
+//@}
+IdlArray.prototype.test = function()
+//@{
+{
+    /** Entry point.  See documentation at beginning of file. */
+
+    // First merge in all the partial interfaces and implements statements we
+    // encountered.
+    this.partials.forEach(function(parsed_idl)
+    {
+        if (!(parsed_idl.name in this.members)
+        || !(this.members[parsed_idl.name] instanceof IdlInterface))
+        {
+            throw "Partial interface " + parsed_idl.name + " with no original interface";
+        }
+        if (parsed_idl.extAttrs)
+        {
+            parsed_idl.extAttrs.forEach(function(extAttr)
+            {
+                this.members[parsed_idl.name].extAttrs.push(extAttr);
+            }.bind(this));
+        }
+        parsed_idl.members.forEach(function(member)
+        {
+            this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member));
+        }.bind(this));
+    }.bind(this));
+    this.partials = [];
+
+    for (var lhs in this["implements"])
+    {
+        this.recursively_get_implements(lhs).forEach(function(rhs)
+        {
+            var errStr = lhs + " implements " + rhs + ", but ";
+            if (!(lhs in this.members)) throw errStr + lhs + " is undefined.";
+            if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface.";
+            if (!(rhs in this.members)) throw errStr + rhs + " is undefined.";
+            if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface.";
+            this.members[rhs].members.forEach(function(member)
+            {
+                this.members[lhs].members.push(new IdlInterfaceMember(member));
+            }.bind(this));
+        }.bind(this));
+    }
+    this["implements"] = {};
+
+    // Now run test() on every member, and test_object() for every object.
+    for (var name in this.members)
+    {
+        this.members[name].test();
+        if (name in this.objects)
+        {
+            this.objects[name].forEach(function(str)
+            {
+                this.members[name].test_object(str);
+            }.bind(this));
+        }
+    }
+};
+
+//@}
+IdlArray.prototype.assert_type_is = function(value, type)
+//@{
+{
+    /**
+     * Helper function that tests that value is an instance of type according
+     * to the rules of WebIDL.  value is any JavaScript value, and type is an
+     * object produced by WebIDLParser.js' "type" production.  That production
+     * is fairly elaborate due to the complexity of WebIDL's types, so it's
+     * best to look at the grammar to figure out what properties it might have.
+     */
+    if (type.idlType == "any")
+    {
+        // No assertions to make
+        return;
+    }
+
+    if (type.nullable && value === null)
+    {
+        // This is fine
+        return;
+    }
+
+    if (type.array)
+    {
+        // TODO: not supported yet
+        return;
+    }
+
+    if (type.sequence)
+    {
+        assert_true(Array.isArray(value), "is not array");
+        if (!value.length)
+        {
+            // Nothing we can do.
+            return;
+        }
+        this.assert_type_is(value[0], type.idlType.idlType);
+        return;
+    }
+
+    type = type.idlType;
+
+    switch(type)
+    {
+        case "void":
+            assert_equals(value, undefined);
+            return;
+
+        case "boolean":
+            assert_equals(typeof value, "boolean");
+            return;
+
+        case "byte":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(-128 <= value && value <= 127, "byte " + value + " not in range [-128, 127]");
+            return;
+
+        case "octet":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(0 <= value && value <= 255, "octet " + value + " not in range [0, 255]");
+            return;
+
+        case "short":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(-32768 <= value && value <= 32767, "short " + value + " not in range [-32768, 32767]");
+            return;
+
+        case "unsigned short":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(0 <= value && value <= 65535, "unsigned short " + value + " not in range [0, 65535]");
+            return;
+
+        case "long":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(-2147483648 <= value && value <= 2147483647, "long " + value + " not in range [-2147483648, 2147483647]");
+            return;
+
+        case "unsigned long":
+            assert_equals(typeof value, "number");
+            assert_equals(value, Math.floor(value), "not an integer");
+            assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]");
+            return;
+
+        case "long long":
+            assert_equals(typeof value, "number");
+            return;
+
+        case "unsigned long long":
+        case "DOMTimeStamp":
+            assert_equals(typeof value, "number");
+            assert_true(0 <= value, "unsigned long long is negative");
+            return;
+
+        case "float":
+        case "double":
+        case "DOMHighResTimeStamp":
+        case "unrestricted float":
+        case "unrestricted double":
+            // TODO: distinguish these cases
+            assert_equals(typeof value, "number");
+            return;
+
+        case "DOMString":
+        case "ByteString":
+        case "USVString":
+            // TODO: https://github.com/w3c/testharness.js/issues/92
+            assert_equals(typeof value, "string");
+            return;
+
+        case "object":
+            assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
+            return;
+    }
+
+    if (!(type in this.members))
+    {
+        throw "Unrecognized type " + type;
+    }
+
+    if (this.members[type] instanceof IdlInterface)
+    {
+        // We don't want to run the full
+        // IdlInterface.prototype.test_instance_of, because that could result
+        // in an infinite loop.  TODO: This means we don't have tests for
+        // NoInterfaceObject interfaces, and we also can't test objects that
+        // come from another self.
+        assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
+        if (value instanceof Object
+        && !this.members[type].has_extended_attribute("NoInterfaceObject")
+        && type in self)
+        {
+            assert_true(value instanceof self[type], "not instanceof " + type);
+        }
+    }
+    else if (this.members[type] instanceof IdlEnum)
+    {
+        assert_equals(typeof value, "string");
+    }
+    else if (this.members[type] instanceof IdlDictionary)
+    {
+        // TODO: Test when we actually have something to test this on
+    }
+    else if (this.members[type] instanceof IdlTypedef)
+    {
+        // TODO: Test when we actually have something to test this on
+    }
+    else
+    {
+        throw "Type " + type + " isn't an interface or dictionary";
+    }
+};
+//@}
+
+/// IdlObject ///
+function IdlObject() {}
+IdlObject.prototype.test = function()
+//@{
+{
+    /**
+     * By default, this does nothing, so no actual tests are run for IdlObjects
+     * that don't define any (e.g., IdlDictionary at the time of this writing).
+     */
+};
+
+//@}
+IdlObject.prototype.has_extended_attribute = function(name)
+//@{
+{
+    /**
+     * This is only meaningful for things that support extended attributes,
+     * such as interfaces, exceptions, and members.
+     */
+    return this.extAttrs.some(function(o)
+    {
+        return o.name == name;
+    });
+};
+
+//@}
+
+/// IdlDictionary ///
+// Used for IdlArray.prototype.assert_type_is
+function IdlDictionary(obj)
+//@{
+{
+    /**
+     * obj is an object produced by the WebIDLParser.js "dictionary"
+     * production.
+     */
+
+    /** Self-explanatory. */
+    this.name = obj.name;
+
+    /** An array of objects produced by the "dictionaryMember" production. */
+    this.members = obj.members;
+
+    /**
+     * The name (as a string) of the dictionary type we inherit from, or null
+     * if there is none.
+     */
+    this.base = obj.inheritance;
+}
+
+//@}
+IdlDictionary.prototype = Object.create(IdlObject.prototype);
+
+/// IdlInterface ///
+function IdlInterface(obj, is_callback) {
+    /**
+     * obj is an object produced by the WebIDLParser.js "exception" or
+     * "interface" production, as appropriate.
+     */
+
+    /** Self-explanatory. */
+    this.name = obj.name;
+
+    /** A back-reference to our IdlArray. */
+    this.array = obj.array;
+
+    /**
+     * An indicator of whether we should run tests on the (exception) interface
+     * object and (exception) interface prototype object.  Tests on members are
+     * controlled by .untested on each member, not this.
+     */
+    this.untested = obj.untested;
+
+    /** An array of objects produced by the "ExtAttr" production. */
+    this.extAttrs = obj.extAttrs;
+
+    /** An array of IdlInterfaceMembers. */
+    this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); });
+    if (this.has_extended_attribute("Unforgeable")) {
+        this.members
+            .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); })
+            .forEach(function(m) { return m.isUnforgeable = true; });
+    }
+
+    /**
+     * The name (as a string) of the type we inherit from, or null if there is
+     * none.
+     */
+    this.base = obj.inheritance;
+
+    this._is_callback = is_callback;
+}
+IdlInterface.prototype = Object.create(IdlObject.prototype);
+IdlInterface.prototype.is_callback = function()
+//@{
+{
+    return this._is_callback;
+};
+//@}
+
+IdlInterface.prototype.has_constants = function()
+//@{
+{
+    return this.members.some(function(member) {
+        return member.type === "const";
+    });
+};
+//@}
+
+IdlInterface.prototype.is_global = function()
+//@{
+{
+    return this.extAttrs.some(function(attribute) {
+        return attribute.name === "Global" ||
+               attribute.name === "PrimaryGlobal";
+    });
+};
+//@}
+
+IdlInterface.prototype.test = function()
+//@{
+{
+    if (this.has_extended_attribute("NoInterfaceObject"))
+    {
+        // No tests to do without an instance.  TODO: We should still be able
+        // to run tests on the prototype object, if we obtain one through some
+        // other means.
+        return;
+    }
+
+    if (!this.untested)
+    {
+        // First test things to do with the exception/interface object and
+        // exception/interface prototype object.
+        this.test_self();
+    }
+    // Then test things to do with its members (constants, fields, attributes,
+    // operations, . . .).  These are run even if .untested is true, because
+    // members might themselves be marked as .untested.  This might happen to
+    // interfaces if the interface itself is untested but a partial interface
+    // that extends it is tested -- then the interface itself and its initial
+    // members will be marked as untested, but the members added by the partial
+    // interface are still tested.
+    this.test_members();
+};
+//@}
+
+IdlInterface.prototype.test_self = function()
+//@{
+{
+    test(function()
+    {
+        // This function tests WebIDL as of 2015-01-13.
+        // TODO: Consider [Exposed].
+
+        // "For every interface that is exposed in a given ECMAScript global
+        // environment and:
+        // * is a callback interface that has constants declared on it, or
+        // * is a non-callback interface that is not declared with the
+        //   [NoInterfaceObject] extended attribute,
+        // a corresponding property MUST exist on the ECMAScript global object.
+        // The name of the property is the identifier of the interface, and its
+        // value is an object called the interface object.
+        // The property has the attributes { [[Writable]]: true,
+        // [[Enumerable]]: false, [[Configurable]]: true }."
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        // TODO: Should we test here that the property is actually writable
+        // etc., or trust getOwnPropertyDescriptor?
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+        var desc = Object.getOwnPropertyDescriptor(self, this.name);
+        assert_false("get" in desc, "self's property " + format_value(this.name) + " has getter");
+        assert_false("set" in desc, "self's property " + format_value(this.name) + " has setter");
+        assert_true(desc.writable, "self's property " + format_value(this.name) + " is not writable");
+        assert_false(desc.enumerable, "self's property " + format_value(this.name) + " is enumerable");
+        assert_true(desc.configurable, "self's property " + format_value(this.name) + " is not configurable");
+
+        if (this.is_callback()) {
+            // "The internal [[Prototype]] property of an interface object for
+            // a callback interface MUST be the Object.prototype object."
+            assert_equals(Object.getPrototypeOf(self[this.name]), Object.prototype,
+                          "prototype of self's property " + format_value(this.name) + " is not Object.prototype");
+
+            return;
+        }
+
+        // "The interface object for a given non-callback interface is a
+        // function object."
+        // "If an object is defined to be a function object, then it has
+        // characteristics as follows:"
+
+        // Its [[Prototype]] internal property is otherwise specified (see
+        // below).
+
+        // "* Its [[Get]] internal property is set as described in ECMA-262
+        //    section 9.1.8."
+        // Not much to test for this.
+
+        // "* Its [[Construct]] internal property is set as described in
+        //    ECMA-262 section 19.2.2.3."
+        // Tested below if no constructor is defined.  TODO: test constructors
+        // if defined.
+
+        // "* Its @@hasInstance property is set as described in ECMA-262
+        //    section 19.2.3.8, unless otherwise specified."
+        // TODO
+
+        // ES6 (rev 30) 19.1.3.6:
+        // "Else, if O has a [[Call]] internal method, then let builtinTag be
+        // "Function"."
+        assert_class_string(self[this.name], "Function", "class string of " + this.name);
+
+        // "The [[Prototype]] internal property of an interface object for a
+        // non-callback interface is determined as follows:"
+        var prototype = Object.getPrototypeOf(self[this.name]);
+        if (this.base) {
+            // "* If the interface inherits from some other interface, the
+            //    value of [[Prototype]] is the interface object for that other
+            //    interface."
+            var has_interface_object =
+                !this.array
+                     .members[this.base]
+                     .has_extended_attribute("NoInterfaceObject");
+            if (has_interface_object) {
+                assert_own_property(self, this.base,
+                                    'should inherit from ' + this.base +
+                                    ', but self has no such property');
+                assert_equals(prototype, self[this.base],
+                              'prototype of ' + this.name + ' is not ' +
+                              this.base);
+            }
+        } else {
+            // "If the interface doesn't inherit from any other interface, the
+            // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262],
+            // section 6.1.7.4)."
+            assert_equals(prototype, Function.prototype,
+                          "prototype of self's property " + format_value(this.name) + " is not Function.prototype");
+        }
+
+        if (!this.has_extended_attribute("Constructor")) {
+            // "The internal [[Call]] method of the interface object behaves as
+            // follows . . .
+            //
+            // "If I was not declared with a [Constructor] extended attribute,
+            // then throw a TypeError."
+            assert_throws(new TypeError(), function() {
+                self[this.name]();
+            }.bind(this), "interface object didn't throw TypeError when called as a function");
+            assert_throws(new TypeError(), function() {
+                new self[this.name]();
+            }.bind(this), "interface object didn't throw TypeError when called as a constructor");
+        }
+    }.bind(this), this.name + " interface: existence and properties of interface object");
+
+    if (!this.is_callback()) {
+        test(function() {
+            // This function tests WebIDL as of 2014-10-25.
+            // https://heycam.github.io/webidl/#es-interface-call
+
+            assert_own_property(self, this.name,
+                                "self does not have own property " + format_value(this.name));
+
+            // "Interface objects for non-callback interfaces MUST have a
+            // property named “length” with attributes { [[Writable]]: false,
+            // [[Enumerable]]: false, [[Configurable]]: true } whose value is
+            // a Number."
+            assert_own_property(self[this.name], "length");
+            var desc = Object.getOwnPropertyDescriptor(self[this.name], "length");
+            assert_false("get" in desc, this.name + ".length has getter");
+            assert_false("set" in desc, this.name + ".length has setter");
+            assert_false(desc.writable, this.name + ".length is writable");
+            assert_false(desc.enumerable, this.name + ".length is enumerable");
+            assert_true(desc.configurable, this.name + ".length is not configurable");
+
+            var constructors = this.extAttrs
+                .filter(function(attr) { return attr.name == "Constructor"; });
+            var expected_length = minOverloadLength(constructors);
+            assert_equals(self[this.name].length, expected_length, "wrong value for " + this.name + ".length");
+        }.bind(this), this.name + " interface object length");
+    }
+
+    // TODO: Test named constructors if I find any interfaces that have them.
+
+    test(function()
+    {
+        // This function tests WebIDL as of 2015-01-21.
+        // https://heycam.github.io/webidl/#interface-object
+
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        // "An interface object for a non-callback interface must have a
+        // property named “prototype” with attributes { [[Writable]]: false,
+        // [[Enumerable]]: false, [[Configurable]]: false } whose value is an
+        // object called the interface prototype object. This object has
+        // properties that correspond to the regular attributes and regular
+        // operations defined on the interface, and is described in more detail
+        // in section 4.5.4 below."
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], "prototype");
+        assert_false("get" in desc, this.name + ".prototype has getter");
+        assert_false("set" in desc, this.name + ".prototype has setter");
+        assert_false(desc.writable, this.name + ".prototype is writable");
+        assert_false(desc.enumerable, this.name + ".prototype is enumerable");
+        assert_false(desc.configurable, this.name + ".prototype is configurable");
+
+        // Next, test that the [[Prototype]] of the interface prototype object
+        // is correct. (This is made somewhat difficult by the existence of
+        // [NoInterfaceObject].)
+        // TODO: Aryeh thinks there's at least other place in this file where
+        //       we try to figure out if an interface prototype object is
+        //       correct. Consolidate that code.
+
+        // "The interface prototype object for a given interface A must have an
+        // internal [[Prototype]] property whose value is returned from the
+        // following steps:
+        // "If A is declared with the [Global] or [PrimaryGlobal] extended
+        // attribute, and A supports named properties, then return the named
+        // properties object for A, as defined in section 4.5.5 below.
+        // "Otherwise, if A is declared to inherit from another interface, then
+        // return the interface prototype object for the inherited interface.
+        // "Otherwise, if A is declared with the [ArrayClass] extended
+        // attribute, then return %ArrayPrototype% ([ECMA-262], section
+        // 6.1.7.4).
+        // "Otherwise, return %ObjectPrototype% ([ECMA-262], section 6.1.7.4).
+        // ([ECMA-262], section 15.2.4).
+        if (this.name === "Window") {
+            assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
+                                'WindowProperties',
+                                'Class name for prototype of Window' +
+                                '.prototype is not "WindowProperties"');
+        } else {
+            var inherit_interface, inherit_interface_has_interface_object;
+            if (this.base) {
+                inherit_interface = this.base;
+                inherit_interface_has_interface_object =
+                    !this.array
+                         .members[inherit_interface]
+                         .has_extended_attribute("NoInterfaceObject");
+            } else if (this.has_extended_attribute('ArrayClass')) {
+                inherit_interface = 'Array';
+                inherit_interface_has_interface_object = true;
+            } else {
+                inherit_interface = 'Object';
+                inherit_interface_has_interface_object = true;
+            }
+            if (inherit_interface_has_interface_object) {
+                assert_own_property(self, inherit_interface,
+                                    'should inherit from ' + inherit_interface + ', but self has no such property');
+                assert_own_property(self[inherit_interface], 'prototype',
+                                    'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
+                assert_equals(Object.getPrototypeOf(self[this.name].prototype),
+                              self[inherit_interface].prototype,
+                              'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype');
+            } else {
+                // We can't test that we get the correct object, because this is the
+                // only way to get our hands on it. We only test that its class
+                // string, at least, is correct.
+                assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
+                                    inherit_interface + 'Prototype',
+                                    'Class name for prototype of ' + this.name +
+                                    '.prototype is not "' + inherit_interface + 'Prototype"');
+            }
+        }
+
+        // "The class string of an interface prototype object is the
+        // concatenation of the interface’s identifier and the string
+        // “Prototype”."
+        assert_class_string(self[this.name].prototype, this.name + "Prototype",
+                            "class string of " + this.name + ".prototype");
+        // String() should end up calling {}.toString if nothing defines a
+        // stringifier.
+        if (!this.has_stringifier()) {
+            assert_equals(String(self[this.name].prototype), "[object " + this.name + "Prototype]",
+                    "String(" + this.name + ".prototype)");
+        }
+    }.bind(this), this.name + " interface: existence and properties of interface prototype object");
+
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // "If the [NoInterfaceObject] extended attribute was not specified on
+        // the interface, then the interface prototype object must also have a
+        // property named “constructor” with attributes { [[Writable]]: true,
+        // [[Enumerable]]: false, [[Configurable]]: true } whose value is a
+        // reference to the interface object for the interface."
+        assert_own_property(self[this.name].prototype, "constructor",
+                            this.name + '.prototype does not have own property "constructor"');
+        var desc = Object.getOwnPropertyDescriptor(self[this.name].prototype, "constructor");
+        assert_false("get" in desc, this.name + ".prototype.constructor has getter");
+        assert_false("set" in desc, this.name + ".prototype.constructor has setter");
+        assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
+        assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
+        assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
+        assert_equals(self[this.name].prototype.constructor, self[this.name],
+                      this.name + '.prototype.constructor is not the same object as ' + this.name);
+    }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
+};
+
+//@}
+IdlInterface.prototype.test_member_const = function(member)
+//@{
+{
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        // "For each constant defined on an interface A, there must be
+        // a corresponding property on the interface object, if it
+        // exists."
+        assert_own_property(self[this.name], member.name);
+        // "The value of the property is that which is obtained by
+        // converting the constant’s IDL value to an ECMAScript
+        // value."
+        assert_equals(self[this.name][member.name], constValue(member.value),
+                      "property has wrong value");
+        // "The property has attributes { [[Writable]]: false,
+        // [[Enumerable]]: true, [[Configurable]]: false }."
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_false(desc.writable, "property is writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_false(desc.configurable, "property is configurable");
+    }.bind(this), this.name + " interface: constant " + member.name + " on interface object");
+    // "In addition, a property with the same characteristics must
+    // exist on the interface prototype object."
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        assert_own_property(self[this.name].prototype, member.name);
+        assert_equals(self[this.name].prototype[member.name], constValue(member.value),
+                      "property has wrong value");
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_false(desc.writable, "property is writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_false(desc.configurable, "property is configurable");
+    }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
+};
+
+
+//@}
+IdlInterface.prototype.test_member_attribute = function(member)
+//@{
+{
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        if (member["static"]) {
+            assert_own_property(self[this.name], member.name,
+                "The interface object must have a property " +
+                format_value(member.name));
+        } else if (this.is_global()) {
+            assert_own_property(self, member.name,
+                "The global object must have a property " +
+                format_value(member.name));
+            assert_false(member.name in self[this.name].prototype,
+                "The prototype object must not have a property " +
+                format_value(member.name));
+
+            // Try/catch around the get here, since it can legitimately throw.
+            // If it does, we obviously can't check for equality with direct
+            // invocation of the getter.
+            var gotValue;
+            var propVal;
+            try {
+                propVal = self[member.name];
+                gotValue = true;
+            } catch (e) {
+                gotValue = false;
+            }
+            if (gotValue) {
+                var getter = Object.getOwnPropertyDescriptor(self, member.name).get;
+                assert_equals(typeof(getter), "function",
+                              format_value(member.name) + " must have a getter");
+                assert_equals(propVal, getter.call(undefined),
+                              "Gets on a global should not require an explicit this");
+            }
+            this.do_interface_attribute_asserts(self, member);
+        } else {
+            assert_true(member.name in self[this.name].prototype,
+                "The prototype object must have a property " +
+                format_value(member.name));
+
+            if (!member.has_extended_attribute("LenientThis")) {
+                assert_throws(new TypeError(), function() {
+                    self[this.name].prototype[member.name];
+                }.bind(this), "getting property on prototype object must throw TypeError");
+            } else {
+                assert_equals(self[this.name].prototype[member.name], undefined,
+                              "getting property on prototype object must return undefined");
+            }
+            this.do_interface_attribute_asserts(self[this.name].prototype, member);
+        }
+    }.bind(this), this.name + " interface: attribute " + member.name);
+};
+
+//@}
+IdlInterface.prototype.test_member_operation = function(member)
+//@{
+{
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // "For each unique identifier of an operation defined on the
+        // interface, there must be a corresponding property on the
+        // interface prototype object (if it is a regular operation) or
+        // the interface object (if it is a static operation), unless
+        // the effective overload set for that identifier and operation
+        // and with an argument count of 0 (for the ECMAScript language
+        // binding) has no entries."
+        //
+        var memberHolderObject;
+        if (member["static"]) {
+            assert_own_property(self[this.name], member.name,
+                    "interface object missing static operation");
+            memberHolderObject = self[this.name];
+        } else if (this.is_global()) {
+            assert_own_property(self, member.name,
+                    "global object missing non-static operation");
+            memberHolderObject = self;
+        } else {
+            assert_own_property(self[this.name].prototype, member.name,
+                    "interface prototype object missing non-static operation");
+            memberHolderObject = self[this.name].prototype;
+        }
+
+        this.do_member_operation_asserts(memberHolderObject, member);
+    }.bind(this), this.name + " interface: operation " + member.name +
+    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
+    ")");
+};
+
+//@}
+IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member)
+//@{
+{
+    var operationUnforgeable = member.isUnforgeable;
+    var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name);
+    // "The property has attributes { [[Writable]]: B,
+    // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
+    // operation is unforgeable on the interface, and true otherwise".
+    assert_false("get" in desc, "property has getter");
+    assert_false("set" in desc, "property has setter");
+    assert_equals(desc.writable, !operationUnforgeable,
+                  "property should be writable if and only if not unforgeable");
+    assert_true(desc.enumerable, "property is not enumerable");
+    assert_equals(desc.configurable, !operationUnforgeable,
+                  "property should be configurable if and only if not unforgeable");
+    // "The value of the property is a Function object whose
+    // behavior is as follows . . ."
+    assert_equals(typeof memberHolderObject[member.name], "function",
+                  "property must be a function");
+    // "The value of the Function object’s “length” property is
+    // a Number determined as follows:
+    // ". . .
+    // "Return the length of the shortest argument list of the
+    // entries in S."
+    assert_equals(memberHolderObject[member.name].length,
+        minOverloadLength(this.members.filter(function(m) {
+            return m.type == "operation" && m.name == member.name;
+        })),
+        "property has wrong .length");
+
+    // Make some suitable arguments
+    var args = member.arguments.map(function(arg) {
+        return create_suitable_object(arg.idlType);
+    });
+
+    // "Let O be a value determined as follows:
+    // ". . .
+    // "Otherwise, throw a TypeError."
+    // This should be hit if the operation is not static, there is
+    // no [ImplicitThis] attribute, and the this value is null.
+    //
+    // TODO: We currently ignore the [ImplicitThis] case.  Except we manually
+    // check for globals, since otherwise we'll invoke window.close().  And we
+    // have to skip this test for anything that on the proto chain of "self",
+    // since that does in fact have implicit-this behavior.
+    if (!member["static"]) {
+        if (!this.is_global() &&
+            memberHolderObject[member.name] != self[member.name])
+        {
+            assert_throws(new TypeError(), function() {
+                memberHolderObject[member.name].apply(null, args);
+            }, "calling operation with this = null didn't throw TypeError");
+        }
+
+        // ". . . If O is not null and is also not a platform object
+        // that implements interface I, throw a TypeError."
+        //
+        // TODO: Test a platform object that implements some other
+        // interface.  (Have to be sure to get inheritance right.)
+        assert_throws(new TypeError(), function() {
+            memberHolderObject[member.name].apply({}, args);
+        }, "calling operation with this = {} didn't throw TypeError");
+    }
+}
+
+//@}
+IdlInterface.prototype.test_member_stringifier = function(member)
+//@{
+{
+    test(function()
+    {
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(self[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // ". . . the property exists on the interface prototype object."
+        var interfacePrototypeObject = self[this.name].prototype;
+        assert_own_property(self[this.name].prototype, "toString",
+                "interface prototype object missing non-static operation");
+
+        var stringifierUnforgeable = member.isUnforgeable;
+        var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
+        // "The property has attributes { [[Writable]]: B,
+        // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
+        // stringifier is unforgeable on the interface, and true otherwise."
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_equals(desc.writable, !stringifierUnforgeable,
+                      "property should be writable if and only if not unforgeable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_equals(desc.configurable, !stringifierUnforgeable,
+                      "property should be configurable if and only if not unforgeable");
+        // "The value of the property is a Function object, which behaves as
+        // follows . . ."
+        assert_equals(typeof interfacePrototypeObject.toString, "function",
+                      "property must be a function");
+        // "The value of the Function object’s “length” property is the Number
+        // value 0."
+        assert_equals(interfacePrototypeObject.toString.length, 0,
+            "property has wrong .length");
+
+        // "Let O be the result of calling ToObject on the this value."
+        assert_throws(new TypeError(), function() {
+            self[this.name].prototype.toString.apply(null, []);
+        }, "calling stringifier with this = null didn't throw TypeError");
+
+        // "If O is not an object that implements the interface on which the
+        // stringifier was declared, then throw a TypeError."
+        //
+        // TODO: Test a platform object that implements some other
+        // interface.  (Have to be sure to get inheritance right.)
+        assert_throws(new TypeError(), function() {
+            self[this.name].prototype.toString.apply({}, []);
+        }, "calling stringifier with this = {} didn't throw TypeError");
+    }.bind(this), this.name + " interface: stringifier");
+};
+
+//@}
+IdlInterface.prototype.test_members = function()
+//@{
+{
+    for (var i = 0; i < this.members.length; i++)
+    {
+        var member = this.members[i];
+        if (member.untested) {
+            continue;
+        }
+
+        switch (member.type) {
+        case "const":
+            this.test_member_const(member);
+            break;
+
+        case "attribute":
+            // For unforgeable attributes, we do the checks in
+            // test_interface_of instead.
+            if (!member.isUnforgeable)
+            {
+                this.test_member_attribute(member);
+            }
+            break;
+
+        case "operation":
+            // TODO: Need to correctly handle multiple operations with the same
+            // identifier.
+            // For unforgeable operations, we do the checks in
+            // test_interface_of instead.
+            if (member.name) {
+                if (!member.isUnforgeable)
+                {
+                    this.test_member_operation(member);
+                }
+            } else if (member.stringifier) {
+                this.test_member_stringifier(member);
+            }
+            break;
+
+        default:
+            // TODO: check more member types.
+            break;
+        }
+    }
+};
+
+//@}
+IdlInterface.prototype.test_object = function(desc)
+//@{
+{
+    var obj, exception = null;
+    try
+    {
+        obj = eval(desc);
+    }
+    catch(e)
+    {
+        exception = e;
+    }
+
+    // TODO: WebIDLParser doesn't currently support named legacycallers, so I'm
+    // not sure what those would look like in the AST
+    var expected_typeof = this.members.some(function(member)
+    {
+        return member.legacycaller
+            || ("idlType" in member && member.idlType.legacycaller)
+            || ("idlType" in member && typeof member.idlType == "object"
+            && "idlType" in member.idlType && member.idlType.idlType == "legacycaller");
+    }) ? "function" : "object";
+
+    this.test_primary_interface_of(desc, obj, exception, expected_typeof);
+    var current_interface = this;
+    while (current_interface)
+    {
+        if (!(current_interface.name in this.array.members))
+        {
+            throw "Interface " + current_interface.name + " not found (inherited by " + this.name + ")";
+        }
+        if (current_interface.prevent_multiple_testing && current_interface.already_tested)
+        {
+            return;
+        }
+        current_interface.test_interface_of(desc, obj, exception, expected_typeof);
+        current_interface = this.array.members[current_interface.base];
+    }
+};
+
+//@}
+IdlInterface.prototype.test_primary_interface_of = function(desc, obj, exception, expected_typeof)
+//@{
+{
+    // We can't easily test that its prototype is correct if there's no
+    // interface object, or the object is from a different global environment
+    // (not instanceof Object).  TODO: test in this case that its prototype at
+    // least looks correct, even if we can't test that it's actually correct.
+    if (!this.has_extended_attribute("NoInterfaceObject")
+    && (typeof obj != expected_typeof || obj instanceof Object))
+    {
+        test(function()
+        {
+            assert_equals(exception, null, "Unexpected exception when evaluating object");
+            assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+            assert_own_property(self, this.name,
+                                "self does not have own property " + format_value(this.name));
+            assert_own_property(self[this.name], "prototype",
+                                'interface "' + this.name + '" does not have own property "prototype"');
+
+            // "The value of the internal [[Prototype]] property of the
+            // platform object is the interface prototype object of the primary
+            // interface from the platform object’s associated global
+            // environment."
+            assert_equals(Object.getPrototypeOf(obj),
+                          self[this.name].prototype,
+                          desc + "'s prototype is not " + this.name + ".prototype");
+        }.bind(this), this.name + " must be primary interface of " + desc);
+    }
+
+    // "The class string of a platform object that implements one or more
+    // interfaces must be the identifier of the primary interface of the
+    // platform object."
+    test(function()
+    {
+        assert_equals(exception, null, "Unexpected exception when evaluating object");
+        assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+        assert_class_string(obj, this.name, "class string of " + desc);
+        if (!this.has_stringifier())
+        {
+            assert_equals(String(obj), "[object " + this.name + "]", "String(" + desc + ")");
+        }
+    }.bind(this), "Stringification of " + desc);
+};
+
+//@}
+IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expected_typeof)
+//@{
+{
+    // TODO: Indexed and named properties, more checks on interface members
+    this.already_tested = true;
+
+    for (var i = 0; i < this.members.length; i++)
+    {
+        var member = this.members[i];
+        if (member.type == "attribute" && member.isUnforgeable)
+        {
+            test(function()
+            {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
+                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+                this.do_interface_attribute_asserts(obj, member);
+            }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
+        }
+        else if (member.type == "operation" &&
+                 member.name &&
+                 member.isUnforgeable)
+        {
+            test(function()
+            {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
+                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+                assert_own_property(obj, member.name,
+                                    "Doesn't have the unforgeable operation property");
+                this.do_member_operation_asserts(obj, member);
+            }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
+        }
+        else if ((member.type == "const"
+        || member.type == "attribute"
+        || member.type == "operation")
+        && member.name)
+        {
+            test(function()
+            {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
+                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+                if (!member["static"]) {
+                    if (!this.is_global()) {
+                        assert_inherits(obj, member.name);
+                    } else {
+                        assert_own_property(obj, member.name);
+                    }
+
+                    if (member.type == "const")
+                    {
+                        assert_equals(obj[member.name], constValue(member.value));
+                    }
+                    if (member.type == "attribute")
+                    {
+                        // Attributes are accessor properties, so they might
+                        // legitimately throw an exception rather than returning
+                        // anything.
+                        var property, thrown = false;
+                        try
+                        {
+                            property = obj[member.name];
+                        }
+                        catch (e)
+                        {
+                            thrown = true;
+                        }
+                        if (!thrown)
+                        {
+                            this.array.assert_type_is(property, member.idlType);
+                        }
+                    }
+                    if (member.type == "operation")
+                    {
+                        assert_equals(typeof obj[member.name], "function");
+                    }
+                }
+            }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member.name + '" with the proper type (' + i + ')');
+        }
+        // TODO: This is wrong if there are multiple operations with the same
+        // identifier.
+        // TODO: Test passing arguments of the wrong type.
+        if (member.type == "operation" && member.name && member.arguments.length)
+        {
+            test(function()
+            {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
+                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+                if (!member["static"]) {
+                    if (!this.is_global() && !member.isUnforgeable) {
+                        assert_inherits(obj, member.name);
+                    } else {
+                        assert_own_property(obj, member.name);
+                    }
+                }
+                else
+                {
+                    assert_false(member.name in obj);
+                }
+
+                var minLength = minOverloadLength(this.members.filter(function(m) {
+                    return m.type == "operation" && m.name == member.name;
+                }));
+                var args = [];
+                for (var i = 0; i < minLength; i++) {
+                    assert_throws(new TypeError(), function()
+                    {
+                        obj[member.name].apply(obj, args);
+                    }.bind(this), "Called with " + i + " arguments");
+
+                    args.push(create_suitable_object(member.arguments[i].idlType));
+                }
+            }.bind(this), this.name + " interface: calling " + member.name +
+            "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
+            ") on " + desc + " with too few arguments must throw TypeError");
+        }
+    }
+};
+
+//@}
+IdlInterface.prototype.has_stringifier = function()
+//@{
+{
+    if (this.members.some(function(member) { return member.stringifier; })) {
+        return true;
+    }
+    if (this.base &&
+        this.array.members[this.base].has_stringifier()) {
+        return true;
+    }
+    return false;
+};
+
+//@}
+IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member)
+//@{
+{
+    // This function tests WebIDL as of 2015-01-27.
+    // TODO: Consider [Exposed].
+
+    // This is called by test_member_attribute() with the prototype as obj if
+    // it is not a global, and the global otherwise, and by test_interface_of()
+    // with the object as obj.
+
+    // "For each exposed attribute of the interface, whether it was declared on
+    // the interface itself or one of its consequential interfaces, there MUST
+    // exist a corresponding property. The characteristics of this property are
+    // as follows:"
+
+    // "The name of the property is the identifier of the attribute."
+    assert_own_property(obj, member.name);
+
+    // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]:
+    // true, [[Configurable]]: configurable }, where:
+    // "configurable is false if the attribute was declared with the
+    // [Unforgeable] extended attribute and true otherwise;
+    // "G is the attribute getter, defined below; and
+    // "S is the attribute setter, also defined below."
+    var desc = Object.getOwnPropertyDescriptor(obj, member.name);
+    assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor');
+    assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
+    assert_true(desc.enumerable, "property is not enumerable");
+    if (member.isUnforgeable)
+    {
+        assert_false(desc.configurable, "[Unforgeable] property must not be configurable");
+    }
+    else
+    {
+        assert_true(desc.configurable, "property must be configurable");
+    }
+
+
+    // "The attribute getter is a Function object whose behavior when invoked
+    // is as follows:"
+    assert_equals(typeof desc.get, "function", "getter must be Function");
+
+    // "If the attribute is a regular attribute, then:"
+    if (!member["static"]) {
+        // "If O is not a platform object that implements I, then:
+        // "If the attribute was specified with the [LenientThis] extended
+        // attribute, then return undefined.
+        // "Otherwise, throw a TypeError."
+        if (!member.has_extended_attribute("LenientThis")) {
+            assert_throws(new TypeError(), function() {
+                desc.get.call({});
+            }.bind(this), "calling getter on wrong object type must throw TypeError");
+        } else {
+            assert_equals(desc.get.call({}), undefined,
+                          "calling getter on wrong object type must return undefined");
+        }
+    }
+
+    // "The value of the Function object’s “length” property is the Number
+    // value 0."
+    assert_equals(desc.get.length, 0, "getter length must be 0");
+
+
+    // TODO: Test calling setter on the interface prototype (should throw
+    // TypeError in most cases).
+    if (member.readonly
+    && !member.has_extended_attribute("PutForwards")
+    && !member.has_extended_attribute("Replaceable"))
+    {
+        // "The attribute setter is undefined if the attribute is declared
+        // readonly and has neither a [PutForwards] nor a [Replaceable]
+        // extended attribute declared on it."
+        assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes");
+    }
+    else
+    {
+        // "Otherwise, it is a Function object whose behavior when
+        // invoked is as follows:"
+        assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
+
+        // "If the attribute is a regular attribute, then:"
+        if (!member["static"]) {
+            // "If /validThis/ is false and the attribute was not specified
+            // with the [LenientThis] extended attribute, then throw a
+            // TypeError."
+            // "If the attribute is declared with a [Replaceable] extended
+            // attribute, then: ..."
+            // "If validThis is false, then return."
+            if (!member.has_extended_attribute("LenientThis")) {
+                assert_throws(new TypeError(), function() {
+                    desc.set.call({});
+                }.bind(this), "calling setter on wrong object type must throw TypeError");
+            } else {
+                assert_equals(desc.set.call({}), undefined,
+                              "calling setter on wrong object type must return undefined");
+            }
+        }
+
+        // "The value of the Function object’s “length” property is the Number
+        // value 1."
+        assert_equals(desc.set.length, 1, "setter length must be 1");
+    }
+}
+//@}
+
+/// IdlInterfaceMember ///
+function IdlInterfaceMember(obj)
+//@{
+{
+    /**
+     * obj is an object produced by the WebIDLParser.js "ifMember" production.
+     * We just forward all properties to this object without modification,
+     * except for special extAttrs handling.
+     */
+    for (var k in obj)
+    {
+        this[k] = obj[k];
+    }
+    if (!("extAttrs" in this))
+    {
+        this.extAttrs = [];
+    }
+
+    this.isUnforgeable = this.has_extended_attribute("Unforgeable");
+}
+
+//@}
+IdlInterfaceMember.prototype = Object.create(IdlObject.prototype);
+
+/// Internal helper functions ///
+function create_suitable_object(type)
+//@{
+{
+    /**
+     * type is an object produced by the WebIDLParser.js "type" production.  We
+     * return a JavaScript value that matches the type, if we can figure out
+     * how.
+     */
+    if (type.nullable)
+    {
+        return null;
+    }
+    switch (type.idlType)
+    {
+        case "any":
+        case "boolean":
+            return true;
+
+        case "byte": case "octet": case "short": case "unsigned short":
+        case "long": case "unsigned long": case "long long":
+        case "unsigned long long": case "float": case "double":
+        case "unrestricted float": case "unrestricted double":
+            return 7;
+
+        case "DOMString":
+        case "ByteString":
+        case "USVString":
+            return "foo";
+
+        case "object":
+            return {a: "b"};
+
+        case "Node":
+            return document.createTextNode("abc");
+    }
+    return null;
+}
+//@}
+
+/// IdlEnum ///
+// Used for IdlArray.prototype.assert_type_is
+function IdlEnum(obj)
+//@{
+{
+    /**
+     * obj is an object produced by the WebIDLParser.js "dictionary"
+     * production.
+     */
+
+    /** Self-explanatory. */
+    this.name = obj.name;
+
+    /** An array of values produced by the "enum" production. */
+    this.values = obj.values;
+
+}
+//@}
+
+IdlEnum.prototype = Object.create(IdlObject.prototype);
+
+/// IdlTypedef ///
+// Used for IdlArray.prototype.assert_type_is
+function IdlTypedef(obj)
+//@{
+{
+    /**
+     * obj is an object produced by the WebIDLParser.js "typedef"
+     * production.
+     */
+
+    /** Self-explanatory. */
+    this.name = obj.name;
+
+    /** An array of values produced by the "typedef" production. */
+    this.values = obj.values;
+
+}
+//@}
+
+IdlTypedef.prototype = Object.create(IdlObject.prototype);
+
+}());
+// vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker:
diff --git a/LayoutTests/http/tests/resources/testharness.css b/LayoutTests/http/tests/resources/testharness.css
new file mode 100644 (file)
index 0000000..e2ed5a0
--- /dev/null
@@ -0,0 +1,102 @@
+html {
+    font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
+}
+
+#log .warning,
+#log .warning a {
+  color: black;
+  background: yellow;
+}
+
+#log .error,
+#log .error a {
+  color: white;
+  background: red;
+}
+
+section#summary {
+    margin-bottom:1em;
+}
+
+table#results {
+    border-collapse:collapse;
+    table-layout:fixed;
+    width:100%;
+}
+
+table#results th:first-child,
+table#results td:first-child {
+    width:4em;
+}
+
+table#results th:last-child,
+table#results td:last-child {
+    width:50%;
+}
+
+table#results.assertions th:last-child,
+table#results.assertions td:last-child {
+    width:35%;
+}
+
+table#results th {
+    padding:0;
+    padding-bottom:0.5em;
+    border-bottom:medium solid black;
+}
+
+table#results td {
+    padding:1em;
+    padding-bottom:0.5em;
+    border-bottom:thin solid black;
+}
+
+tr.pass > td:first-child {
+    color:green;
+}
+
+tr.fail > td:first-child {
+    color:red;
+}
+
+tr.timeout > td:first-child {
+    color:red;
+}
+
+tr.notrun > td:first-child {
+    color:blue;
+}
+
+.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {
+    font-variant:small-caps;
+}
+
+table#results span {
+    display:block;
+}
+
+table#results span.expected {
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+    white-space:pre;
+}
+
+table#results span.actual {
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
+    white-space:pre;
+}
+
+span.ok {
+    color:green;
+}
+
+tr.error {
+    color:red;
+}
+
+span.timeout {
+    color:red;
+}
+
+span.ok, span.timeout, span.error {
+    font-variant:small-caps;
+}
\ No newline at end of file
diff --git a/LayoutTests/http/tests/resources/testharness.js b/LayoutTests/http/tests/resources/testharness.js
new file mode 100644 (file)
index 0000000..f4c66aa
--- /dev/null
@@ -0,0 +1,2657 @@
+/*global self*/
+/*jshint latedef: nofunc*/
+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/* Documentation is in docs/api.md */
+
+(function ()
+{
+    var debug = false;
+    // default timeout is 10 seconds, test can override if needed
+    var settings = {
+        output:true,
+        harness_timeout:{
+            "normal":10000,
+            "long":60000
+        },
+        test_timeout:null,
+        message_events: ["start", "test_state", "result", "completion"]
+    };
+
+    var xhtml_ns = "http://www.w3.org/1999/xhtml";
+
+    /*
+     * TestEnvironment is an abstraction for the environment in which the test
+     * harness is used. Each implementation of a test environment has to provide
+     * the following interface:
+     *
+     * interface TestEnvironment {
+     *   // Invoked after the global 'tests' object has been created and it's
+     *   // safe to call add_*_callback() to register event handlers.
+     *   void on_tests_ready();
+     *
+     *   // Invoked after setup() has been called to notify the test environment
+     *   // of changes to the test harness properties.
+     *   void on_new_harness_properties(object properties);
+     *
+     *   // Should return a new unique default test name.
+     *   DOMString next_default_test_name();
+     *
+     *   // Should return the test harness timeout duration in milliseconds.
+     *   float test_timeout();
+     *
+     *   // Should return the global scope object.
+     *   object global_scope();
+     * };
+     */
+
+    /*
+     * A test environment with a DOM. The global object is 'window'. By default
+     * test results are displayed in a table. Any parent windows receive
+     * callbacks or messages via postMessage() when test events occur. See
+     * apisample11.html and apisample12.html.
+     */
+    function WindowTestEnvironment() {
+        this.name_counter = 0;
+        this.window_cache = null;
+        this.output_handler = null;
+        this.all_loaded = false;
+        var this_obj = this;
+        this.message_events = [];
+
+        this.message_functions = {
+            start: [add_start_callback, remove_start_callback,
+                    function (properties) {
+                        this_obj._dispatch("start_callback", [properties],
+                                           {type: "start", properties: properties});
+                    }],
+
+            test_state: [add_test_state_callback, remove_test_state_callback,
+                         function(test) {
+                             this_obj._dispatch("test_state_callback", [test],
+                                                {type: "test_state",
+                                                 test: test.structured_clone()});
+                         }],
+            result: [add_result_callback, remove_result_callback,
+                     function (test) {
+                         this_obj.output_handler.show_status();
+                         this_obj._dispatch("result_callback", [test],
+                                            {type: "result",
+                                             test: test.structured_clone()});
+                     }],
+            completion: [add_completion_callback, remove_completion_callback,
+                         function (tests, harness_status) {
+                             var cloned_tests = map(tests, function(test) {
+                                 return test.structured_clone();
+                             });
+                             this_obj._dispatch("completion_callback", [tests, harness_status],
+                                                {type: "complete",
+                                                 tests: cloned_tests,
+                                                 status: harness_status.structured_clone()});
+                         }]
+        }
+
+        on_event(window, 'load', function() {
+            this_obj.all_loaded = true;
+        });
+    }
+
+    WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
+        this._forEach_windows(
+                function(w, same_origin) {
+                    if (same_origin) {
+                        try {
+                            var has_selector = selector in w;
+                        } catch(e) {
+                            // If document.domain was set at some point same_origin can be
+                            // wrong and the above will fail.
+                            has_selector = false;
+                        }
+                        if (has_selector) {
+                            try {
+                                w[selector].apply(undefined, callback_args);
+                            } catch (e) {
+                                if (debug) {
+                                    throw e;
+                                }
+                            }
+                        }
+                    }
+                    if (supports_post_message(w) && w !== self) {
+                        w.postMessage(message_arg, "*");
+                    }
+                });
+    };
+
+    WindowTestEnvironment.prototype._forEach_windows = function(callback) {
+        // Iterate of the the windows [self ... top, opener]. The callback is passed
+        // two objects, the first one is the windows object itself, the second one
+        // is a boolean indicating whether or not its on the same origin as the
+        // current window.
+        var cache = this.window_cache;
+        if (!cache) {
+            cache = [[self, true]];
+            var w = self;
+            var i = 0;
+            var so;
+            var origins = location.ancestorOrigins;
+            while (w != w.parent) {
+                w = w.parent;
+                // In WebKit, calls to parent windows' properties that aren't on the same
+                // origin cause an error message to be displayed in the error console but
+                // don't throw an exception. This is a deviation from the current HTML5
+                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
+                // The problem with WebKit's behavior is that it pollutes the error console
+                // with error messages that can't be caught.
+                //
+                // This issue can be mitigated by relying on the (for now) proprietary
+                // `location.ancestorOrigins` property which returns an ordered list of
+                // the origins of enclosing windows. See:
+                // http://trac.webkit.org/changeset/113945.
+                if (origins) {
+                    so = (location.origin == origins[i]);
+                } else {
+                    so = is_same_origin(w);
+                }
+                cache.push([w, so]);
+                i++;
+            }
+            w = window.opener;
+            if (w) {
+                // window.opener isn't included in the `location.ancestorOrigins` prop.
+                // We'll just have to deal with a simple check and an error msg on WebKit
+                // browsers in this case.
+                cache.push([w, is_same_origin(w)]);
+            }
+            this.window_cache = cache;
+        }
+
+        forEach(cache,
+                function(a) {
+                    callback.apply(null, a);
+                });
+    };
+
+    WindowTestEnvironment.prototype.on_tests_ready = function() {
+        var output = new Output();
+        this.output_handler = output;
+
+        var this_obj = this;
+
+        add_start_callback(function (properties) {
+            this_obj.output_handler.init(properties);
+        });
+
+        add_test_state_callback(function(test) {
+            this_obj.output_handler.show_status();
+        });
+
+        add_result_callback(function (test) {
+            this_obj.output_handler.show_status();
+        });
+
+        add_completion_callback(function (tests, harness_status) {
+            this_obj.output_handler.show_results(tests, harness_status);
+        });
+        this.setup_messages(settings.message_events);
+    };
+
+    WindowTestEnvironment.prototype.setup_messages = function(new_events) {
+        var this_obj = this;
+        forEach(settings.message_events, function(x) {
+            var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
+            var new_dispatch = new_events.indexOf(x) !== -1;
+            if (!current_dispatch && new_dispatch) {
+                this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
+            } else if (current_dispatch && !new_dispatch) {
+                this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
+            }
+        });
+        this.message_events = new_events;
+    }
+
+    WindowTestEnvironment.prototype.next_default_test_name = function() {
+        //Don't use document.title to work around an Opera bug in XHTML documents
+        var title = document.getElementsByTagName("title")[0];
+        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return prefix + suffix;
+    };
+
+    WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
+        this.output_handler.setup(properties);
+        if (properties.hasOwnProperty("message_events")) {
+            this.setup_messages(properties.message_events);
+        }
+    };
+
+    WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        on_event(window, 'load', callback);
+    };
+
+    WindowTestEnvironment.prototype.test_timeout = function() {
+        var metas = document.getElementsByTagName("meta");
+        for (var i = 0; i < metas.length; i++) {
+            if (metas[i].name == "timeout") {
+                if (metas[i].content == "long") {
+                    return settings.harness_timeout.long;
+                }
+                break;
+            }
+        }
+        return settings.harness_timeout.normal;
+    };
+
+    WindowTestEnvironment.prototype.global_scope = function() {
+        return window;
+    };
+
+    /*
+     * Base TestEnvironment implementation for a generic web worker.
+     *
+     * Workers accumulate test results. One or more clients can connect and
+     * retrieve results from a worker at any time.
+     *
+     * WorkerTestEnvironment supports communicating with a client via a
+     * MessagePort.  The mechanism for determining the appropriate MessagePort
+     * for communicating with a client depends on the type of worker and is
+     * implemented by the various specializations of WorkerTestEnvironment
+     * below.
+     *
+     * A client document using testharness can use fetch_tests_from_worker() to
+     * retrieve results from a worker. See apisample16.html.
+     */
+    function WorkerTestEnvironment() {
+        this.name_counter = 0;
+        this.all_loaded = true;
+        this.message_list = [];
+        this.message_ports = [];
+    }
+
+    WorkerTestEnvironment.prototype._dispatch = function(message) {
+        this.message_list.push(message);
+        for (var i = 0; i < this.message_ports.length; ++i)
+        {
+            this.message_ports[i].postMessage(message);
+        }
+    };
+
+    // The only requirement is that port has a postMessage() method. It doesn't
+    // have to be an instance of a MessagePort, and often isn't.
+    WorkerTestEnvironment.prototype._add_message_port = function(port) {
+        this.message_ports.push(port);
+        for (var i = 0; i < this.message_list.length; ++i)
+        {
+            port.postMessage(this.message_list[i]);
+        }
+    };
+
+    WorkerTestEnvironment.prototype.next_default_test_name = function() {
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return "Untitled" + suffix;
+    };
+
+    WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+    WorkerTestEnvironment.prototype.on_tests_ready = function() {
+        var this_obj = this;
+        add_start_callback(
+                function(properties) {
+                    this_obj._dispatch({
+                        type: "start",
+                        properties: properties,
+                    });
+                });
+        add_test_state_callback(
+                function(test) {
+                    this_obj._dispatch({
+                        type: "test_state",
+                        test: test.structured_clone()
+                    });
+                });
+        add_result_callback(
+                function(test) {
+                    this_obj._dispatch({
+                        type: "result",
+                        test: test.structured_clone()
+                    });
+                });
+        add_completion_callback(
+                function(tests, harness_status) {
+                    this_obj._dispatch({
+                        type: "complete",
+                        tests: map(tests,
+                            function(test) {
+                                return test.structured_clone();
+                            }),
+                        status: harness_status.structured_clone()
+                    });
+                });
+    };
+
+    WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
+
+    WorkerTestEnvironment.prototype.test_timeout = function() {
+        // Tests running in a worker don't have a default timeout. I.e. all
+        // worker tests behave as if settings.explicit_timeout is true.
+        return null;
+    };
+
+    WorkerTestEnvironment.prototype.global_scope = function() {
+        return self;
+    };
+
+    /*
+     * Dedicated web workers.
+     * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a dedicated worker.
+     */
+    function DedicatedWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        // self is an instance of DedicatedWorkerGlobalScope which exposes
+        // a postMessage() method for communicating via the message channel
+        // established when the worker is created.
+        this._add_message_port(self);
+    }
+    DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+        // In the absence of an onload notification, we a require dedicated
+        // workers to explicitly signal when the tests are done.
+        tests.wait_for_finish = true;
+    };
+
+    /*
+     * Shared web workers.
+     * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a shared web worker.
+     */
+    function SharedWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        var this_obj = this;
+        // Shared workers receive message ports via the 'onconnect' event for
+        // each connection.
+        self.addEventListener("connect",
+                function(message_event) {
+                    this_obj._add_message_port(message_event.source);
+                });
+    }
+    SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+        // In the absence of an onload notification, we a require shared
+        // workers to explicitly signal when the tests are done.
+        tests.wait_for_finish = true;
+    };
+
+    /*
+     * Service workers.
+     * http://www.w3.org/TR/service-workers/
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a service worker.
+     */
+    function ServiceWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        this.all_loaded = false;
+        this.on_loaded_callback = null;
+        var this_obj = this;
+        self.addEventListener("message",
+                function(event) {
+                    if (event.data.type && event.data.type === "connect") {
+                        if (event.ports && event.ports[0]) {
+                            // If a MessageChannel was passed, then use it to
+                            // send results back to the main window.  This
+                            // allows the tests to work even if the browser
+                            // does not fully support MessageEvent.source in
+                            // ServiceWorkers yet.
+                            this_obj._add_message_port(event.ports[0]);
+                            event.ports[0].start();
+                        } else {
+                            // If there is no MessageChannel, then attempt to
+                            // use the MessageEvent.source to send results
+                            // back to the main window.
+                            this_obj._add_message_port(event.source);
+                        }
+                    }
+                });
+
+        // The oninstall event is received after the service worker script and
+        // all imported scripts have been fetched and executed. It's the
+        // equivalent of an onload event for a document. All tests should have
+        // been added by the time this event is received, thus it's not
+        // necessary to wait until the onactivate event.
+        on_event(self, "install",
+                function(event) {
+                    this_obj.all_loaded = true;
+                    if (this_obj.on_loaded_callback) {
+                        this_obj.on_loaded_callback();
+                    }
+                });
+    }
+    ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        if (this.all_loaded) {
+            callback();
+        } else {
+            this.on_loaded_callback = callback;
+        }
+    };
+
+    function create_test_environment() {
+        if ('document' in self) {
+            return new WindowTestEnvironment();
+        }
+        if ('DedicatedWorkerGlobalScope' in self &&
+            self instanceof DedicatedWorkerGlobalScope) {
+            return new DedicatedWorkerTestEnvironment();
+        }
+        if ('SharedWorkerGlobalScope' in self &&
+            self instanceof SharedWorkerGlobalScope) {
+            return new SharedWorkerTestEnvironment();
+        }
+        if ('ServiceWorkerGlobalScope' in self &&
+            self instanceof ServiceWorkerGlobalScope) {
+            return new ServiceWorkerTestEnvironment();
+        }
+        throw new Error("Unsupported test environment");
+    }
+
+    var test_environment = create_test_environment();
+
+    function is_shared_worker(worker) {
+        return 'SharedWorker' in self && worker instanceof SharedWorker;
+    }
+
+    function is_service_worker(worker) {
+        return 'ServiceWorker' in self && worker instanceof ServiceWorker;
+    }
+
+    /*
+     * API functions
+     */
+
+    function test(func, name, properties)
+    {
+        var test_name = name ? name : test_environment.next_default_test_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        test_obj.step(func, test_obj, test_obj);
+        if (test_obj.phase === test_obj.phases.STARTED) {
+            test_obj.done();
+        }
+    }
+
+    function async_test(func, name, properties)
+    {
+        if (typeof func !== "function") {
+            properties = name;
+            name = func;
+            func = null;
+        }
+        var test_name = name ? name : test_environment.next_default_test_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        if (func) {
+            test_obj.step(func, test_obj, test_obj);
+        }
+        return test_obj;
+    }
+
+    function promise_test(func, name, properties) {
+        var test = async_test(name, properties);
+        // If there is no promise tests queue make one.
+        test.step(function() {
+            if (!tests.promise_tests) {
+                tests.promise_tests = Promise.resolve();
+            }
+        });
+        tests.promise_tests = tests.promise_tests.then(function() {
+            return Promise.resolve(test.step(func, test, test))
+                .then(
+                    function() {
+                        test.done();
+                    })
+                .catch(test.step_func(
+                    function(value) {
+                        if (value instanceof AssertionError) {
+                            throw value;
+                        }
+                        assert(false, "promise_test", null,
+                               "Unhandled rejection with value: ${value}", {value:value});
+                    }));
+        });
+    }
+
+    function promise_rejects(test, expected, promise) {
+        return promise.then(test.unreached_func("Should have rejected.")).catch(function(e) {
+            assert_throws(expected, function() { throw e });
+        });
+    }
+
+    /**
+     * This constructor helper allows DOM events to be handled using Promises,
+     * which can make it a lot easier to test a very specific series of events,
+     * including ensuring that unexpected events are not fired at any point.
+     */
+    function EventWatcher(test, watchedNode, eventTypes)
+    {
+        if (typeof eventTypes == 'string') {
+            eventTypes = [eventTypes];
+        }
+
+        var waitingFor = null;
+
+        var eventHandler = test.step_func(function(evt) {
+            assert_true(!!waitingFor,
+                        'Not expecting event, but got ' + evt.type + ' event');
+            assert_equals(evt.type, waitingFor.types[0],
+                          'Expected ' + waitingFor.types[0] + ' event, but got ' +
+                          evt.type + ' event instead');
+            if (waitingFor.types.length > 1) {
+                // Pop first event from array
+                waitingFor.types.shift();
+                return;
+            }
+            // We need to null out waitingFor before calling the resolve function
+            // since the Promise's resolve handlers may call wait_for() which will
+            // need to set waitingFor.
+            var resolveFunc = waitingFor.resolve;
+            waitingFor = null;
+            resolveFunc(evt);
+        });
+
+        for (var i = 0; i < eventTypes.length; i++) {
+            watchedNode.addEventListener(eventTypes[i], eventHandler);
+        }
+
+        /**
+         * Returns a Promise that will resolve after the specified event or
+         * series of events has occured.
+         */
+        this.wait_for = function(types) {
+            if (waitingFor) {
+                return Promise.reject('Already waiting for an event or events');
+            }
+            if (typeof types == 'string') {
+                types = [types];
+            }
+            return new Promise(function(resolve, reject) {
+                waitingFor = {
+                    types: types,
+                    resolve: resolve,
+                    reject: reject
+                };
+            });
+        };
+
+        function stop_watching() {
+            for (var i = 0; i < eventTypes.length; i++) {
+                watchedNode.removeEventListener(eventTypes[i], eventHandler);
+            }
+        };
+
+        test.add_cleanup(stop_watching);
+
+        return this;
+    }
+    expose(EventWatcher, 'EventWatcher');
+
+    function setup(func_or_properties, maybe_properties)
+    {
+        var func = null;
+        var properties = {};
+        if (arguments.length === 2) {
+            func = func_or_properties;
+            properties = maybe_properties;
+        } else if (func_or_properties instanceof Function) {
+            func = func_or_properties;
+        } else {
+            properties = func_or_properties;
+        }
+        tests.setup(func, properties);
+        test_environment.on_new_harness_properties(properties);
+    }
+
+    function done() {
+        if (tests.tests.length === 0) {
+            tests.set_file_is_test();
+        }
+        if (tests.file_is_test) {
+            tests.tests[0].done();
+        }
+        tests.end_wait();
+    }
+
+    function generate_tests(func, args, properties) {
+        forEach(args, function(x, i)
+                {
+                    var name = x[0];
+                    test(function()
+                         {
+                             func.apply(this, x.slice(1));
+                         },
+                         name,
+                         Array.isArray(properties) ? properties[i] : properties);
+                });
+    }
+
+    function on_event(object, event, callback)
+    {
+        object.addEventListener(event, callback, false);
+    }
+
+    function step_timeout(f, t) {
+        var outer_this = this;
+        var args = Array.prototype.slice.call(arguments, 2);
+        return setTimeout(function() {
+            f.apply(outer_this, args);
+        }, t * tests.timeout_multiplier);
+    }
+
+    expose(test, 'test');
+    expose(async_test, 'async_test');
+    expose(promise_test, 'promise_test');
+    expose(promise_rejects, 'promise_rejects');
+    expose(generate_tests, 'generate_tests');
+    expose(setup, 'setup');
+    expose(done, 'done');
+    expose(on_event, 'on_event');
+    expose(step_timeout, 'step_timeout');
+
+    /*
+     * Return a string truncated to the given length, with ... added at the end
+     * if it was longer.
+     */
+    function truncate(s, len)
+    {
+        if (s.length > len) {
+            return s.substring(0, len - 3) + "...";
+        }
+        return s;
+    }
+
+    /*
+     * Return true if object is probably a Node object.
+     */
+    function is_node(object)
+    {
+        // I use duck-typing instead of instanceof, because
+        // instanceof doesn't work if the node is from another window (like an
+        // iframe's contentWindow):
+        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
+        if ("nodeType" in object &&
+            "nodeName" in object &&
+            "nodeValue" in object &&
+            "childNodes" in object) {
+            try {
+                object.nodeType;
+            } catch (e) {
+                // The object is probably Node.prototype or another prototype
+                // object that inherits from it, and not a Node instance.
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Convert a value to a nice, human-readable string
+     */
+    function format_value(val, seen)
+    {
+        if (!seen) {
+            seen = [];
+        }
+        if (typeof val === "object" && val !== null) {
+            if (seen.indexOf(val) >= 0) {
+                return "[...]";
+            }
+            seen.push(val);
+        }
+        if (Array.isArray(val)) {
+            return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
+        }
+
+        switch (typeof val) {
+        case "string":
+            val = val.replace("\\", "\\\\");
+            for (var i = 0; i < 32; i++) {
+                var replace = "\\";
+                switch (i) {
+                case 0: replace += "0"; break;
+                case 1: replace += "x01"; break;
+                case 2: replace += "x02"; break;
+                case 3: replace += "x03"; break;
+                case 4: replace += "x04"; break;
+                case 5: replace += "x05"; break;
+                case 6: replace += "x06"; break;
+                case 7: replace += "x07"; break;
+                case 8: replace += "b"; break;
+                case 9: replace += "t"; break;
+                case 10: replace += "n"; break;
+                case 11: replace += "v"; break;
+                case 12: replace += "f"; break;
+                case 13: replace += "r"; break;
+                case 14: replace += "x0e"; break;
+                case 15: replace += "x0f"; break;
+                case 16: replace += "x10"; break;
+                case 17: replace += "x11"; break;
+                case 18: replace += "x12"; break;
+                case 19: replace += "x13"; break;
+                case 20: replace += "x14"; break;
+                case 21: replace += "x15"; break;
+                case 22: replace += "x16"; break;
+                case 23: replace += "x17"; break;
+                case 24: replace += "x18"; break;
+                case 25: replace += "x19"; break;
+                case 26: replace += "x1a"; break;
+                case 27: replace += "x1b"; break;
+                case 28: replace += "x1c"; break;
+                case 29: replace += "x1d"; break;
+                case 30: replace += "x1e"; break;
+                case 31: replace += "x1f"; break;
+                }
+                val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
+            }
+            return '"' + val.replace(/"/g, '\\"') + '"';
+        case "boolean":
+        case "undefined":
+            return String(val);
+        case "number":
+            // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
+            // special-case.
+            if (val === -0 && 1/val === -Infinity) {
+                return "-0";
+            }
+            return String(val);
+        case "object":
+            if (val === null) {
+                return "null";
+            }
+
+            // Special-case Node objects, since those come up a lot in my tests.  I
+            // ignore namespaces.
+            if (is_node(val)) {
+                switch (val.nodeType) {
+                case Node.ELEMENT_NODE:
+                    var ret = "<" + val.localName;
+                    for (var i = 0; i < val.attributes.length; i++) {
+                        ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
+                    }
+                    ret += ">" + val.innerHTML + "</" + val.localName + ">";
+                    return "Element node " + truncate(ret, 60);
+                case Node.TEXT_NODE:
+                    return 'Text node "' + truncate(val.data, 60) + '"';
+                case Node.PROCESSING_INSTRUCTION_NODE:
+                    return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
+                case Node.COMMENT_NODE:
+                    return "Comment node <!--" + truncate(val.data, 60) + "-->";
+                case Node.DOCUMENT_NODE:
+                    return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+                case Node.DOCUMENT_TYPE_NODE:
+                    return "DocumentType node";
+                case Node.DOCUMENT_FRAGMENT_NODE:
+                    return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+                default:
+                    return "Node object of unknown type";
+                }
+            }
+
+        /* falls through */
+        default:
+            return typeof val + ' "' + truncate(String(val), 60) + '"';
+        }
+    }
+    expose(format_value, "format_value");
+
+    /*
+     * Assertions
+     */
+
+    function assert_true(actual, description)
+    {
+        assert(actual === true, "assert_true", description,
+                                "expected true got ${actual}", {actual:actual});
+    }
+    expose(assert_true, "assert_true");
+
+    function assert_false(actual, description)
+    {
+        assert(actual === false, "assert_false", description,
+                                 "expected false got ${actual}", {actual:actual});
+    }
+    expose(assert_false, "assert_false");
+
+    function same_value(x, y) {
+        if (y !== y) {
+            //NaN case
+            return x !== x;
+        }
+        if (x === 0 && y === 0) {
+            //Distinguish +0 and -0
+            return 1/x === 1/y;
+        }
+        return x === y;
+    }
+
+    function assert_equals(actual, expected, description)
+    {
+         /*
+          * Test if two primitives are equal or two objects
+          * are the same object
+          */
+        if (typeof actual != typeof expected) {
+            assert(false, "assert_equals", description,
+                          "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
+                          {expected:expected, actual:actual});
+            return;
+        }
+        assert(same_value(actual, expected), "assert_equals", description,
+                                             "expected ${expected} but got ${actual}",
+                                             {expected:expected, actual:actual});
+    }
+    expose(assert_equals, "assert_equals");
+
+    function assert_not_equals(actual, expected, description)
+    {
+         /*
+          * Test if two primitives are unequal or two objects
+          * are different objects
+          */
+        assert(!same_value(actual, expected), "assert_not_equals", description,
+                                              "got disallowed value ${actual}",
+                                              {actual:actual});
+    }
+    expose(assert_not_equals, "assert_not_equals");
+
+    function assert_in_array(actual, expected, description)
+    {
+        assert(expected.indexOf(actual) != -1, "assert_in_array", description,
+                                               "value ${actual} not in array ${expected}",
+                                               {actual:actual, expected:expected});
+    }
+    expose(assert_in_array, "assert_in_array");
+
+    function assert_object_equals(actual, expected, description)
+    {
+         //This needs to be improved a great deal
+         function check_equal(actual, expected, stack)
+         {
+             stack.push(actual);
+
+             var p;
+             for (p in actual) {
+                 assert(expected.hasOwnProperty(p), "assert_object_equals", description,
+                                                    "unexpected property ${p}", {p:p});
+
+                 if (typeof actual[p] === "object" && actual[p] !== null) {
+                     if (stack.indexOf(actual[p]) === -1) {
+                         check_equal(actual[p], expected[p], stack);
+                     }
+                 } else {
+                     assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
+                                                       "property ${p} expected ${expected} got ${actual}",
+                                                       {p:p, expected:expected, actual:actual});
+                 }
+             }
+             for (p in expected) {
+                 assert(actual.hasOwnProperty(p),
+                        "assert_object_equals", description,
+                        "expected property ${p} missing", {p:p});
+             }
+             stack.pop();
+         }
+         check_equal(actual, expected, []);
+    }
+    expose(assert_object_equals, "assert_object_equals");
+
+    function assert_array_equals(actual, expected, description)
+    {
+        assert(actual.length === expected.length,
+               "assert_array_equals", description,
+               "lengths differ, expected ${expected} got ${actual}",
+               {expected:expected.length, actual:actual.length});
+
+        for (var i = 0; i < actual.length; i++) {
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+                   "assert_array_equals", description,
+                   "property ${i}, property expected to be ${expected} but was ${actual}",
+                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+                   actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+            assert(same_value(expected[i], actual[i]),
+                   "assert_array_equals", description,
+                   "property ${i}, expected ${expected} but got ${actual}",
+                   {i:i, expected:expected[i], actual:actual[i]});
+        }
+    }
+    expose(assert_array_equals, "assert_array_equals");
+
+    function assert_approx_equals(actual, expected, epsilon, description)
+    {
+        /*
+         * Test if two primitive numbers are equal withing +/- epsilon
+         */
+        assert(typeof actual === "number",
+               "assert_approx_equals", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(Math.abs(actual - expected) <= epsilon,
+               "assert_approx_equals", description,
+               "expected ${expected} +/- ${epsilon} but got ${actual}",
+               {expected:expected, actual:actual, epsilon:epsilon});
+    }
+    expose(assert_approx_equals, "assert_approx_equals");
+
+    function assert_less_than(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is less than another
+         */
+        assert(typeof actual === "number",
+               "assert_less_than", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual < expected,
+               "assert_less_than", description,
+               "expected a number less than ${expected} but got ${actual}",
+               {expected:expected, actual:actual});
+    }
+    expose(assert_less_than, "assert_less_than");
+
+    function assert_greater_than(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is greater than another
+         */
+        assert(typeof actual === "number",
+               "assert_greater_than", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual > expected,
+               "assert_greater_than", description,
+               "expected a number greater than ${expected} but got ${actual}",
+               {expected:expected, actual:actual});
+    }
+    expose(assert_greater_than, "assert_greater_than");
+
+    function assert_between_exclusive(actual, lower, upper, description)
+    {
+        /*
+         * Test if a primitive number is between two others
+         */
+        assert(typeof actual === "number",
+               "assert_between_exclusive", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual > lower && actual < upper,
+               "assert_between_exclusive", description,
+               "expected a number greater than ${lower} " +
+               "and less than ${upper} but got ${actual}",
+               {lower:lower, upper:upper, actual:actual});
+    }
+    expose(assert_between_exclusive, "assert_between_exclusive");
+
+    function assert_less_than_equal(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is less than or equal to another
+         */
+        assert(typeof actual === "number",
+               "assert_less_than_equal", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual <= expected,
+               "assert_less_than_equal", description,
+               "expected a number less than or equal to ${expected} but got ${actual}",
+               {expected:expected, actual:actual});
+    }
+    expose(assert_less_than_equal, "assert_less_than_equal");
+
+    function assert_greater_than_equal(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is greater than or equal to another
+         */
+        assert(typeof actual === "number",
+               "assert_greater_than_equal", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual >= expected,
+               "assert_greater_than_equal", description,
+               "expected a number greater than or equal to ${expected} but got ${actual}",
+               {expected:expected, actual:actual});
+    }
+    expose(assert_greater_than_equal, "assert_greater_than_equal");
+
+    function assert_between_inclusive(actual, lower, upper, description)
+    {
+        /*
+         * Test if a primitive number is between to two others or equal to either of them
+         */
+        assert(typeof actual === "number",
+               "assert_between_inclusive", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual >= lower && actual <= upper,
+               "assert_between_inclusive", description,
+               "expected a number greater than or equal to ${lower} " +
+               "and less than or equal to ${upper} but got ${actual}",
+               {lower:lower, upper:upper, actual:actual});
+    }
+    expose(assert_between_inclusive, "assert_between_inclusive");
+
+    function assert_regexp_match(actual, expected, description) {
+        /*
+         * Test if a string (actual) matches a regexp (expected)
+         */
+        assert(expected.test(actual),
+               "assert_regexp_match", description,
+               "expected ${expected} but got ${actual}",
+               {expected:expected, actual:actual});
+    }
+    expose(assert_regexp_match, "assert_regexp_match");
+
+    function assert_class_string(object, class_string, description) {
+        assert_equals({}.toString.call(object), "[object " + class_string + "]",
+                      description);
+    }
+    expose(assert_class_string, "assert_class_string");
+
+
+    function _assert_own_property(name) {
+        return function(object, property_name, description)
+        {
+            assert(object.hasOwnProperty(property_name),
+                   name, description,
+                   "expected property ${p} missing", {p:property_name});
+        };
+    }
+    expose(_assert_own_property("assert_exists"), "assert_exists");
+    expose(_assert_own_property("assert_own_property"), "assert_own_property");
+
+    function assert_not_exists(object, property_name, description)
+    {
+        assert(!object.hasOwnProperty(property_name),
+               "assert_not_exists", description,
+               "unexpected property ${p} found", {p:property_name});
+    }
+    expose(assert_not_exists, "assert_not_exists");
+
+    function _assert_inherits(name) {
+        return function (object, property_name, description)
+        {
+            assert(typeof object === "object",
+                   name, description,
+                   "provided value is not an object");
+
+            assert("hasOwnProperty" in object,
+                   name, description,
+                   "provided value is an object but has no hasOwnProperty method");
+
+            assert(!object.hasOwnProperty(property_name),
+                   name, description,
+                   "property ${p} found on object expected in prototype chain",
+                   {p:property_name});
+
+            assert(property_name in object,
+                   name, description,
+                   "property ${p} not found in prototype chain",
+                   {p:property_name});
+        };
+    }
+    expose(_assert_inherits("assert_inherits"), "assert_inherits");
+    expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
+
+    function assert_readonly(object, property_name, description)
+    {
+         var initial_value = object[property_name];
+         try {
+             //Note that this can have side effects in the case where
+             //the property has PutForwards
+             object[property_name] = initial_value + "a"; //XXX use some other value here?
+             assert(same_value(object[property_name], initial_value),
+                    "assert_readonly", description,
+                    "changing property ${p} succeeded",
+                    {p:property_name});
+         } finally {
+             object[property_name] = initial_value;
+         }
+    }
+    expose(assert_readonly, "assert_readonly");
+
+    function assert_throws(code, func, description)
+    {
+        try {
+            func.call(this);
+            assert(false, "assert_throws", description,
+                   "${func} did not throw", {func:func});
+        } catch (e) {
+            if (e instanceof AssertionError) {
+                throw e;
+            }
+            if (code === null) {
+                return;
+            }
+            if (typeof code === "object") {
+                assert(typeof e == "object" && "name" in e && e.name == code.name,
+                       "assert_throws", description,
+                       "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
+                                    {func:func, actual:e, actual_name:e.name,
+                                     expected:code,
+                                     expected_name:code.name});
+                return;
+            }
+
+            var code_name_map = {
+                INDEX_SIZE_ERR: 'IndexSizeError',
+                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
+                WRONG_DOCUMENT_ERR: 'WrongDocumentError',
+                INVALID_CHARACTER_ERR: 'InvalidCharacterError',
+                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
+                NOT_FOUND_ERR: 'NotFoundError',
+                NOT_SUPPORTED_ERR: 'NotSupportedError',
+                INVALID_STATE_ERR: 'InvalidStateError',
+                SYNTAX_ERR: 'SyntaxError',
+                INVALID_MODIFICATION_ERR: 'InvalidModificationError',
+                NAMESPACE_ERR: 'NamespaceError',
+                INVALID_ACCESS_ERR: 'InvalidAccessError',
+                TYPE_MISMATCH_ERR: 'TypeMismatchError',
+                SECURITY_ERR: 'SecurityError',
+                NETWORK_ERR: 'NetworkError',
+                ABORT_ERR: 'AbortError',
+                URL_MISMATCH_ERR: 'URLMismatchError',
+                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
+                TIMEOUT_ERR: 'TimeoutError',
+                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
+                DATA_CLONE_ERR: 'DataCloneError'
+            };
+
+            var name = code in code_name_map ? code_name_map[code] : code;
+
+            var name_code_map = {
+                IndexSizeError: 1,
+                HierarchyRequestError: 3,
+                WrongDocumentError: 4,
+                InvalidCharacterError: 5,
+                NoModificationAllowedError: 7,
+                NotFoundError: 8,
+                NotSupportedError: 9,
+                InvalidStateError: 11,
+                SyntaxError: 12,
+                InvalidModificationError: 13,
+                NamespaceError: 14,
+                InvalidAccessError: 15,
+                TypeMismatchError: 17,
+                SecurityError: 18,
+                NetworkError: 19,
+                AbortError: 20,
+                URLMismatchError: 21,
+                QuotaExceededError: 22,
+                TimeoutError: 23,
+                InvalidNodeTypeError: 24,
+                DataCloneError: 25,
+
+                EncodingError: 0,
+                NotReadableError: 0,
+                UnknownError: 0,
+                ConstraintError: 0,
+                DataError: 0,
+                TransactionInactiveError: 0,
+                ReadOnlyError: 0,
+                VersionError: 0,
+                OperationError: 0,
+            };
+
+            if (!(name in name_code_map)) {
+                throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
+            }
+
+            var required_props = { code: name_code_map[name] };
+
+            if (required_props.code === 0 ||
+               (typeof e == "object" &&
+                "name" in e &&
+                e.name !== e.name.toUpperCase() &&
+                e.name !== "DOMException")) {
+                // New style exception: also test the name property.
+                required_props.name = name;
+            }
+
+            //We'd like to test that e instanceof the appropriate interface,
+            //but we can't, because we don't know what window it was created
+            //in.  It might be an instanceof the appropriate interface on some
+            //unknown other window.  TODO: Work around this somehow?
+
+            assert(typeof e == "object",
+                   "assert_throws", description,
+                   "${func} threw ${e} with type ${type}, not an object",
+                   {func:func, e:e, type:typeof e});
+
+            for (var prop in required_props) {
+                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
+                       "assert_throws", description,
+                       "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
+                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
+            }
+        }
+    }
+    expose(assert_throws, "assert_throws");
+
+    function assert_unreached(description) {
+         assert(false, "assert_unreached", description,
+                "Reached unreachable code");
+    }
+    expose(assert_unreached, "assert_unreached");
+
+    function assert_any(assert_func, actual, expected_array)
+    {
+        var args = [].slice.call(arguments, 3);
+        var errors = [];
+        var passed = false;
+        forEach(expected_array,
+                function(expected)
+                {
+                    try {
+                        assert_func.apply(this, [actual, expected].concat(args));
+                        passed = true;
+                    } catch (e) {
+                        errors.push(e.message);
+                    }
+                });
+        if (!passed) {
+            throw new AssertionError(errors.join("\n\n"));
+        }
+    }
+    expose(assert_any, "assert_any");
+
+    function Test(name, properties)
+    {
+        if (tests.file_is_test && tests.tests.length) {
+            throw new Error("Tried to create a test with file_is_test");
+        }
+        this.name = name;
+
+        this.phase = this.phases.INITIAL;
+
+        this.status = this.NOTRUN;
+        this.timeout_id = null;
+        this.index = null;
+
+        this.properties = properties;
+        var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
+        if (timeout !== null) {
+            this.timeout_length = timeout * tests.timeout_multiplier;
+        } else {
+            this.timeout_length = null;
+        }
+
+        this.message = null;
+        this.stack = null;
+
+        this.steps = [];
+
+        this.cleanup_callbacks = [];
+
+        tests.push(this);
+    }
+
+    Test.statuses = {
+        PASS:0,
+        FAIL:1,
+        TIMEOUT:2,
+        NOTRUN:3
+    };
+
+    Test.prototype = merge({}, Test.statuses);
+
+    Test.prototype.phases = {
+        INITIAL:0,
+        STARTED:1,
+        HAS_RESULT:2,
+        COMPLETE:3
+    };
+
+    Test.prototype.structured_clone = function()
+    {
+        if (!this._structured_clone) {
+            var msg = this.message;
+            msg = msg ? String(msg) : msg;
+            this._structured_clone = merge({
+                name:String(this.name),
+                properties:merge({}, this.properties),
+            }, Test.statuses);
+        }
+        this._structured_clone.status = this.status;
+        this._structured_clone.message = this.message;
+        this._structured_clone.stack = this.stack;
+        this._structured_clone.index = this.index;
+        return this._structured_clone;
+    };
+
+    Test.prototype.step = function(func, this_obj)
+    {
+        if (this.phase > this.phases.STARTED) {
+            return;
+        }
+        this.phase = this.phases.STARTED;
+        //If we don't get a result before the harness times out that will be a test timout
+        this.set_status(this.TIMEOUT, "Test timed out");
+
+        tests.started = true;
+        tests.notify_test_state(this);
+
+        if (this.timeout_id === null) {
+            this.set_timeout();
+        }
+
+        this.steps.push(func);
+
+        if (arguments.length === 1) {
+            this_obj = this;
+        }
+
+        try {
+            return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
+        } catch (e) {
+            if (this.phase >= this.phases.HAS_RESULT) {
+                return;
+            }
+            var message = String((typeof e === "object" && e !== null) ? e.message : e);
+            var stack = e.stack ? e.stack : null;
+
+            this.set_status(this.FAIL, message, stack);
+            this.phase = this.phases.HAS_RESULT;
+            this.done();
+        }
+    };
+
+    Test.prototype.step_func = function(func, this_obj)
+    {
+        var test_this = this;
+
+        if (arguments.length === 1) {
+            this_obj = test_this;
+        }
+
+        return function()
+        {
+            return test_this.step.apply(test_this, [func, this_obj].concat(
+                Array.prototype.slice.call(arguments)));
+        };
+    };
+
+    Test.prototype.step_func_done = function(func, this_obj)
+    {
+        var test_this = this;
+
+        if (arguments.length === 1) {
+            this_obj = test_this;
+        }
+
+        return function()
+        {
+            if (func) {
+                test_this.step.apply(test_this, [func, this_obj].concat(
+                    Array.prototype.slice.call(arguments)));
+            }
+            test_this.done();
+        };
+    };
+
+    Test.prototype.unreached_func = function(description)
+    {
+        return this.step_func(function() {
+            assert_unreached(description);
+        });
+    };
+
+    Test.prototype.step_timeout = function(f, timeout) {
+        var test_this = this;
+        var args = Array.prototype.slice.call(arguments, 2);
+        return setTimeout(this.step_func(function() {
+            return f.apply(test_this, args);
+        }, timeout * tests.timeout_multiplier));
+    }
+
+    Test.prototype.add_cleanup = function(callback) {
+        this.cleanup_callbacks.push(callback);
+    };
+
+    Test.prototype.force_timeout = function() {
+        this.set_status(this.TIMEOUT);
+        this.phase = this.phases.HAS_RESULT;
+    };
+
+    Test.prototype.set_timeout = function()
+    {
+        if (this.timeout_length !== null) {
+            var this_obj = this;
+            this.timeout_id = setTimeout(function()
+                                         {
+                                             this_obj.timeout();
+                                         }, this.timeout_length);
+        }
+    };
+
+    Test.prototype.set_status = function(status, message, stack)
+    {
+        this.status = status;
+        this.message = message;
+        this.stack = stack ? stack : null;
+    };
+
+    Test.prototype.timeout = function()
+    {
+        this.timeout_id = null;
+        this.set_status(this.TIMEOUT, "Test timed out");
+        this.phase = this.phases.HAS_RESULT;
+        this.done();
+    };
+
+    Test.prototype.done = function()
+    {
+        if (this.phase == this.phases.COMPLETE) {
+            return;
+        }
+
+        if (this.phase <= this.phases.STARTED) {
+            this.set_status(this.PASS, null);
+        }
+
+        this.phase = this.phases.COMPLETE;
+
+        clearTimeout(this.timeout_id);
+        tests.result(this);
+        this.cleanup();
+    };
+
+    Test.prototype.cleanup = function() {
+        forEach(this.cleanup_callbacks,
+                function(cleanup_callback) {
+                    cleanup_callback();
+                });
+    };
+
+    /*
+     * A RemoteTest object mirrors a Test object on a remote worker. The
+     * associated RemoteWorker updates the RemoteTest object in response to
+     * received events. In turn, the RemoteTest object replicates these events
+     * on the local document. This allows listeners (test result reporting
+     * etc..) to transparently handle local and remote events.
+     */
+    function RemoteTest(clone) {
+        var this_obj = this;
+        Object.keys(clone).forEach(
+                function(key) {
+                    this_obj[key] = clone[key];
+                });
+        this.index = null;
+        this.phase = this.phases.INITIAL;
+        this.update_state_from(clone);
+        tests.push(this);
+    }
+
+    RemoteTest.prototype.structured_clone = function() {
+        var clone = {};
+        Object.keys(this).forEach(
+                (function(key) {
+                    if (typeof(this[key]) === "object") {
+                        clone[key] = merge({}, this[key]);
+                    } else {
+                        clone[key] = this[key];
+                    }
+                }).bind(this));
+        clone.phases = merge({}, this.phases);
+        return clone;
+    };
+
+    RemoteTest.prototype.cleanup = function() {};
+    RemoteTest.prototype.phases = Test.prototype.phases;
+    RemoteTest.prototype.update_state_from = function(clone) {
+        this.status = clone.status;
+        this.message = clone.message;
+        this.stack = clone.stack;
+        if (this.phase === this.phases.INITIAL) {
+            this.phase = this.phases.STARTED;
+        }
+    };
+    RemoteTest.prototype.done = function() {
+        this.phase = this.phases.COMPLETE;
+    }
+
+    /*
+     * A RemoteWorker listens for test events from a worker. These events are
+     * then used to construct and maintain RemoteTest objects that mirror the
+     * tests running on the remote worker.
+     */
+    function RemoteWorker(worker) {
+        this.running = true;
+        this.tests = new Array();
+
+        var this_obj = this;
+        worker.onerror = function(error) { this_obj.worker_error(error); };
+
+        var message_port;
+
+        if (is_service_worker(worker)) {
+            if (window.MessageChannel) {
+                // The ServiceWorker's implicit MessagePort is currently not
+                // reliably accessible from the ServiceWorkerGlobalScope due to
+                // Blink setting MessageEvent.source to null for messages sent
+                // via ServiceWorker.postMessage(). Until that's resolved,
+                // create an explicit MessageChannel and pass one end to the
+                // worker.
+                var message_channel = new MessageChannel();
+                message_port = message_channel.port1;
+                message_port.start();
+                worker.postMessage({type: "connect"}, [message_channel.port2]);
+            } else {
+                // If MessageChannel is not available, then try the
+                // ServiceWorker.postMessage() approach using MessageEvent.source
+                // on the other end.
+                message_port = navigator.serviceWorker;
+                worker.postMessage({type: "connect"});
+            }
+        } else if (is_shared_worker(worker)) {
+            message_port = worker.port;
+        } else {
+            message_port = worker;
+        }
+
+        // Keeping a reference to the worker until worker_done() is seen
+        // prevents the Worker object and its MessageChannel from going away
+        // before all the messages are dispatched.
+        this.worker = worker;
+
+        message_port.onmessage =
+            function(message) {
+                if (this_obj.running && (message.data.type in this_obj.message_handlers)) {
+                    this_obj.message_handlers[message.data.type].call(this_obj, message.data);
+                }
+            };
+    }
+
+    RemoteWorker.prototype.worker_error = function(error) {
+        var message = error.message || String(error);
+        var filename = (error.filename ? " " + error.filename: "");
+        // FIXME: Display worker error states separately from main document
+        // error state.
+        this.worker_done({
+            status: {
+                status: tests.status.ERROR,
+                message: "Error in worker" + filename + ": " + message,
+                stack: error.stack
+            }
+        });
+        error.preventDefault();
+    };
+
+    RemoteWorker.prototype.test_state = function(data) {
+        var remote_test = this.tests[data.test.index];
+        if (!remote_test) {
+            remote_test = new RemoteTest(data.test);
+            this.tests[data.test.index] = remote_test;
+        }
+        remote_test.update_state_from(data.test);
+        tests.notify_test_state(remote_test);
+    };
+
+    RemoteWorker.prototype.test_done = function(data) {
+        var remote_test = this.tests[data.test.index];
+        remote_test.update_state_from(data.test);
+        remote_test.done();
+        tests.result(remote_test);
+    };
+
+    RemoteWorker.prototype.worker_done = function(data) {
+        if (tests.status.status === null &&
+            data.status.status !== data.status.OK) {
+            tests.status.status = data.status.status;
+            tests.status.message = data.status.message;
+            tests.status.stack = data.status.stack;
+        }
+        this.running = false;
+        this.worker = null;
+        if (tests.all_done()) {
+            tests.complete();
+        }
+    };
+
+    RemoteWorker.prototype.message_handlers = {
+        test_state: RemoteWorker.prototype.test_state,
+        result: RemoteWorker.prototype.test_done,
+        complete: RemoteWorker.prototype.worker_done
+    };
+
+    /*
+     * Harness
+     */
+
+    function TestsStatus()
+    {
+        this.status = null;
+        this.message = null;
+        this.stack = null;
+    }
+
+    TestsStatus.statuses = {
+        OK:0,
+        ERROR:1,
+        TIMEOUT:2
+    };
+
+    TestsStatus.prototype = merge({}, TestsStatus.statuses);
+
+    TestsStatus.prototype.structured_clone = function()
+    {
+        if (!this._structured_clone) {
+            var msg = this.message;
+            msg = msg ? String(msg) : msg;
+            this._structured_clone = merge({
+                status:this.status,
+                message:msg,
+                stack:this.stack
+            }, TestsStatus.statuses);
+        }
+        return this._structured_clone;
+    };
+
+    function Tests()
+    {
+        this.tests = [];
+        this.num_pending = 0;
+
+        this.phases = {
+            INITIAL:0,
+            SETUP:1,
+            HAVE_TESTS:2,
+            HAVE_RESULTS:3,
+            COMPLETE:4
+        };
+        this.phase = this.phases.INITIAL;
+
+        this.properties = {};
+
+        this.wait_for_finish = false;
+        this.processing_callbacks = false;
+
+        this.allow_uncaught_exception = false;
+
+        this.file_is_test = false;
+
+        this.timeout_multiplier = 1;
+        this.timeout_length = test_environment.test_timeout();
+        this.timeout_id = null;
+
+        this.start_callbacks = [];
+        this.test_state_callbacks = [];
+        this.test_done_callbacks = [];
+        this.all_done_callbacks = [];
+
+        this.pending_workers = [];
+
+        this.status = new TestsStatus();
+
+        var this_obj = this;
+
+        test_environment.add_on_loaded_callback(function() {
+            if (this_obj.all_done()) {
+                this_obj.complete();
+            }
+        });
+
+        this.set_timeout();
+    }
+
+    Tests.prototype.setup = function(func, properties)
+    {
+        if (this.phase >= this.phases.HAVE_RESULTS) {
+            return;
+        }
+
+        if (this.phase < this.phases.SETUP) {
+            this.phase = this.phases.SETUP;
+        }
+
+        this.properties = properties;
+
+        for (var p in properties) {
+            if (properties.hasOwnProperty(p)) {
+                var value = properties[p];
+                if (p == "allow_uncaught_exception") {
+                    this.allow_uncaught_exception = value;
+                } else if (p == "explicit_done" && value) {
+                    this.wait_for_finish = true;
+                } else if (p == "explicit_timeout" && value) {
+                    this.timeout_length = null;
+                    if (this.timeout_id)
+                    {
+                        clearTimeout(this.timeout_id);
+                    }
+                } else if (p == "timeout_multiplier") {
+                    this.timeout_multiplier = value;
+                }
+            }
+        }
+
+        if (func) {
+            try {
+                func();
+            } catch (e) {
+                this.status.status = this.status.ERROR;
+                this.status.message = String(e);
+                this.status.stack = e.stack ? e.stack : null;
+            }
+        }
+        this.set_timeout();
+    };
+
+    Tests.prototype.set_file_is_test = function() {
+        if (this.tests.length > 0) {
+            throw new Error("Tried to set file as test after creating a test");
+        }
+        this.wait_for_finish = true;
+        this.file_is_test = true;
+        // Create the test, which will add it to the list of tests
+        async_test();
+    };
+
+    Tests.prototype.set_timeout = function() {
+        var this_obj = this;
+        clearTimeout(this.timeout_id);
+        if (this.timeout_length !== null) {
+            this.timeout_id = setTimeout(function() {
+                                             this_obj.timeout();
+                                         }, this.timeout_length);
+        }
+    };
+
+    Tests.prototype.timeout = function() {
+        if (this.status.status === null) {
+            this.status.status = this.status.TIMEOUT;
+        }
+        this.complete();
+    };
+
+    Tests.prototype.end_wait = function()
+    {
+        this.wait_for_finish = false;
+        if (this.all_done()) {
+            this.complete();
+        }
+    };
+
+    Tests.prototype.push = function(test)
+    {
+        if (this.phase < this.phases.HAVE_TESTS) {
+            this.start();
+        }
+        this.num_pending++;
+        test.index = this.tests.push(test);
+        this.notify_test_state(test);
+    };
+
+    Tests.prototype.notify_test_state = function(test) {
+        var this_obj = this;
+        forEach(this.test_state_callbacks,
+                function(callback) {
+                    callback(test, this_obj);
+                });
+    };
+
+    Tests.prototype.all_done = function() {
+        return (this.tests.length > 0 && test_environment.all_loaded &&
+                this.num_pending === 0 && !this.wait_for_finish &&
+                !this.processing_callbacks &&
+                !this.pending_workers.some(function(w) { return w.running; }));
+    };
+
+    Tests.prototype.start = function() {
+        this.phase = this.phases.HAVE_TESTS;
+        this.notify_start();
+    };
+
+    Tests.prototype.notify_start = function() {
+        var this_obj = this;
+        forEach (this.start_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj.properties);
+                 });
+    };
+
+    Tests.prototype.result = function(test)
+    {
+        if (this.phase > this.phases.HAVE_RESULTS) {
+            return;
+        }
+        this.phase = this.phases.HAVE_RESULTS;
+        this.num_pending--;
+        this.notify_result(test);
+    };
+
+    Tests.prototype.notify_result = function(test) {
+        var this_obj = this;
+        this.processing_callbacks = true;
+        forEach(this.test_done_callbacks,
+                function(callback)
+                {
+                    callback(test, this_obj);
+                });
+        this.processing_callbacks = false;
+        if (this_obj.all_done()) {
+            this_obj.complete();
+        }
+    };
+
+    Tests.prototype.complete = function() {
+        if (this.phase === this.phases.COMPLETE) {
+            return;
+        }
+        this.phase = this.phases.COMPLETE;
+        var this_obj = this;
+        this.tests.forEach(
+            function(x)
+            {
+                if (x.phase < x.phases.COMPLETE) {
+                    this_obj.notify_result(x);
+                    x.cleanup();
+                    x.phase = x.phases.COMPLETE;
+                }
+            }
+        );
+        this.notify_complete();
+    };
+
+    Tests.prototype.notify_complete = function() {
+        var this_obj = this;
+        if (this.status.status === null) {
+            this.status.status = this.status.OK;
+        }
+
+        forEach (this.all_done_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj.tests, this_obj.status);
+                 });
+    };
+
+    Tests.prototype.fetch_tests_from_worker = function(worker) {
+        if (this.phase >= this.phases.COMPLETE) {
+            return;
+        }
+
+        this.pending_workers.push(new RemoteWorker(worker));
+    };
+
+    function fetch_tests_from_worker(port) {
+        tests.fetch_tests_from_worker(port);
+    }
+    expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
+
+    function timeout() {
+        if (tests.timeout_length === null) {
+            tests.timeout();
+        }
+    }
+    expose(timeout, 'timeout');
+
+    function add_start_callback(callback) {
+        tests.start_callbacks.push(callback);
+    }
+
+    function add_test_state_callback(callback) {
+        tests.test_state_callbacks.push(callback);
+    }
+
+    function add_result_callback(callback) {
+        tests.test_done_callbacks.push(callback);
+    }
+
+    function add_completion_callback(callback) {
+        tests.all_done_callbacks.push(callback);
+    }
+
+    expose(add_start_callback, 'add_start_callback');
+    expose(add_test_state_callback, 'add_test_state_callback');
+    expose(add_result_callback, 'add_result_callback');
+    expose(add_completion_callback, 'add_completion_callback');
+
+    function remove(array, item) {
+        var index = array.indexOf(item);
+        if (index > -1) {
+            array.splice(index, 1);
+        }
+    }
+
+    function remove_start_callback(callback) {
+        remove(tests.start_callbacks, callback);
+    }
+
+    function remove_test_state_callback(callback) {
+        remove(tests.test_state_callbacks, callback);
+    }
+
+    function remove_result_callback(callback) {
+        remove(tests.test_done_callbacks, callback);
+    }
+
+    function remove_completion_callback(callback) {
+       remove(tests.all_done_callbacks, callback);
+    }
+
+    /*
+     * Output listener
+    */
+
+    function Output() {
+        this.output_document = document;
+        this.output_node = null;
+        this.enabled = settings.output;
+        this.phase = this.INITIAL;
+    }
+
+    Output.prototype.INITIAL = 0;
+    Output.prototype.STARTED = 1;
+    Output.prototype.HAVE_RESULTS = 2;
+    Output.prototype.COMPLETE = 3;
+
+    Output.prototype.setup = function(properties) {
+        if (this.phase > this.INITIAL) {
+            return;
+        }
+
+        //If output is disabled in testharnessreport.js the test shouldn't be
+        //able to override that
+        this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
+                                        properties.output : settings.output);
+    };
+
+    Output.prototype.init = function(properties) {
+        if (this.phase >= this.STARTED) {
+            return;
+        }
+        if (properties.output_document) {
+            this.output_document = properties.output_document;
+        } else {
+            this.output_document = document;
+        }
+        this.phase = this.STARTED;
+    };
+
+    Output.prototype.resolve_log = function() {
+        var output_document;
+        if (typeof this.output_document === "function") {
+            output_document = this.output_document.apply(undefined);
+        } else {
+            output_document = this.output_document;
+        }
+        if (!output_document) {
+            return;
+        }
+        var node = output_document.getElementById("log");
+        if (!node) {
+            if (!document.body || document.readyState == "loading") {
+                return;
+            }
+            node = output_document.createElement("div");
+            node.id = "log";
+            output_document.body.appendChild(node);
+        }
+        this.output_document = output_document;
+        this.output_node = node;
+    };
+
+    Output.prototype.show_status = function() {
+        if (this.phase < this.STARTED) {
+            this.init();
+        }
+        if (!this.enabled) {
+            return;
+        }
+        if (this.phase < this.HAVE_RESULTS) {
+            this.resolve_log();
+            this.phase = this.HAVE_RESULTS;
+        }
+        var done_count = tests.tests.length - tests.num_pending;
+        if (this.output_node) {
+            if (done_count < 100 ||
+                (done_count < 1000 && done_count % 100 === 0) ||
+                done_count % 1000 === 0) {
+                this.output_node.textContent = "Running, " +
+                    done_count + " complete, " +
+                    tests.num_pending + " remain";
+            }
+        }
+    };
+
+    Output.prototype.show_results = function (tests, harness_status) {
+        if (this.phase >= this.COMPLETE) {
+            return;
+        }
+        if (!this.enabled) {
+            return;
+        }
+        if (!this.output_node) {
+            this.resolve_log();
+        }
+        this.phase = this.COMPLETE;
+
+        var log = this.output_node;
+        if (!log) {
+            return;
+        }
+        var output_document = this.output_document;
+
+        while (log.lastChild) {
+            log.removeChild(log.lastChild);
+        }
+
+        var harness_url = get_harness_url();
+        if (harness_url !== null) {
+            var stylesheet = output_document.createElementNS(xhtml_ns, "link");
+            stylesheet.setAttribute("rel", "stylesheet");
+            stylesheet.setAttribute("href", harness_url + "testharness.css");
+            var heads = output_document.getElementsByTagName("head");
+            if (heads.length) {
+                heads[0].appendChild(stylesheet);
+            }
+        }
+
+        var status_text_harness = {};
+        status_text_harness[harness_status.OK] = "OK";
+        status_text_harness[harness_status.ERROR] = "Error";
+        status_text_harness[harness_status.TIMEOUT] = "Timeout";
+
+        var status_text = {};
+        status_text[Test.prototype.PASS] = "Pass";
+        status_text[Test.prototype.FAIL] = "Fail";
+        status_text[Test.prototype.TIMEOUT] = "Timeout";
+        status_text[Test.prototype.NOTRUN] = "Not Run";
+
+        var status_number = {};
+        forEach(tests,
+                function(test) {
+                    var status = status_text[test.status];
+                    if (status_number.hasOwnProperty(status)) {
+                        status_number[status] += 1;
+                    } else {
+                        status_number[status] = 1;
+                    }
+                });
+
+        function status_class(status)
+        {
+            return status.replace(/\s/g, '').toLowerCase();
+        }
+
+        var summary_template = ["section", {"id":"summary"},
+                                ["h2", {}, "Summary"],
+                                function()
+                                {
+
+                                    var status = status_text_harness[harness_status.status];
+                                    var rv = [["section", {},
+                                               ["p", {},
+                                                "Harness status: ",
+                                                ["span", {"class":status_class(status)},
+                                                 status
+                                                ],
+                                               ]
+                                              ]];
+
+                                    if (harness_status.status === harness_status.ERROR) {
+                                        rv[0].push(["pre", {}, harness_status.message]);
+                                        if (harness_status.stack) {
+                                            rv[0].push(["pre", {}, harness_status.stack]);
+                                        }
+                                    }
+                                    return rv;
+                                },
+                                ["p", {}, "Found ${num_tests} tests"],
+                                function() {
+                                    var rv = [["div", {}]];
+                                    var i = 0;
+                                    while (status_text.hasOwnProperty(i)) {
+                                        if (status_number.hasOwnProperty(status_text[i])) {
+                                            var status = status_text[i];
+                                            rv[0].push(["div", {"class":status_class(status)},
+                                                        ["label", {},
+                                                         ["input", {type:"checkbox", checked:"checked"}],
+                                                         status_number[status] + " " + status]]);
+                                        }
+                                        i++;
+                                    }
+                                    return rv;
+                                },
+                               ];
+
+        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
+
+        forEach(output_document.querySelectorAll("section#summary label"),
+                function(element)
+                {
+                    on_event(element, "click",
+                             function(e)
+                             {
+                                 if (output_document.getElementById("results") === null) {
+                                     e.preventDefault();
+                                     return;
+                                 }
+                                 var result_class = element.parentNode.getAttribute("class");
+                                 var style_element = output_document.querySelector("style#hide-" + result_class);
+                                 var input_element = element.querySelector("input");
+                                 if (!style_element && !input_element.checked) {
+                                     style_element = output_document.createElementNS(xhtml_ns, "style");
+                                     style_element.id = "hide-" + result_class;
+                                     style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
+                                     output_document.body.appendChild(style_element);
+                                 } else if (style_element && input_element.checked) {
+                                     style_element.parentNode.removeChild(style_element);
+                                 }
+                             });
+                });
+
+        // This use of innerHTML plus manual escaping is not recommended in
+        // general, but is necessary here for performance.  Using textContent
+        // on each individual <td> adds tens of seconds of execution time for
+        // large test suites (tens of thousands of tests).
+        function escape_html(s)
+        {
+            return s.replace(/\&/g, "&amp;")
+                .replace(/</g, "&lt;")
+                .replace(/"/g, "&quot;")
+                .replace(/'/g, "&#39;");
+        }
+
+        function has_assertions()
+        {
+            for (var i = 0; i < tests.length; i++) {
+                if (tests[i].properties.hasOwnProperty("assert")) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        function get_assertion(test)
+        {
+            if (test.properties.hasOwnProperty("assert")) {
+                if (Array.isArray(test.properties.assert)) {
+                    return test.properties.assert.join(' ');
+                }
+                return test.properties.assert;
+            }
+            return '';
+        }
+
+        log.appendChild(document.createElementNS(xhtml_ns, "section"));
+        var assertions = has_assertions();
+        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +
+            "<thead><tr><th>Result</th><th>Test Name</th>" +
+            (assertions ? "<th>Assertion</th>" : "") +
+            "<th>Message</th></tr></thead>" +
+            "<tbody>";
+        for (var i = 0; i < tests.length; i++) {
+            html += '<tr class="' +
+                escape_html(status_class(status_text[tests[i].status])) +
+                '"><td>' +
+                escape_html(status_text[tests[i].status]) +
+                "</td><td>" +
+                escape_html(tests[i].name) +
+                "</td><td>" +
+                (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
+                escape_html(tests[i].message ? tests[i].message : " ") +
+                (tests[i].stack ? "<pre>" +
+                 escape_html(tests[i].stack) +
+                 "</pre>": "") +
+                "</td></tr>";
+        }
+        html += "</tbody></table>";
+        try {
+            log.lastChild.innerHTML = html;
+        } catch (e) {
+            log.appendChild(document.createElementNS(xhtml_ns, "p"))
+               .textContent = "Setting innerHTML for the log threw an exception.";
+            log.appendChild(document.createElementNS(xhtml_ns, "pre"))
+               .textContent = html;
+        }
+    };
+
+    /*
+     * Template code
+     *
+     * A template is just a javascript structure. An element is represented as:
+     *
+     * [tag_name, {attr_name:attr_value}, child1, child2]
+     *
+     * the children can either be strings (which act like text nodes), other templates or
+     * functions (see below)
+     *
+     * A text node is represented as
+     *
+     * ["{text}", value]
+     *
+     * String values have a simple substitution syntax; ${foo} represents a variable foo.
+     *
+     * It is possible to embed logic in templates by using a function in a place where a
+     * node would usually go. The function must either return part of a template or null.
+     *
+     * In cases where a set of nodes are required as output rather than a single node
+     * with children it is possible to just use a list
+     * [node1, node2, node3]
+     *
+     * Usage:
+     *
+     * render(template, substitutions) - take a template and an object mapping
+     * variable names to parameters and return either a DOM node or a list of DOM nodes
+     *
+     * substitute(template, substitutions) - take a template and variable mapping object,
+     * make the variable substitutions and return the substituted template
+     *
+     */
+
+    function is_single_node(template)
+    {
+        return typeof template[0] === "string";
+    }
+
+    function substitute(template, substitutions)
+    {
+        if (typeof template === "function") {
+            var replacement = template(substitutions);
+            if (!replacement) {
+                return null;
+            }
+
+            return substitute(replacement, substitutions);
+        }
+
+        if (is_single_node(template)) {
+            return substitute_single(template, substitutions);
+        }
+
+        return filter(map(template, function(x) {
+                              return substitute(x, substitutions);
+                          }), function(x) {return x !== null;});
+    }
+
+    function substitute_single(template, substitutions)
+    {
+        var substitution_re = /\$\{([^ }]*)\}/g;
+
+        function do_substitution(input) {
+            var components = input.split(substitution_re);
+            var rv = [];
+            for (var i = 0; i < components.length; i += 2) {
+                rv.push(components[i]);
+                if (components[i + 1]) {
+                    rv.push(String(substitutions[components[i + 1]]));
+                }
+            }
+            return rv;
+        }
+
+        function substitute_attrs(attrs, rv)
+        {
+            rv[1] = {};
+            for (var name in template[1]) {
+                if (attrs.hasOwnProperty(name)) {
+                    var new_name = do_substitution(name).join("");
+                    var new_value = do_substitution(attrs[name]).join("");
+                    rv[1][new_name] = new_value;
+                }
+            }
+        }
+
+        function substitute_children(children, rv)
+        {
+            for (var i = 0; i < children.length; i++) {
+                if (children[i] instanceof Object) {
+                    var replacement = substitute(children[i], substitutions);
+                    if (replacement !== null) {
+                        if (is_single_node(replacement)) {
+                            rv.push(replacement);
+                        } else {
+                            extend(rv, replacement);
+                        }
+                    }
+                } else {
+                    extend(rv, do_substitution(String(children[i])));
+                }
+            }
+            return rv;
+        }
+
+        var rv = [];
+        rv.push(do_substitution(String(template[0])).join(""));
+
+        if (template[0] === "{text}") {
+            substitute_children(template.slice(1), rv);
+        } else {
+            substitute_attrs(template[1], rv);
+            substitute_children(template.slice(2), rv);
+        }
+
+        return rv;
+    }
+
+    function make_dom_single(template, doc)
+    {
+        var output_document = doc || document;
+        var element;
+        if (template[0] === "{text}") {
+            element = output_document.createTextNode("");
+            for (var i = 1; i < template.length; i++) {
+                element.data += template[i];
+            }
+        } else {
+            element = output_document.createElementNS(xhtml_ns, template[0]);
+            for (var name in template[1]) {
+                if (template[1].hasOwnProperty(name)) {
+                    element.setAttribute(name, template[1][name]);
+                }
+            }
+            for (var i = 2; i < template.length; i++) {
+                if (template[i] instanceof Object) {
+                    var sub_element = make_dom(template[i]);
+                    element.appendChild(sub_element);
+                } else {
+                    var text_node = output_document.createTextNode(template[i]);
+                    element.appendChild(text_node);
+                }
+            }
+        }
+
+        return element;
+    }
+
+    function make_dom(template, substitutions, output_document)
+    {
+        if (is_single_node(template)) {
+            return make_dom_single(template, output_document);
+        }
+
+        return map(template, function(x) {
+                       return make_dom_single(x, output_document);
+                   });
+    }
+
+    function render(template, substitutions, output_document)
+    {
+        return make_dom(substitute(template, substitutions), output_document);
+    }
+
+    /*
+     * Utility funcions
+     */
+    function assert(expected_true, function_name, description, error, substitutions)
+    {
+        if (tests.tests.length === 0) {
+            tests.set_file_is_test();
+        }
+        if (expected_true !== true) {
+            var msg = make_message(function_name, description,
+                                   error, substitutions);
+            throw new AssertionError(msg);
+        }
+    }
+
+    function AssertionError(message)
+    {
+        this.message = message;
+        this.stack = this.get_stack();
+    }
+
+    AssertionError.prototype = Object.create(Error.prototype);
+
+    AssertionError.prototype.get_stack = function() {
+        var stack = new Error().stack;
+        // IE11 does not initialize 'Error.stack' until the object is thrown.
+        if (!stack) {
+            try {
+                throw new Error();
+            } catch (e) {
+                stack = e.stack;
+            }
+        }
+
+        var lines = stack.split("\n");
+
+        // Create a pattern to match stack frames originating within testharness.js.  These include the
+        // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21').
+        var re = new RegExp((get_script_url() || "\\btestharness.js") + ":\\d+:\\d+");
+
+        // Some browsers include a preamble that specifies the type of the error object.  Skip this by
+        // advancing until we find the first stack frame originating from testharness.js.
+        var i = 0;
+        while (!re.test(lines[i]) && i < lines.length) {
+            i++;
+        }
+
+        // Then skip the top frames originating from testharness.js to begin the stack at the test code.
+        while (re.test(lines[i]) && i < lines.length) {
+            i++;
+        }
+
+        // Paranoid check that we didn't skip all frames.  If so, return the original stack unmodified.
+        if (i >= lines.length) {
+            return stack;
+        }
+
+        return lines.slice(i).join("\n");
+    }
+
+    function make_message(function_name, description, error, substitutions)
+    {
+        for (var p in substitutions) {
+            if (substitutions.hasOwnProperty(p)) {
+                substitutions[p] = format_value(substitutions[p]);
+            }
+        }
+        var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
+                                   merge({function_name:function_name,
+                                          description:(description?description + " ":"")},
+                                          substitutions));
+        return node_form.slice(1).join("");
+    }
+
+    function filter(array, callable, thisObj) {
+        var rv = [];
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                var pass = callable.call(thisObj, array[i], i, array);
+                if (pass) {
+                    rv.push(array[i]);
+                }
+            }
+        }
+        return rv;
+    }
+
+    function map(array, callable, thisObj)
+    {
+        var rv = [];
+        rv.length = array.length;
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                rv[i] = callable.call(thisObj, array[i], i, array);
+            }
+        }
+        return rv;
+    }
+
+    function extend(array, items)
+    {
+        Array.prototype.push.apply(array, items);
+    }
+
+    function forEach(array, callback, thisObj)
+    {
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                callback.call(thisObj, array[i], i, array);
+            }
+        }
+    }
+
+    function merge(a,b)
+    {
+        var rv = {};
+        var p;
+        for (p in a) {
+            rv[p] = a[p];
+        }
+        for (p in b) {
+            rv[p] = b[p];
+        }
+        return rv;
+    }
+
+    function expose(object, name)
+    {
+        var components = name.split(".");
+        var target = test_environment.global_scope();
+        for (var i = 0; i < components.length - 1; i++) {
+            if (!(components[i] in target)) {
+                target[components[i]] = {};
+            }
+            target = target[components[i]];
+        }
+        target[components[components.length - 1]] = object;
+    }
+
+    function is_same_origin(w) {
+        try {
+            'random_prop' in w;
+            return true;
+        } catch (e) {
+            return false;
+        }
+    }
+
+    /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
+    function get_script_url()
+    {
+        if (!('document' in self)) {
+            return undefined;
+        }
+
+        var scripts = document.getElementsByTagName("script");
+        for (var i = 0; i < scripts.length; i++) {
+            var src;
+            if (scripts[i].src) {
+                src = scripts[i].src;
+            } else if (scripts[i].href) {
+                //SVG case
+                src = scripts[i].href.baseVal;
+            }
+
+            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
+            if (matches) {
+                return src;
+            }
+        }
+        return undefined;
+    }
+
+    /** Returns the URL path at which the files for testharness.js are assumed to reside (e.g., '/resources/').
+        The path is derived from inspecting the 'src' of the <script> tag that included 'testharness.js'. */
+    function get_harness_url()
+    {
+        var script_url = get_script_url();
+
+        // Exclude the 'testharness.js' file from the returned path, but '+ 1' to include the trailing slash.
+        return script_url ? script_url.slice(0, script_url.lastIndexOf('/') + 1) : undefined;
+    }
+
+    function supports_post_message(w)
+    {
+        var supports;
+        var type;
+        // Given IE implements postMessage across nested iframes but not across
+        // windows or tabs, you can't infer cross-origin communication from the presence
+        // of postMessage on the current window object only.
+        //
+        // Touching the postMessage prop on a window can throw if the window is
+        // not from the same origin AND post message is not supported in that
+        // browser. So just doing an existence test here won't do, you also need
+        // to wrap it in a try..cacth block.
+        try {
+            type = typeof w.postMessage;
+            if (type === "function") {
+                supports = true;
+            }
+
+            // IE8 supports postMessage, but implements it as a host object which
+            // returns "object" as its `typeof`.
+            else if (type === "object") {
+                supports = true;
+            }
+
+            // This is the case where postMessage isn't supported AND accessing a
+            // window property across origins does NOT throw (e.g. old Safari browser).
+            else {
+                supports = false;
+            }
+        } catch (e) {
+            // This is the case where postMessage isn't supported AND accessing a
+            // window property across origins throws (e.g. old Firefox browser).
+            supports = false;
+        }
+        return supports;
+    }
+
+    /**
+     * Setup globals
+     */
+
+    var tests = new Tests();
+
+    addEventListener("error", function(e) {
+        if (tests.file_is_test) {
+            var test = tests.tests[0];
+            if (test.phase >= test.phases.HAS_RESULT) {
+                return;
+            }
+            test.set_status(test.FAIL, e.message, e.stack);
+            test.phase = test.phases.HAS_RESULT;
+            test.done();
+            done();
+        } else if (!tests.allow_uncaught_exception) {
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = e.message;
+            tests.status.stack = e.stack;
+        }
+    });
+
+    test_environment.on_tests_ready();
+
+})();
+// vim: set expandtab shiftwidth=4 tabstop=4:
diff --git a/LayoutTests/http/tests/resources/testharnessreport.js b/LayoutTests/http/tests/resources/testharnessreport.js
new file mode 100644 (file)
index 0000000..b25ddd7
--- /dev/null
@@ -0,0 +1,87 @@
+/*\r
+ * THIS FILE INTENTIONALLY LEFT BLANK\r
+ *\r
+ * More specifically, this file is intended for vendors to implement\r
+ * code needed to integrate testharness.js tests with their own test systems.\r
+ *\r
+ * Typically such integration will attach callbacks when each test is\r
+ * has run, using add_result_callback(callback(test)), or when the whole test file has\r
+ * completed, using add_completion_callback(callback(tests, harness_status)).\r
+ *\r
+ * For more documentation about the callback functions and the\r
+ * parameters they are called with see testharness.js\r
+ */\r
+\r
+// Setup for WebKit JavaScript tests\r
+if (self.testRunner) {\r
+    testRunner.dumpAsText();\r
+    testRunner.waitUntilDone();\r
+}\r
+\r
+// Function used to convert the test status code into\r
+// the corresponding string\r
+function convertResult(resultStatus){\r
+       if(resultStatus == 0)\r
+               return("PASS");\r
+       else if(resultStatus == 1)\r
+               return("FAIL");\r
+       else if(resultStatus == 2)\r
+               return("TIMEOUT");\r
+       else\r
+               return("NOTRUN");\r
+}\r
+\r
+/* Disable the default output of testharness.js.  The default output formats\r
+*  test results into an HTML table.  When that table is dumped as text, no\r
+*  spacing between cells is preserved, and it is therefore not readable. By\r
+*  setting output to false, the HTML table will not be created\r
+*/\r
+setup({"output":false});\r
+\r
+/*  Using a callback function, test results will be added to the page in a \r
+*   manner that allows dumpAsText to produce readable test results\r
+*/\r
+add_completion_callback(function (tests, harness_status){\r
+       \r
+       // Create element to hold results\r
+       var results = document.createElement("pre");\r
+       \r
+       // Declare result string\r
+       var resultStr = "\n";\r
+       \r
+       // Check harness_status.  If it is not 0, tests did not\r
+       // execute correctly, output the error code and message\r
+       if(harness_status.status != 0){\r
+               resultStr += "Harness Error. harness_status.status = " + \r
+                                        harness_status.status +\r
+                                        " , harness_status.message = " +\r
+                                        harness_status.message;\r
+       }\r
+       else {\r
+               // Iterate through tests array and build string that contains\r
+               // results for all tests\r
+               for(var i=0; i<tests.length; i++){                               \r
+                       var message = (tests[i].message != null) ? tests[i].message : "";\r
+                       if (tests[i].status == 1 && !tests[i].dumpStack) {\r
+                               // Remove stack for failed tests for proper string comparison without file paths.\r
+                               // For a test to dump the stack set its dumpStack attribute to true.\r
+                               var