diff --git a/examples/apisample.htm b/examples/apisample.htm
index 504a343..182954e 100644
--- a/examples/apisample.htm
+++ b/examples/apisample.htm
@@ -134,6 +134,41 @@
Sample HTML5 API Tests
assert_throws("TEST_ERR", function() {throw e});
}, "Test assert_throws with non-DOM-exception expected to Fail");
+ test(function()
+ {
+ var o = Object.create(null, {
+ "readonly": {value: NaN, writable: false}
+ });
+
+ assert_readonly(Object.create(o), "readonly", "must succeed for inherited properties.");
+ }, "Test assert_readonly with non-writable property.");
+
+ test(function()
+ {
+ var o = Object.create(null, {
+ "readonly": { get: function() { throw new Error('Access is denied.'); }}
+ });
+
+ assert_readonly(o, "readonly", "must anticipate a get accessor that throws.");
+ }, "Test assert_readonly with non-settable property.");
+
+ test(function()
+ {
+ var o = { writable: NaN };
+ assert_readonly(o, "writable", "expected failure.");
+ }, "Test assert_readonly with writable property expected to fail.");
+
+ test(function()
+ {
+ var o = Object.create(null, {
+ "settable": {
+ get: function() { return NaN; },
+ set: function() {}
+ }});
+
+ assert_readonly(o, "settable", "expected failure.");
+ }, "Test assert_readonly with settable property expected to fail.");
+
var t = async_test("Test step_func")
setTimeout(
t.step_func(
diff --git a/testharness.js b/testharness.js
index 7920ccd..ff2e4ca 100644
--- a/testharness.js
+++ b/testharness.js
@@ -1128,20 +1128,55 @@ policies and contribution forms [3].
expose(_assert_inherits("assert_inherits"), "assert_inherits");
expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
- function assert_readonly(object, property_name, description)
- {
- var initial_value = object[property_name];
- try {
- //Note that this can have side effects in the case where
- //the property has PutForwards
- object[property_name] = initial_value + "a"; //XXX use some other value here?
- assert(same_value(object[property_name], initial_value),
- "assert_readonly", description,
- "changing property ${p} succeeded",
- {p:property_name});
- } finally {
- object[property_name] = initial_value;
- }
+ function getPropertyDescriptor(object, property_name) {
+ for (; object; object = Object.getPrototypeOf(object)) {
+ var descriptor = Object.getOwnPropertyDescriptor(object, property_name);
+ if (descriptor) {
+ return descriptor;
+ }
+ }
+ return undefined;
+ }
+
+ function assert_readonly(object, property_name, description) {
+ // Ensure that the property is non-writable and has no set accessor.
+ var descriptor = getPropertyDescriptor(object, property_name);
+ assert(!!descriptor, "assert_readonly", description, "property ${p} not defined on object.", {p:property_name});
+ assert(!descriptor.writable, "assert_readonly", description, "property ${p} must not be writable.", {p:property_name});
+ assert(!descriptor.set, "assert_readonly", description, "property ${p} must not define a set accessor.", {p:property_name});
+
+ // Attempt to retrieve the current proprety value to use as one of our test values below.
+ var maybe_initial_value = "initial value unknown";
+ try {
+ maybe_initial_value = object[property_name];
+ } catch (e) {
+ // Swallow any errors. It's valid for a property to throw (e.g., 'Access is denied').
+ }
+
+ // Ensure that setting the property in 'strict mode' fails with a TypeError. We attempt
+ // setting a few values that possibly involve special handling in host implementations.
+ [maybe_initial_value, undefined, null, NaN]
+ .forEach(function (value) {
+ 'use strict';
+
+ var thrown = undefined;
+ try {
+ object[property_name] = value;
+ } catch (e) {
+ thrown = e;
+ }
+
+ // Ensure a TypeError was thrown.
+ assert(!!thrown, "assert_readonly", description,
+ "setting property ${p} to ${v} must throw an error when in strict mode, but did not.",
+ {p:property_name, v:value});
+ assert(thrown.name === "TypeError", "assert_readonly", description,
+ "setting property ${p} to ${v} must throw a TypeError when in strict mode, but threw ${e}.",
+ {p:property_name, v:value, e:thrown.name});
+
+ // Note: While it would be unusual, a readonly property can conceivably change value.
+ // Therefore, we do not assert that the current value matches the initial value.
+ });
}
expose(assert_readonly, "assert_readonly");