Bug 1173288 - Update testharness code in imptests; rs=jgraham
authorMs2ger <ms2ger@gmail.com>
Sat, 20 Jun 2015 09:16:51 +0200
changeset 249823 caabf5d3e1bd17762bb6acaf940f9496a1ceb400
parent 249822 fe94dced5e595db6b1e8aaa824583a31e6b05355
child 249824 ea5b6a488b421af3eac319d095c233644105f0c5
push id61350
push userMs2ger@gmail.com
push dateSat, 20 Jun 2015 07:17:33 +0000
treeherdermozilla-inbound@a560e8f99991 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgraham
bugs1173288
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1173288 - Update testharness code in imptests; rs=jgraham
dom/imptests/WebIDLParser.js
dom/imptests/failures/html/dom/test_interfaces.html.json
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json
dom/imptests/html/dom/test_interfaces.html
dom/imptests/idlharness.js
dom/imptests/moz.build
dom/imptests/testharness.css
dom/imptests/testharness.js
--- a/dom/imptests/WebIDLParser.js
+++ b/dom/imptests/WebIDLParser.js
@@ -915,10 +915,10 @@
             parse:  function (str, opt) {
                 if (!opt) opt = {};
                 var tokens = tokenise(str);
                 return parse(tokens, opt);
             }
     };
 
     if (inNode) module.exports = obj;
-    else        window.WebIDL2 = obj;
+    else        self.WebIDL2 = obj;
 }());
--- a/dom/imptests/failures/html/dom/test_interfaces.html.json
+++ b/dom/imptests/failures/html/dom/test_interfaces.html.json
@@ -1,42 +1,35 @@
 {
   "DOMException exception: existence and properties of exception interface prototype object's \"name\" property": true,
-  "CustomEvent interface: existence and properties of interface object": true,
   "EventListener interface: existence and properties of interface prototype object": true,
   "EventListener interface: existence and properties of interface prototype object's \"constructor\" property": true,
   "EventListener interface: operation handleEvent(Event)": true,
   "MutationObserver interface: operation observe(Node,MutationObserverInit)": true,
-  "Node interface: existence and properties of interface object": true,
-  "Document interface: existence and properties of interface object": true,
   "Document interface: operation prepend([object Object],[object Object])": true,
   "Document interface: operation append([object Object],[object Object])": true,
-  "XMLDocument interface: existence and properties of interface object": true,
   "Document interface: xmlDoc must inherit property \"prepend\" with the proper type (28)": true,
   "Document interface: calling prepend([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true,
   "Document interface: xmlDoc must inherit property \"append\" with the proper type (29)": true,
   "Document interface: calling append([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true,
-  "DocumentFragment interface: existence and properties of interface object": true,
   "DocumentFragment interface: operation prepend([object Object],[object Object])": true,
   "DocumentFragment interface: operation append([object Object],[object Object])": true,
   "DocumentFragment interface: document.createDocumentFragment() must inherit property \"prepend\" with the proper type (4)": true,
   "DocumentFragment interface: calling prepend([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true,
   "DocumentFragment interface: document.createDocumentFragment() must inherit property \"append\" with the proper type (5)": true,
   "DocumentFragment interface: calling append([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true,
-  "DocumentType interface: existence and properties of interface object": true,
   "DocumentType interface: operation before([object Object],[object Object])": true,
   "DocumentType interface: operation after([object Object],[object Object])": true,
   "DocumentType interface: operation replace([object Object],[object Object])": true,
   "DocumentType interface: document.doctype must inherit property \"before\" with the proper type (3)": true,
   "DocumentType interface: calling before([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
   "DocumentType interface: document.doctype must inherit property \"after\" with the proper type (4)": true,
   "DocumentType interface: calling after([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
   "DocumentType interface: document.doctype must inherit property \"replace\" with the proper type (5)": true,
   "DocumentType interface: calling replace([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
-  "Element interface: existence and properties of interface object": true,
   "Element interface: attribute namespaceURI": true,
   "Element interface: attribute prefix": true,
   "Element interface: attribute localName": true,
   "Element interface: operation prepend([object Object],[object Object])": true,
   "Element interface: operation append([object Object],[object Object])": true,
   "Element interface: operation before([object Object],[object Object])": true,
   "Element interface: operation after([object Object],[object Object])": true,
   "Element interface: operation replace([object Object],[object Object])": true,
@@ -47,41 +40,53 @@
   "Element interface: element must inherit property \"before\" with the proper type (25)": true,
   "Element interface: calling before([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Element interface: element must inherit property \"after\" with the proper type (26)": true,
   "Element interface: calling after([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Element interface: element must inherit property \"replace\" with the proper type (27)": true,
   "Element interface: calling replace([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Attr interface: existence and properties of interface object": true,
   "Attr interface: existence and properties of interface prototype object": true,
-  "CharacterData interface: existence and properties of interface object": true,
   "CharacterData interface: operation before([object Object],[object Object])": true,
   "CharacterData interface: operation after([object Object],[object Object])": true,
   "CharacterData interface: operation replace([object Object],[object Object])": true,
-  "Text interface: existence and properties of interface object": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
-  "ProcessingInstruction interface: existence and properties of interface object": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
-  "Comment interface: existence and properties of interface object": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
-  "NodeFilter interface: existence and properties of interface object": true,
-  "NodeList interface: existence and properties of interface prototype object": true,
-  "DOMTokenList interface: operation add(DOMString)": true,
-  "DOMTokenList interface: operation remove(DOMString)": true,
-  "DOMTokenList interface: calling add(DOMString) on document.body.classList with too few arguments must throw TypeError": true,
-  "DOMTokenList interface: calling remove(DOMString) on document.body.classList with too few arguments must throw TypeError": true,
-  "DOMSettableTokenList interface: existence and properties of interface object": true
+  "NodeFilter interface: existence and properties of interface prototype object": true,
+  "NodeFilter interface: existence and properties of interface prototype object": true,
+  "NodeFilter interface: existence and properties of interface prototype object's \"constructor\" property": true,
+  "EventListener interface: existence and properties of interface object": true,
+  "EventListener interface object length": true,
+  "NodeFilter interface: constant FILTER_ACCEPT on interface prototype object": true,
+  "NodeFilter interface: constant FILTER_REJECT on interface prototype object": true,
+  "NodeFilter interface: constant FILTER_SKIP on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ALL on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ELEMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ATTRIBUTE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_TEXT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_CDATA_SECTION on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ENTITY_REFERENCE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ENTITY on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_PROCESSING_INSTRUCTION on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_COMMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT_TYPE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT_FRAGMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_NOTATION on interface prototype object": true,
+  "NodeFilter interface: operation acceptNode(Node)": true,
+  "NodeList interface: existence and properties of interface prototype object": true
 }
deleted file mode 100644
--- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-# THIS FILE IS AUTOGENERATED BY parseFailures.py - DO NOT EDIT
-[DEFAULT]
-support-files =
-
-
-[test_interfaces.html.json]
deleted file mode 100644
--- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "XMLHttpRequestUpload interface: existence and properties of interface object": true,
-  "XMLHttpRequest interface: existence and properties of interface object": true
-}
--- a/dom/imptests/html/dom/test_interfaces.html
+++ b/dom/imptests/html/dom/test_interfaces.html
@@ -5,45 +5,16 @@
 <script src=/resources/testharnessreport.js></script>
 <script src=/resources/WebIDLParser.js></script>
 <script src=/resources/idlharness.js></script>
 
 <h1>DOM IDL tests</h1>
 <div id=log></div>
 
 <script type=text/plain>
-exception DOMException {
-  const unsigned short INDEX_SIZE_ERR = 1;
-  const unsigned short DOMSTRING_SIZE_ERR = 2; // historical
-  const unsigned short HIERARCHY_REQUEST_ERR = 3;
-  const unsigned short WRONG_DOCUMENT_ERR = 4;
-  const unsigned short INVALID_CHARACTER_ERR = 5;
-  const unsigned short NO_DATA_ALLOWED_ERR = 6; // historical
-  const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
-  const unsigned short NOT_FOUND_ERR = 8;
-  const unsigned short NOT_SUPPORTED_ERR = 9;
-  const unsigned short INUSE_ATTRIBUTE_ERR = 10; // historical
-  const unsigned short INVALID_STATE_ERR = 11;
-  const unsigned short SYNTAX_ERR = 12;
-  const unsigned short INVALID_MODIFICATION_ERR = 13;
-  const unsigned short NAMESPACE_ERR = 14;
-  const unsigned short INVALID_ACCESS_ERR = 15;
-  const unsigned short VALIDATION_ERR = 16; // historical
-  const unsigned short TYPE_MISMATCH_ERR = 17; // historical; use TypeError instead
-  const unsigned short SECURITY_ERR = 18;
-  const unsigned short NETWORK_ERR = 19;
-  const unsigned short ABORT_ERR = 20;
-  const unsigned short URL_MISMATCH_ERR = 21;
-  const unsigned short QUOTA_EXCEEDED_ERR = 22;
-  const unsigned short TIMEOUT_ERR = 23;
-  const unsigned short INVALID_NODE_TYPE_ERR = 24;
-  const unsigned short DATA_CLONE_ERR = 25;
-  unsigned short code;
-};
-
 [Constructor(DOMString name)]
 interface DOMError {
   readonly attribute DOMString name;
 };
 
 [Constructor(DOMString type, optional EventInit eventInitDict)]
 interface Event {
   readonly attribute DOMString type;
@@ -446,30 +417,28 @@ interface DOMTokenList {
 };
 
 interface DOMSettableTokenList : DOMTokenList {
             attribute DOMString value;
 };
 </script>
 <script>
 "use strict";
-var xmlDoc, domException, detachedRange, element;
+var xmlDoc, detachedRange, element;
 var idlArray;
 setup(function() {
 	xmlDoc = document.implementation.createDocument(null, "", null);
-	try { document.appendChild(document); } catch(e) { domException = e; }
 	detachedRange = document.createRange();
 	detachedRange.detach();
 	element = xmlDoc.createElementNS(null, "test");
 	element.setAttribute("bar", "baz");
 
 	idlArray = new IdlArray();
 	idlArray.add_idls(document.querySelector("script[type=text\\/plain]").textContent);
 	idlArray.add_objects({
-		DOMException: ['domException'],
 		Event: ['document.createEvent("Event")', 'new Event("foo")'],
 		CustomEvent: ['new CustomEvent("foo")'],
 		XMLDocument: ['xmlDoc'],
 		DOMImplementation: ['document.implementation'],
 		DocumentFragment: ['document.createDocumentFragment()'],
 		DocumentType: ['document.doctype'],
 		Element: ['element'],
 		Attr: ['document.querySelector("[id]").attributes[0]'],
--- a/dom/imptests/idlharness.js
+++ b/dom/imptests/idlharness.js
@@ -51,19 +51,32 @@ policies and contribution forms [3].
 /// 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
-window.IdlArray = function()
+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 = {};
@@ -161,21 +174,18 @@ IdlArray.prototype.internal_add_idls = f
         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);
-            break;
-
-        case "exception":
-            this.members[parsed_idl.name] = new IdlException(parsed_idl);
+            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;
 
@@ -188,18 +198,18 @@ IdlArray.prototype.internal_add_idls = f
             console.log("callback not yet supported");
             break;
 
         case "enum":
             this.members[parsed_idl.name] = new IdlEnum(parsed_idl);
             break;
 
         case "callback interface":
-            // TODO
-            console.log("callback interface not yet supported");
+            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));
 };
 
@@ -408,22 +418,24 @@ IdlArray.prototype.assert_type_is = func
             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":
@@ -443,23 +455,23 @@ IdlArray.prototype.assert_type_is = func
     }
 
     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 window.
+        // 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 window)
+        && type in self)
         {
-            assert_true(value instanceof window[type], "not instanceof " + type);
+            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)
     {
@@ -524,21 +536,18 @@ function IdlDictionary(obj)
      * if there is none.
      */
     this.base = obj.inheritance;
 }
 
 //@}
 IdlDictionary.prototype = Object.create(IdlObject.prototype);
 
-/// IdlExceptionOrInterface ///
-// Code sharing!
-function IdlExceptionOrInterface(obj)
-//@{
-{
+/// 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;
 
@@ -552,27 +561,58 @@ function IdlExceptionOrInterface(obj)
      */
     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;
+};
 //@}
-IdlExceptionOrInterface.prototype = Object.create(IdlObject.prototype);
-IdlExceptionOrInterface.prototype.test = function()
+
+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;
@@ -588,853 +628,604 @@ IdlExceptionOrInterface.prototype.test =
     // 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();
 };
-
-//@}
-
-/// IdlException ///
-function IdlException(obj) { IdlExceptionOrInterface.call(this, obj); }
-IdlException.prototype = Object.create(IdlExceptionOrInterface.prototype);
-IdlException.prototype.test_self = function()
-//@{
-{
-    test(function()
-    {
-        // "For every exception that is not declared with the
-        // [NoInterfaceObject] extended attribute, a corresponding property
-        // must exist on the exception’s relevant namespace object. The name of
-        // the property is the identifier of the exception, and its value is an
-        // object called the exception interface object, which provides access
-        // to any constants that have been associated with the exception. The
-        // property has the attributes { [[Writable]]: true, [[Enumerable]]:
-        // false, [[Configurable]]: true }."
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        var desc = Object.getOwnPropertyDescriptor(window, this.name);
-        assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
-        assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
-        assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
-        assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
-        assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
-
-        // "The exception interface object for a given exception must be a
-        // function object."
-        // "If an object is defined to be a function object, then it has
-        // characteristics as follows:"
-        // "Its [[Prototype]] internal property is the Function prototype
-        // object."
-        // Note: This doesn't match browsers as of December 2011, see
-        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14813
-        assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
-                      "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
-        // "Its [[Get]] internal property is set as described in ECMA-262
-        // section 15.3.5.4."
-        // Not much to test for this.
-        // "Its [[Construct]] internal property is set as described in ECMA-262
-        // section 13.2.2."
-        // Tested below.
-        // "Its [[HasInstance]] internal property is set as described in
-        // ECMA-262 section 15.3.5.3, unless otherwise specified."
-        // TODO
-        // "Its [[Class]] internal property is “Function”."
-        // String() returns something implementation-dependent, because it
-        // calls Function#toString.
-        assert_class_string(window[this.name], "Function",
-                            "class string of " + this.name);
-
-        // TODO: Test 4.9.1.1. Exception interface object [[Call]] method
-        // (which does not match browsers:
-        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14885)
-    }.bind(this), this.name + " exception: existence and properties of exception interface object");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-
-        // "The exception interface object must also have a property named
-        // “prototype” with attributes { [[Writable]]: false, [[Enumerable]]:
-        // false, [[Configurable]]: false } whose value is an object called the
-        // exception interface prototype object. This object also provides
-        // access to the constants that are declared on the exception."
-        assert_own_property(window[this.name], "prototype",
-                            'exception "' + this.name + '" does not have own property "prototype"');
-        var desc = Object.getOwnPropertyDescriptor(window[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");
-
-        // "The exception interface prototype object for a given exception must
-        // have an internal [[Prototype]] property whose value is as follows:
-        //
-        // "If the exception is declared to inherit from another exception,
-        // then the value of the internal [[Prototype]] property is the
-        // exception interface prototype object for the inherited exception.
-        // "Otherwise, the exception is not declared to inherit from another
-        // exception. The value of the internal [[Prototype]] property is the
-        // Error prototype object ([ECMA-262], section 15.11.3.1)."
-        //
-        // Note: This doesn't match browsers as of December 2011, see
-        // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14887.
-        var inherit_exception = this.base ? this.base : "Error";
-        assert_own_property(window, inherit_exception,
-                            'should inherit from ' + inherit_exception + ', but window has no such property');
-        assert_own_property(window[inherit_exception], "prototype",
-                            'should inherit from ' + inherit_exception + ', but that object has no "prototype" property');
-        assert_equals(Object.getPrototypeOf(window[this.name].prototype),
-                      window[inherit_exception].prototype,
-                      'prototype of ' + this.name + '.prototype is not ' + inherit_exception + '.prototype');
-
-        // "The class string of an exception interface prototype object is the
-        // concatenation of the exception’s identifier and the string
-        // “Prototype”."
-        assert_class_string(window[this.name].prototype, this.name + "Prototype",
-                            "class string of " + this.name + ".prototype");
-        // TODO: Test String(), based on ES definition of
-        // Error.prototype.toString?
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
-                            'interface "' + this.name + '" does not have own property "prototype"');
-
-        // "There must be a property named “name” on the exception interface
-        // prototype object with attributes { [[Writable]]: true,
-        // [[Enumerable]]: false, [[Configurable]]: true } and whose value is
-        // the identifier of the exception."
-        assert_own_property(window[this.name].prototype, "name",
-                'prototype object does not have own property "name"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "name");
-        assert_false("get" in desc, this.name + ".prototype.name has getter");
-        assert_false("set" in desc, this.name + ".prototype.name has setter");
-        assert_true(desc.writable, this.name + ".prototype.name is not writable");
-        assert_false(desc.enumerable, this.name + ".prototype.name is enumerable");
-        assert_true(desc.configurable, this.name + ".prototype.name is not configurable");
-        assert_equals(desc.value, this.name, this.name + ".prototype.name has incorrect value");
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"name\" property");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
-                            'interface "' + this.name + '" does not have own property "prototype"');
-
-        // "If the [NoInterfaceObject] extended attribute was not specified on
-        // the exception, then there must also be a property named
-        // “constructor” on the exception interface prototype object with
-        // attributes { [[Writable]]: true, [[Enumerable]]: false,
-        // [[Configurable]]: true } and whose value is a reference to the
-        // exception interface object for the exception."
-        assert_own_property(window[this.name].prototype, "constructor",
-                            this.name + '.prototype does not have own property "constructor"');
-        var desc = Object.getOwnPropertyDescriptor(window[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(window[this.name].prototype.constructor, window[this.name],
-                      this.name + '.prototype.constructor is not the same object as ' + this.name);
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"constructor\" property");
-};
-
-//@}
-IdlException.prototype.test_members = function()
-//@{
-{
-    for (var i = 0; i < this.members.length; i++)
-    {
-        var member = this.members[i];
-        if (member.untested)
-        {
-            continue;
-        }
-        if (member.type == "const" && member.name != "prototype")
-        {
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-
-                // "For each constant defined on the exception, there must be a
-                // corresponding property on the exception interface object, if
-                // it exists, if the identifier of the constant is not
-                // “prototype”."
-                assert_own_property(window[this.name], member.name);
-                // "The value of the property is the ECMAScript value that is
-                // equivalent to the constant’s IDL value, according to the
-                // rules in section 4.2 above."
-                assert_equals(window[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(window[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 + " exception: constant " + member.name + " on exception interface object");
-            // "In addition, a property with the same characteristics must
-            // exist on the exception interface prototype object."
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-                assert_own_property(window[this.name], "prototype",
-                                    'exception "' + this.name + '" does not have own property "prototype"');
-
-                assert_own_property(window[this.name].prototype, member.name);
-                assert_equals(window[this.name].prototype[member.name], constValue(member.value),
-                              "property has wrong value");
-                var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, 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 + " exception: constant " + member.name + " on exception interface prototype object");
-        }
-        else if (member.type == "field")
-        {
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-                assert_own_property(window[this.name], "prototype",
-                                    'exception "' + this.name + '" does not have own property "prototype"');
-
-                // "For each exception field, there must be a corresponding
-                // property on the exception interface prototype object, whose
-                // characteristics are as follows:
-                // "The name of the property is the identifier of the exception
-                // field."
-                assert_own_property(window[this.name].prototype, member.name);
-                // "The property has attributes { [[Get]]: G, [[Enumerable]]:
-                // true, [[Configurable]]: true }, where G is the exception
-                // field getter, defined below."
-                var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, 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');
-                // TODO: ES5 doesn't seem to say whether desc should have a
-                // .set property.
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_true(desc.configurable, "property is not configurable");
-                // "The exception field getter is a Function object whose
-                // behavior when invoked is as follows:"
-                assert_equals(typeof desc.get, "function", "typeof getter");
-                // "The value of the Function object’s “length” property is the
-                // Number value 0."
-                // This test is before the TypeError tests so that it's easiest
-                // to see that Firefox 11a1 only fails one assert in this test.
-                assert_equals(desc.get.length, 0, "getter length");
-                // "Let O be the result of calling ToObject on the this value.
-                // "If O is not a platform object representing an exception for
-                // the exception on which the exception field was declared,
-                // then throw a TypeError."
-                // TODO: Test on a platform object representing an exception.
-                assert_throws(new TypeError(), function()
-                {
-                    window[this.name].prototype[member.name];
-                }.bind(this), "getting property on prototype object must throw TypeError");
-                assert_throws(new TypeError(), function()
-                {
-                    desc.get.call({});
-                }.bind(this), "calling getter on wrong object type must throw TypeError");
-            }.bind(this), this.name + " exception: field " + member.name + " on exception interface prototype object");
-        }
-    }
-};
-
-//@}
-IdlException.prototype.test_object = function(desc)
-//@{
-{
-    var obj, exception = null;
-    try
-    {
-        obj = eval(desc);
-    }
-    catch(e)
-    {
-        exception = e;
-    }
-
-    test(function()
-    {
-        assert_equals(exception, null, "Unexpected exception when evaluating object");
-        assert_equals(typeof obj, "object", "wrong typeof object");
-
-        // 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 != "object" || obj instanceof Object))
-        {
-            assert_own_property(window, this.name,
-                                "window does not have own property " + format_value(this.name));
-            assert_own_property(window[this.name], "prototype",
-                                'exception "' + this.name + '" does not have own property "prototype"');
-
-            // "The value of the internal [[Prototype]] property of the
-            // exception object must be the exception interface prototype
-            // object from the global environment the exception object is
-            // associated with."
-            assert_equals(Object.getPrototypeOf(obj),
-                          window[this.name].prototype,
-                          desc + "'s prototype is not " + this.name + ".prototype");
-        }
-
-        // "The class string of the exception object must be the identifier of
-        // the exception."
-        assert_class_string(obj, this.name, "class string of " + desc);
-        // Stringifier is not defined for DOMExceptions, because message isn't
-        // defined.
-    }.bind(this), this.name + " must be represented by " + desc);
-
-    for (var i = 0; i < this.members.length; i++)
-    {
-        var member = this.members[i];
-        test(function()
-        {
-            assert_equals(exception, null, "Unexpected exception when evaluating object");
-            assert_equals(typeof obj, "object", "wrong typeof object");
-            assert_inherits(obj, member.name);
-            if (member.type == "const")
-            {
-                assert_equals(obj[member.name], constValue(member.value));
-            }
-            if (member.type == "field")
-            {
-                this.array.assert_type_is(obj[member.name], member.idlType);
-            }
-        }.bind(this), this.name + " exception: " + desc + ' must inherit property "' + member.name + '" with the proper type');
-    }
-};
-//@}
-
-/// IdlInterface ///
-function IdlInterface(obj) { IdlExceptionOrInterface.call(this, obj); }
-IdlInterface.prototype = Object.create(IdlExceptionOrInterface.prototype);
-IdlInterface.prototype.is_callback = function()
-//@{
-{
-    return this.has_extended_attribute("Callback");
-};
-//@}
-
-IdlInterface.prototype.has_constants = function()
-//@{
-{
-    return this.members.some(function(member) {
-        return member.type === "const";
-    });
-};
 //@}
 
 IdlInterface.prototype.test_self = function()
 //@{
 {
     test(function()
     {
-        // This function tests WebIDL as of 2012-11-28.
+        // This function tests WebIDL as of 2015-01-13.
+        // TODO: Consider [Exposed].
 
-        // "For every interface that:
+        // "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(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        var desc = Object.getOwnPropertyDescriptor(window, this.name);
-        assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
-        assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
-        assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
-        assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
-        assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
+        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(window[this.name]), Object.prototype,
-                          "prototype of window's property " + format_value(this.name) + " is not Object.prototype");
+            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 the Function prototype
-        //    object."
-        assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
-                      "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
+        // Its [[Prototype]] internal property is otherwise specified (see
+        // below).
 
         // "* Its [[Get]] internal property is set as described in ECMA-262
-        //    section 15.3.5.4."
+        //    section 9.1.8."
         // Not much to test for this.
 
         // "* Its [[Construct]] internal property is set as described in
-        //    ECMA-262 section 13.2.2."
+        //    ECMA-262 section 19.2.2.3."
         // Tested below if no constructor is defined.  TODO: test constructors
         // if defined.
 
-        // "* Its [[HasInstance]] internal property is set as described in
-        //    ECMA-262 section 15.3.5.3, unless otherwise specified."
+        // "* Its @@hasInstance property is set as described in ECMA-262
+        //    section 19.2.3.8, unless otherwise specified."
         // TODO
 
-        // "* Its [[NativeBrand]] internal property is “Function”."
-        // String() returns something implementation-dependent, because it calls
-        // Function#toString.
-        assert_class_string(window[this.name], "Function", "class string of " + this.name);
+        // 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() {
-                window[this.name]();
+                self[this.name]();
             }.bind(this), "interface object didn't throw TypeError when called as a function");
             assert_throws(new TypeError(), function() {
-                new window[this.name]();
+                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(window, this.name,
-                                "window does not have own property " + format_value(this.name));
+            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(window[this.name], "length");
-            var desc = Object.getOwnPropertyDescriptor(window[this.name], "length");
+            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;
-            if (!constructors.length) {
-                // "If the [Constructor] extended attribute, does not appear on
-                // the interface definition, then the value is 0."
-                expected_length = 0;
-            } else {
-                // "Otherwise, the value is determined as follows: . . .
-                // "Return the length of the shortest argument list of the
-                // entries in S."
-                expected_length = constructors.map(function(attr) {
-                    return attr.arguments ? attr.arguments.filter(function(arg) {
-                        return !arg.optional;
-                    }).length : 0;
-                })
-                .reduce(function(m, n) { return Math.min(m, n); });
-            }
-            assert_equals(window[this.name].length, expected_length, "wrong value for " + this.name + ".length");
+            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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        // This function tests WebIDL as of 2015-01-21.
+        // https://heycam.github.io/webidl/#interface-object
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        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;
         }
 
-        // "The interface object must also 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 attributes and operations defined on the
-        // interface, and is described in more detail in section 4.5.3 below."
-        assert_own_property(window[this.name], "prototype",
+        // "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(window[this.name], "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 as follows:
-        // "If A is not declared to inherit from another interface, then the
-        // value of the internal [[Prototype]] property of A is the Array
-        // prototype object ([ECMA-262], section 15.4.4) if the interface was
-        // declared with ArrayClass, or the Object prototype object otherwise
+        // 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).
-        // "Otherwise, A does inherit from another interface. The value of the
-        // internal [[Prototype]] property of A is the interface prototype
-        // object for the inherited interface."
-        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;
+        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 {
-            inherit_interface = 'Object';
-            inherit_interface_has_interface_object = true;
-        }
-        if (inherit_interface_has_interface_object) {
-            assert_own_property(window, inherit_interface,
-                                'should inherit from ' + inherit_interface + ', but window has no such property');
-            assert_own_property(window[inherit_interface], 'prototype',
-                                'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
-            assert_equals(Object.getPrototypeOf(window[this.name].prototype),
-                          window[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(window[this.name].prototype),
-                                inherit_interface + 'Prototype',
-                                'Class name for prototype of ' + this.name +
-                                '.prototype is not "' + inherit_interface + 'Prototype"');
+            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(window[this.name].prototype, this.name + "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(window[this.name].prototype), "[object " + this.name + "Prototype]",
+            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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        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(window[this.name], "prototype",
+        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(window[this.name].prototype, "constructor",
+        assert_own_property(self[this.name].prototype, "constructor",
                             this.name + '.prototype does not have own property "constructor"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "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(window[this.name].prototype.constructor, window[this.name],
+        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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        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(window[this.name], member.name);
+        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(window[this.name][member.name], constValue(member.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(window[this.name], member.name);
+        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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        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(window[this.name], "prototype",
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
-        assert_own_property(window[this.name].prototype, member.name);
-        assert_equals(window[this.name].prototype[member.name], constValue(member.value),
+        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(window[this.name], member.name);
+        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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
+        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(window[this.name], member.name,
+            assert_own_property(self[this.name], member.name,
                 "The interface object must have a property " +
                 format_value(member.name));
-        }
-        else
-        {
-            assert_true(member.name in window[this.name].prototype,
+        } 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() {
-                    window[this.name].prototype[member.name];
+                    self[this.name].prototype[member.name];
                 }.bind(this), "getting property on prototype object must throw TypeError");
             } else {
-                assert_equals(window[this.name].prototype[member.name], undefined,
+                assert_equals(self[this.name].prototype[member.name], undefined,
                               "getting property on prototype object must return undefined");
             }
-            do_interface_attribute_asserts(window[this.name].prototype, member);
+            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()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        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(window[this.name], "prototype",
+        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 prototypeOrInterfaceObject;
+        var memberHolderObject;
         if (member["static"]) {
-            assert_own_property(window[this.name], member.name,
-                    "interface prototype object missing static operation");
-            prototypeOrInterfaceObject = window[this.name];
-        }
-        else
-        {
-            assert_own_property(window[this.name].prototype, member.name,
+            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");
-            prototypeOrInterfaceObject = window[this.name].prototype;
+            memberHolderObject = self[this.name].prototype;
         }
 
-        var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
-        // "The property has attributes { [[Writable]]: true,
-        // [[Enumerable]]: true, [[Configurable]]: true }."
-        assert_false("get" in desc, "property has getter");
-        assert_false("set" in desc, "property has setter");
-        assert_true(desc.writable, "property is not writable");
-        assert_true(desc.enumerable, "property is not enumerable");
-        assert_true(desc.configurable, "property is not configurable");
-        // "The value of the property is a Function object whose
-        // behavior is as follows . . ."
-        assert_equals(typeof prototypeOrInterfaceObject[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."
-        //
-        // TODO: Doesn't handle overloading or variadic arguments.
-        assert_equals(prototypeOrInterfaceObject[member.name].length,
-            member.arguments.filter(function(arg) {
-                return !arg.optional;
-            }).length,
-            "property has wrong .length");
+        this.do_member_operation_asserts(memberHolderObject, member);
+    }.bind(this), this.name + " interface: operation " + member.name +
+    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
+    ")");
+};
 
-        // Make some suitable arguments
-        var args = member.arguments.map(function(arg) {
-            return create_suitable_object(arg.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");
 
-        // "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.
-        if (!member["static"]) {
+    // 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() {
-                window[this.name].prototype[member.name].apply(null, args);
+                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() {
-            window[this.name].prototype[member.name].apply({}, args);
+            memberHolderObject[member.name].apply({}, args);
         }, "calling operation with this = {} didn't throw TypeError");
-    }.bind(this), this.name + " interface: operation " + member.name +
-    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
-    ")");
-};
+    }
+}
 
 //@}
 IdlInterface.prototype.test_member_stringifier = function(member)
 //@{
 {
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        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(window[this.name], "prototype",
+        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 = window[this.name].prototype;
-        assert_own_property(window[this.name].prototype, "toString",
+        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_true(desc.writable, "property is not writable");
+        assert_equals(desc.writable, !stringifierUnforgeable,
+                      "property should be writable if and only if not unforgeable");
         assert_true(desc.enumerable, "property is not enumerable");
-        assert_true(desc.configurable, "property is not configurable");
+        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() {
-            window[this.name].prototype.toString.apply(null, []);
+            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() {
-            window[this.name].prototype.toString.apply({}, []);
+            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()
 //@{
 {
@@ -1448,26 +1239,32 @@ IdlInterface.prototype.test_members = fu
         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.has_extended_attribute("Unforgeable")) {
+            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) {
-                this.test_member_operation(member);
+                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;
@@ -1526,27 +1323,27 @@ IdlInterface.prototype.test_primary_inte
     // 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(window, this.name,
-                                "window does not have own property " + format_value(this.name));
-            assert_own_property(window[this.name], "prototype",
+            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),
-                          window[this.name].prototype,
+                          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()
@@ -1566,36 +1363,54 @@ IdlInterface.prototype.test_interface_of
 //@{
 {
     // 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.has_extended_attribute("Unforgeable"))
+        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");
-                do_interface_attribute_asserts(obj, member);
+                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"]) {
-                    assert_inherits(obj, member.name);
+                    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
@@ -1626,29 +1441,32 @@ IdlInterface.prototype.test_interface_of
         // 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"]) {
-                    assert_inherits(obj, member.name);
+                    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 < member.arguments.length; i++)
-                {
-                    if (member.arguments[i].optional)
-                    {
-                        break;
-                    }
+                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 +
@@ -1668,92 +1486,117 @@ IdlInterface.prototype.has_stringifier =
     if (this.base &&
         this.array.members[this.base].has_stringifier()) {
         return true;
     }
     return false;
 };
 
 //@}
-function do_interface_attribute_asserts(obj, member)
+IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member)
 //@{
 {
-    // "For each attribute defined on the interface, there must exist a
-    // corresponding property. If the attribute was declared with the
-    // [Unforgeable] extended attribute, then the property exists on every
-    // object that implements the interface.  Otherwise, it exists on the
-    // interface’s interface prototype object."
-    //
-    // This is called by test_self() with the prototype as obj, and by
-    // test_interface_of() with the object as obj.
+    // 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.has_extended_attribute("Unforgeable"))
+    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:
-    // "...
+    // 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(typeof desc.get, "function", "getter must be Function");
     assert_equals(desc.get.length, 0, "getter length must be 0");
-    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");
-    }
+
 
     // TODO: Test calling setter on the interface prototype (should throw
     // TypeError in most cases).
-    //
-    // "The attribute setter is undefined if the attribute is declared readonly
-    // and has neither a [PutForwards] nor a [Replaceable] extended attribute
-    // declared on it.  Otherwise, it is a Function object whose behavior when
-    // invoked is as follows:
-    // "...
-    // "The value of the Function object’s “length” property is the Number
-    // value 1."
     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");
-        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");
-        }
     }
 }
 //@}
 
 /// IdlInterfaceMember ///
 function IdlInterfaceMember(obj)
 //@{
 {
@@ -1765,16 +1608,18 @@ function IdlInterfaceMember(obj)
     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)
 //@{
--- a/dom/imptests/moz.build
+++ b/dom/imptests/moz.build
@@ -27,10 +27,9 @@ MOCHITEST_MANIFESTS += [
     'failures/html/html/semantics/forms/the-select-element/mochitest.ini',
     'failures/html/html/semantics/scripting-1/the-script-element/mochitest.ini',
     'failures/html/html/semantics/tabular-data/the-table-element/mochitest.ini',
     'failures/html/html/webappapis/atob/mochitest.ini',
     'failures/html/js/builtins/mochitest.ini',
     'failures/html/microdata/microdata-dom-api/mochitest.ini',
     'failures/html/typedarrays/mochitest.ini',
     'failures/webapps/WebStorage/tests/submissions/Infraware/mochitest.ini',
-    'failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini',
 ]
--- a/dom/imptests/testharness.css
+++ b/dom/imptests/testharness.css
@@ -9,21 +9,16 @@ html {
 }
 
 #log .error,
 #log .error a {
   color: white;
   background: red;
 }
 
-#log pre {
-  border: 1px solid black;
-  padding: 1em;
-}
-
 section#summary {
     margin-bottom:1em;
 }
 
 table#results {
     border-collapse:collapse;
     table-layout:fixed;
     width:100%;
--- a/dom/imptests/testharness.js
+++ b/dom/imptests/testharness.js
@@ -17,17 +17,18 @@ policies and contribution forms [3].
     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
+        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:
@@ -59,30 +60,73 @@ policies and contribution forms [3].
      * 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, is_same_origin) {
-                    if (is_same_origin && selector in w) {
+                function(w, same_origin) {
+                    if (same_origin) {
                         try {
-                            w[selector].apply(undefined, callback_args);
-                        } catch (e) {
-                            if (debug) {
-                                throw e;
+                            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, "*");
                     }
                 });
     };
@@ -136,51 +180,63 @@ policies and contribution forms [3].
                 });
     };
 
     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);
-            this_obj._dispatch("start_callback", [properties],
-                           { type: "start", properties: properties });
         });
+
         add_test_state_callback(function(test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("test_state_callback", [test],
-                               { type: "test_state", test: test.structured_clone() });
         });
+
         add_result_callback(function (test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("result_callback", [test],
-                               { type: "result", test: test.structured_clone() });
         });
+
         add_completion_callback(function (tests, harness_status) {
             this_obj.output_handler.show_results(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() });
         });
+        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");
@@ -354,18 +410,30 @@ policies and contribution forms [3].
     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") {
-                        this_obj._add_message_port(event.ports[0]);
-                        event.ports[0].start();
+                        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.
@@ -459,16 +527,22 @@ policies and contribution forms [3].
                     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') {
@@ -574,16 +648,17 @@ policies and contribution forms [3].
     function on_event(object, event, callback)
     {
         object.addEventListener(event, callback, false);
     }
 
     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');
 
     /*
      * Return a string truncated to the given length, with ... added at the end
      * if it was longer.
@@ -837,17 +912,17 @@ policies and contribution forms [3].
         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",
+                   "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]});
         }
     }
@@ -928,17 +1003,17 @@ policies and contribution forms [3].
          * 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", description,
+               "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)
     {
         /*
@@ -1120,32 +1195,38 @@ policies and contribution forms [3].
                 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
+                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 ||
-               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {
+               (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?
@@ -1209,16 +1290,17 @@ policies and contribution forms [3].
         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);
     }
 
@@ -1245,16 +1327,17 @@ policies and contribution forms [3].
             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;
@@ -1277,25 +1360,20 @@ policies and contribution forms [3].
         }
 
         try {
             return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
         } catch (e) {
             if (this.phase >= this.phases.HAS_RESULT) {
                 return;
             }
-            var message = (typeof e === "object" && e !== null) ? e.message : e;
-            if (typeof e.stack != "undefined" && typeof e.message == "string") {
-                //Try to make it more informative for some exceptions, at least
-                //in Gecko and WebKit.  This results in a stack dump instead of
-                //just errors like "Cannot read property 'parentNode' of null"
-                //or "root is null".  Makes it a lot longer, of course.
-                message += "(stack: " + e.stack + ")";
-            }
-            this.set_status(this.FAIL, message);
+            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;
@@ -1351,20 +1429,21 @@ policies and contribution forms [3].
             var this_obj = this;
             this.timeout_id = setTimeout(function()
                                          {
                                              this_obj.timeout();
                                          }, this.timeout_length);
         }
     };
 
-    Test.prototype.set_status = function(status, message)
+    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();
@@ -1411,32 +1490,33 @@ policies and contribution forms [3].
         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) {
+                (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;
     }
 
@@ -1450,25 +1530,34 @@ policies and contribution forms [3].
         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)) {
-            // 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]);
+            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
@@ -1486,17 +1575,18 @@ policies and contribution forms [3].
     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
+                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) {
@@ -1514,16 +1604,17 @@ policies and contribution forms [3].
         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();
         }
     };
 
@@ -1536,16 +1627,17 @@ policies and contribution forms [3].
     /*
      * Harness
      */
 
     function TestsStatus()
     {
         this.status = null;
         this.message = null;
+        this.stack = null;
     }
 
     TestsStatus.statuses = {
         OK:0,
         ERROR:1,
         TIMEOUT:2
     };
 
@@ -1553,17 +1645,18 @@ policies and contribution forms [3].
 
     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
+                message:msg,
+                stack:this.stack
             }, TestsStatus.statuses);
         }
         return this._structured_clone;
     };
 
     function Tests()
     {
         this.tests = [];
@@ -1643,16 +1736,17 @@ policies and contribution forms [3].
         }
 
         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");
@@ -1806,31 +1900,52 @@ policies and contribution forms [3].
     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)
-    {
+    function add_result_callback(callback) {
         tests.test_done_callbacks.push(callback);
     }
 
-    function add_completion_callback(callback)
-    {
-       tests.all_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;
@@ -2000,16 +2115,19 @@ policies and contribution forms [3].
                                                 ["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)) {
@@ -2097,16 +2215,19 @@ policies and contribution forms [3].
                 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.";
@@ -2292,21 +2413,44 @@ policies and contribution forms [3].
                                    error, substitutions);
             throw new AssertionError(msg);
         }
     }
 
     function AssertionError(message)
     {
         this.message = message;
+        this.stack = this.get_stack();
     }
 
-    AssertionError.prototype.toString = function() {
-        return this.message;
-    };
+    AssertionError.prototype = Object.create(Error.prototype);
+
+    AssertionError.prototype.get_stack = function() {
+        var stack = new Error().stack;
+        if (!stack) {
+            try {
+                throw new Error();
+            } catch (e) {
+                stack = e.stack;
+            }
+        }
+        var lines = stack.split("\n");
+        var rv = [];
+        var re = /\/resources\/testharness\.js/;
+        var i = 0;
+        // Fire remove any preamble that doesn't match the regexp
+        while (!re.test(lines[i])) {
+            i++
+        }
+        // Then remove top frames in testharness.js itself
+        while (re.test(lines[i])) {
+            i++
+        }
+        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]);
             }
         }
@@ -2435,23 +2579,23 @@ policies and contribution forms [3].
     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;
             }
-            var message = e.message;
-            test.set_status(test.FAIL, message);
+            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: