Update web-platform-tests to revision 0d318188757a9c996e20b82db201fd04de5aa255

This commit is contained in:
James Graham 2015-03-27 09:15:38 +00:00
parent b2a5225831
commit 1a81b18b9f
12321 changed files with 544385 additions and 6 deletions

View file

@ -0,0 +1,3 @@
ROBIN-TODO.txt
scratch
node_modules

View file

@ -0,0 +1,3 @@
[submodule "webidl2"]
path = webidl2
url = https://github.com/darobin/webidl2.js.git

View file

@ -0,0 +1,2 @@
# make tests that use utf-16 not inherit the encoding for testharness.js et. al.
AddCharset utf-8 .css .js

View file

@ -0,0 +1,3 @@
importScripts("testharness.js");
throw new Error("This failure is expected.");

View file

@ -0,0 +1,34 @@
importScripts("testharness.js");
test(
function(test) {
assert_true(true, "True is true");
},
"Worker test that completes successfully");
test(
function(test) {
assert_true(false, "Failing test");
},
"Worker test that fails ('FAIL')");
async_test(
function(test) {
assert_true(true, "True is true");
},
"Worker test that times out ('TIMEOUT')");
async_test("Worker test that doesn't run ('NOT RUN')");
async_test(
function(test) {
self.setTimeout(
function() {
test.done();
},
0);
},
"Worker async_test that completes successfully");
// An explicit done() is required for dedicated and shared web workers.
done();

View file

@ -0,0 +1,175 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Sample HTML5 API Tests</title>
<meta name="timeout" content="6000">
</head>
<body onload="load_test_attr.done()">
<h1>Sample HTML5 API Tests</h1>
<div id="log"></div>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
setup_run = false;
setup(function() {
setup_run = true;
});
test(function() {assert_true(setup_run)}, "Setup function ran");
// Two examples for testing events from handler and attributes
var load_test_event = async_test("window onload event fires when set from the handler");
function windowLoad()
{
load_test_event.done();
}
on_event(window, "load", windowLoad);
// see the body onload below
var load_test_attr = async_test("body element fires the onload event set from the attribute");
</script>
<script>
function bodyElement()
{
assert_equals(document.body, document.getElementsByTagName("body")[0]);
}
test(bodyElement, "document.body should be the first body element in the document");
test(function() {
assert_equals(1,1);
assert_equals(NaN, NaN, "NaN case");
assert_equals(0, 0, "Zero case");
}, "assert_equals tests")
test(function() {
assert_equals(-0, 0, "Zero case");
}, "assert_equals tests expected to fail")
test(function() {
assert_not_equals({}, {}, "object case");
assert_not_equals(-0, 0, "Zero case");
}, "assert_not_equals tests")
function testAssertPass()
{
assert_true(true);
}
test(testAssertPass, "assert_true expected to pass");
function testAssertFalse()
{
assert_true(false, "false should not be true");
}
test(testAssertFalse, "assert_true expected to fail");
function basicAssertArrayEquals()
{
assert_array_equals([1, NaN], [1, NaN], "[1, NaN] is equal to [1, NaN]");
}
test(basicAssertArrayEquals, "basic assert_array_equals test");
function basicAssertObjectEquals()
{
assert_object_equals([1, 2, [1, 2]], { 0: 1, 1: 2, 2: { 0: 1, 1: 2} }, "array is equal to object")
}
test(basicAssertObjectEquals, "basic assert_object_equals test");
function basicAssertApproxEquals()
{
assert_approx_equals(10, 11, 1, "10 is approximately (+/- 1) 11")
}
test(basicAssertApproxEquals, "basic assert_approx_equals test");
function basicAssertLessThan()
{
assert_less_than(10, 11, "10 is less than 11")
}
test(basicAssertApproxEquals, "basic assert_less_than test");
function basicAssertGreaterThan()
{
assert_greater_than(10, 11, "10 is not greater than 11");
}
test(basicAssertGreaterThan, "assert_greater_than expected to fail");
function basicAssertGreaterThanEqual()
{
assert_greater_than_equal(10, 10, "10 is greater than or equal to 10")
}
test(basicAssertGreaterThanEqual, "basic assert_greater_than_equal test");
function basicAssertLessThanEqual()
{
assert_greater_than_equal('10', 10, "'10' is not a number")
}
test(basicAssertLessThanEqual, "assert_less_than_equal expected to fail");
function testAssertInherits() {
var A = function(){this.a = "a"}
A.prototype = {b:"b"}
var a = new A();
assert_exists(a, "a");
assert_not_exists(a, "b");
assert_inherits(a, "b");
}
test(testAssertInherits, "test for assert[_not]_exists and insert_inherits")
test(function()
{
var a = document.createElement("a")
var b = document.createElement("b")
assert_throws("NOT_FOUND_ERR", function(){a.removeChild(b)});
}, "Test throw DOM exception")
test(function()
{
var a = document.createTextNode("a")
var b = document.createElement("b")
assert_throws("NOT_FOUND_ERR", function(){a.appendChild(b)});
}, "Test throw DOM exception expected to fail")
test(function()
{
var e = {code:0, name:"TEST_ERR", TEST_ERR:0}
assert_throws("TEST_ERR", function() {throw e});
}, "Test assert_throws with non-DOM-exception expected to Fail");
var t = async_test("Test step_func")
setTimeout(
t.step_func(
function () {
assert_true(true); t.done();
}), 0);
async_test(function(t) {
setTimeout(t.step_func(function (){assert_true(true); t.done();}), 0);
}, "Test async test with callback");
async_test(function() {
setTimeout(this.step_func(function (){assert_true(true); this.done();}), 0);
}, "Test async test with callback and `this` obj.");
async_test("test should timeout (fail) with the default of 2 seconds").step(function(){});
async_test("test should timeout (fail) with a custom set timeout value of 1 second",
{timeout:1000}).step(function(){});
async_test("async test that is never started, should have status Not Run", {timeout:1000});
test(function(t) {
window.global = 1;
t.add_cleanup(function() {delete window.global});
assert_equals(window.global, 1);
},
"Test that defines a global and cleans it up");
test(function() {assert_equals(window.global, undefined)},
"Test that cleanup handlers from previous test ran");
</script>
</body>
</html>

View file

@ -0,0 +1,119 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Async Tests and Promises</title>
</head>
<body>
<h1>Async Tests and Promises</h1>
<p>This test assumes ECMAScript 6 Promise support. Some failures are expected.</p>
<div id="log"></div>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
test(function() {
var p = new Promise(function(resolve, reject) {});
assert_true('then' in p);
assert_equals(typeof Promise.resolve, 'function');
assert_equals(typeof Promise.reject, 'function');
}, "Promises are supported in your browser");
(function() {
var t = async_test("Promise resolution");
t.step(function() {
Promise.resolve('x').then(
t.step_func(function(value) {
assert_equals(value, 'x');
t.done();
}),
t.unreached_func('Promise should not reject')
);
});
}());
(function() {
var t = async_test("Promise rejection");
t.step(function() {
Promise.reject(Error('fail')).then(
t.unreached_func('Promise should reject'),
t.step_func(function(reason) {
assert_true(reason instanceof Error);
assert_equals(reason.message, 'fail');
t.done();
})
);
});
}());
(function() {
var t = async_test("Promises resolution chaining");
t.step(function() {
var resolutions = [];
Promise.resolve('a').then(
t.step_func(function(value) {
resolutions.push(value);
return 'b';
})
).then(
t.step_func(function(value) {
resolutions.push(value);
return 'c';
})
).then(
t.step_func(function(value) {
resolutions.push(value);
assert_array_equals(resolutions, ['a', 'b', 'c']);
t.done();
})
).catch(
t.unreached_func('promise should not have rejected')
);
});
}());
(function() {
var t = async_test("Use of step_func with Promises");
t.step(function() {
var resolutions = [];
Promise.resolve('x').then(
t.step_func_done(),
t.unreached_func('Promise should not have rejected')
);
});
}());
(function() {
var t = async_test("Promises and test assertion failures (should fail)");
t.step(function() {
var resolutions = [];
Promise.resolve('x').then(
t.step_func(function(value) {
assert_true(false, 'This failure is expected');
})
).then(
t.unreached_func('Promise should not have resolved')
).catch(
t.unreached_func('Promise should not have rejected')
);
});
}());
(function() {
var t = async_test("Use of unreached_func with Promises (should fail)");
t.step(function() {
var resolutions = [];
var r;
var p = new Promise(function(resolve, reject) {
// Reject instead of resolve, to demonstrate failure.
reject(123);
});
p.then(
function(value) {
assert_equals(value, 123, 'This should not actually happen');
},
t.unreached_func('This failure is expected')
);
});
}());
</script>

View file

@ -0,0 +1,99 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Example with iframe that notifies containing document via callbacks</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
</head>
<body onload="start_test_in_iframe()">
<h1>Callbacks From Tests Running In An IFRAME</h1>
<p>A test is run inside an <tt>iframe</tt> with a same origin document. The
containing document should receive callbacks as the tests progress inside the
<tt>iframe</tt>. A single passing test is expected in the summary below.
<div id="log"></div>
<script>
var callbacks = [];
var START = 1
var TEST_STATE = 2
var RESULT = 3
var COMPLETION = 4
var test_complete = false;
setup({explicit_done: true});
// The following callbacks are called for tests in this document as well as the
// tests in the IFRAME. Currently, callbacks invoked from this document and any
// child document are indistinguishable from each other.
function start_callback(properties) {
callbacks.push(START);
}
function test_state_callback(test) {
callbacks.push(TEST_STATE);
}
function result_callback(test) {
callbacks.push(RESULT);
}
function completion_callback(tests, status) {
if (test_complete) {
return;
}
test_complete = true;
callbacks.push(COMPLETION);
verify_received_callbacks();
done();
}
function verify_received_callbacks() {
var copy_of_callbacks = callbacks.slice(0);
// Note that you can't run test assertions directly in a callback even if
// this is a file test. When the callback is invoked from a same-origin child
// page, the callstack reaches into the calling child document. Any
// exception thrown in a callback will be handled by the child rather than
// this document.
test(
function() {
// callbacks list should look like:
// START 1*(TEST_STATE) RESULT COMPLETION
assert_equals(copy_of_callbacks.shift(), START,
"The first received callback should be 'start_callback'.");
assert_equals(copy_of_callbacks.shift(), TEST_STATE,
"'test_state_callback' should be received before any " +
"result or completion callbacks.");
while(copy_of_callbacks.length > 0) {
var callback = copy_of_callbacks.shift();
if (callback != TEST_STATE) {
copy_of_callbacks.unshift(callback);
break;
}
}
assert_equals(copy_of_callbacks.shift(), RESULT,
"'test_state_callback' should be followed by 'result_callback'.");
assert_equals(copy_of_callbacks.shift(), COMPLETION,
"Final 'result_callback' should be followed by 'completion_callback'.");
assert_equals(copy_of_callbacks.length, 0,
"'completion_callback' should be the last callback.");
});
}
function start_test_in_iframe() {
// This document is going to clear any received callbacks and maintain
// radio silence until the test in the iframe runs to completion. The
// completion_callback() will then complete the testing on this document.
callbacks.length = 0;
var iframe = document.createElement("iframe");
// apisample6.html has a single test.
iframe.src = "apisample6.html";
iframe.style.setProperty("display", "none");
document.getElementById("target").appendChild(iframe);
}
</script>
<div id="target">
</div>
</body>

View file

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Example with iframe that notifies containing document via cross document messaging</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
</head>
<body>
<h1>Notifications From Tests Running In An IFRAME</h1>
<p>A test is run inside an <tt>iframe</tt> with a same origin document. The
containing document should receive messages via <tt>postMessage</tt>/
<tt>onmessage</tt> as the tests progress inside the <tt>iframe</tt>. A single
passing test is expected in the summary below.
</p>
<div id="log"></div>
<script>
var t = async_test("Containing document receives messages");
var start_received = false;
var result_received = false;
var completion_received = false;
// These are the messages that are expected to be seen while running the tests
// in the IFRAME.
var expected_messages = [
t.step_func(
function(message) {
assert_equals(message.data.type, "start");
assert_own_property(message.data, "properties");
}),
t.step_func(
function(message) {
assert_equals(message.data.type, "test_state");
assert_equals(message.data.test.status, message.data.test.NOTRUN);
}),
t.step_func(
function(message) {
assert_equals(message.data.type, "result");
assert_equals(message.data.test.status, message.data.test.PASS);
}),
t.step_func(
function(message) {
assert_equals(message.data.type, "complete");
assert_equals(message.data.tests.length, 1);
assert_equals(message.data.tests[0].status,
message.data.tests[0].PASS);
assert_equals(message.data.status.status, message.data.status.OK);
t.done();
}),
t.unreached_func("Too many messages received")
];
on_event(window,
"message",
function(message) {
var handler = expected_messages.shift();
handler(message);
});
</script>
<iframe src="apisample6.html" style="display:none">
<!-- apisample6 implements a file_is_test test. -->
</iframe>
</body>

View file

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Promise Tests</title>
</head>
<body>
<h1>Promise Tests</h1>
<p>This test demonstrates the use of <tt>promise_test</tt>. Assumes ECMAScript 6
Promise support. Some failures are expected.</p>
<div id="log"></div>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
test(
function() {
var p = new Promise(function(resolve, reject){});
assert_true("then" in p);
assert_equals(typeof Promise.resolve, "function");
assert_equals(typeof Promise.reject, "function");
},
"Promises are supported in your browser");
promise_test(
function() {
return Promise.resolve("x")
.then(
function(value) {
assert_equals(value,
"x",
"Fulfilled promise should pass result to " +
"fulfill reaction.");
});
},
"Promise fulfillment with result");
promise_test(
function(t) {
return Promise.reject(new Error("fail"))
.then(t.unreached_func("Promise should reject"),
function(reason) {
assert_true(
reason instanceof Error,
"Rejected promise should pass reason to fulfill reaction.");
assert_equals(
reason.message,
"fail",
"Rejected promise should pass reason to reject reaction.");
});
},
"Promise rejection with result");
promise_test(
function() {
var resolutions = [];
return Promise.resolve("a")
.then(
function(value) {
resolutions.push(value);
return "b";
})
.then(
function(value) {
resolutions.push(value);
return "c";
})
.then(
function(value) {
resolutions.push(value);
assert_array_equals(resolutions, ["a", "b", "c"]);
});
},
"Chain of promise resolutions");
promise_test(
function(t) {
var resolutions = [];
return Promise.resolve("x")
.then(
function(value) {
assert_true(false, "Expected failure.");
})
.then(t.unreached_func("UNEXPECTED FAILURE: Promise should not have resolved."));
},
"Assertion failure in a fulfill reaction (should FAIL with an expected failure)");
promise_test(
function(t) {
return new Promise(
function(resolve, reject) {
reject(123);
})
.then(t.unreached_func("UNEXPECTED FAILURE: Fulfill reaction reached after rejection."),
t.unreached_func("Expected failure."));
},
"unreached_func as reactor (should FAIL with an expected failure)");
promise_test(
function() {
return true;
},
"promise_test with function that doesn't return a Promise");
promise_test(function(){},
"promise_test with function that doesn't return anything");
promise_test(
function() {
return Promise.reject("Expected rejection");
},
"promise_test with unhandled rejection (should FAIL)");
promise_test(
function() {
return Promise.resolve(10)
.then(
function(value) {
throw Error("Expected exception.");
});
},
"promise_test with unhandled exception in fulfill reaction (should FAIL)");
promise_test(
function(t) {
return Promise.reject(10)
.then(
t.unreached_func("UNEXPECTED FAILURE: Fulfill reaction reached after rejection."),
function(value) {
throw Error("Expected exception.");
});
},
"promise_test with unhandled exception in reject reaction (should FAIL)");
</script>

View file

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Dedicated Worker Tests</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
</head>
<body>
<h1>Dedicated Web Worker Tests</h1>
<p>Demonstrates running <tt>testharness</tt> based tests inside a dedicated web worker.
<p>The test harness is expected to fail due to an uncaught exception in one worker.</p>
<div id="log"></div>
<script>
test(function(t) {
assert_true("Worker" in self, "Browser should support Workers");
},
"Browser supports Workers");
fetch_tests_from_worker(new Worker("apisample-worker.js"));
fetch_tests_from_worker(new Worker("apisample-error-worker.js"));
test(function(t) {
assert_false(false, "False is false");
},
"Test running on main document.");
</script>
</body>

View file

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Example with a shared worker</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
</head>
<body>
<h1>Shared Web Worker Tests</h1>
<p>Demonstrates running <tt>testharness</tt> based tests inside a shared worker.
<p>The test harness should time out due to one of the tests in the worker timing out.
<p>This test assumes that the browser supports <a href="http://www.w3.org/TR/workers/#shared-workers-and-the-sharedworker-interface">shared web workers</a>.
<div id="log"></div>
<script>
test(
function(t) {
assert_true("SharedWorker" in self,
"Browser should support SharedWorkers");
},
"Browser supports SharedWorkers");
fetch_tests_from_worker(new SharedWorker("apisample-worker.js",
"My shared worker"));
</script>
</body>

View file

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Example with a service worker</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
</head>
<body>
<h1>Service Worker Tests</h1>
<p>Demonstrates running <tt>testharness</tt> based tests inside a service worker.
<p>The test harness should time out due to one of the tests inside the worker timing out.
<p>This test assumes that the browser supports <a href="http://www.w3.org/TR/service-workers/">ServiceWorkers</a>.
<div id="log"></div>
<script>
test(
function(t) {
assert_true("serviceWorker" in navigator,
"navigator.serviceWorker exists");
},
"Browser supports ServiceWorker");
promise_test(
function() {
// Since the service worker registration could be in an indeterminate
// state (due to, for example, a previous test run failing), we start by
// unregstering our service worker and then registering it again.
var scope = "/service-worker-scope";
var worker_url = "apisample-worker.js";
return navigator.serviceWorker.register(worker_url, {scope: scope})
.then(
function(registration) {
return registration.unregister();
})
.then(
function() {
return navigator.serviceWorker.register(worker_url, {scope: scope});
})
.then(
function(registration) {
add_completion_callback(
function() {
registration.unregister();
});
return new Promise(
function(resolve) {
registration.addEventListener("updatefound",
function() {
resolve(registration.installing);
});
});
})
.then(
function(worker) {
fetch_tests_from_worker(worker);
});
},
"Register ServiceWorker");
</script>
</body>

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Sample HTML5 API Tests</title>
</head>
<body onload="load_test_attr.done()">
<h1>Sample HTML5 API Tests</h1>
<p>There should be two results</p>
<div id="log"></div>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
setup({explicit_done:true})
test(function() {assert_true(true)}, "Test defined before onload");
onload = function() {test(function (){assert_true(true)}, "Test defined after onload");
done();
}
</script>

View file

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Sample HTML5 API Tests</title>
</head>
<script src="testharness.js"></script>
<body onload="load_test_attr.done()">
<h1>Sample HTML5 API Tests</h1>
<div id="log"></div>
<script>
setup({explicit_timeout:true});
var t = async_test("This test should give a status of 'Not Run' without a delay");
timeout();
</script>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Harness Handling Uncaught Exception</title>
</head>
<script src="testharness.js"></script>
<body>
<h1>Harness Handling Uncaught Exception</h1>
<div id="log"></div>
<script>
var t = async_test("This should show a harness status of 'Error' and a test status of 'Not Run'");
throw new Error("Example Error");
</script>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Harness Ignoring Uncaught Exception</title>
</head>
<script src="testharness.js"></script>
<body>
<h1>Harness Ignoring Uncaught Exception</h1>
<div id="log"></div>
<script>
setup({allow_uncaught_exception:true});
var t = async_test("setup({allow_uncaught_exception:true}) should allow tests to pass even if there is an exception");
onerror = t.step_func(function() {t.done()});
throw new Error("Example Error");
</script>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<title>Example with file_is_test</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
onload = function() {
assert_true(true);
done();
}
</script>

View file

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<title>Example with file_is_test (should fail)</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
onload = function() {
assert_true(false);
done();
}
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<title>Example single page test with no body</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
assert_true(true);
done();
</script>

View file

@ -0,0 +1,7 @@
<!DOCTYPE HTML>
<title>Example single page test with no asserts</title>
<script src="testharness.js"></script>
<script src="testharnessreport.js"></script>
<script>
done();
</script>

View file

@ -0,0 +1,548 @@
## Introduction ##
testharness.js provides a framework for writing testcases. It is intended to
provide a convenient API for making common assertions, and to work both
for testing synchronous and asynchronous DOM features in a way that
promotes clear, robust, tests.
## Basic Usage ##
The test harness script can be used from HTML or SVG documents and web worker
scripts.
From an HTML or SVG document, start by importing both `testharness.js` and
`testharnessreport.js` scripts into the document:
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
Refer to the [Web Workers](#web-workers) section for details and an example on
testing within a web worker.
Within each file one may define one or more tests. Each test is atomic in the
sense that a single test has a single result (`PASS`/`FAIL`/`TIMEOUT`/`NOTRUN`).
Within each test one may have a number of asserts. The test fails at the first
failing assert, and the remainder of the test is (typically) not run.
If the file containing the tests is a HTML file, a table containing the test
results will be added to the document after all tests have run. By default this
will be added to a `div` element with `id=log` if it exists, or a new `div`
element appended to `document.body` if it does not.
NOTE: By default tests must be created before the load event fires. For ways
to create tests after the load event, see "Determining when all tests
are complete", below.
## Synchronous Tests ##
To create a synchronous test use the test() function:
test(test_function, name, properties)
`test_function` is a function that contains the code to test. For example a
trivial passing test would be:
test(function() {assert_true(true)}, "assert_true with true")
The function passed in is run in the `test()` call.
`properties` is a javascript object for passing extra options to the
test. Currently it is only used to provide test-specific
metadata, as described in the [metadata](#metadata) section below.
## Asynchronous Tests ##
Testing asynchronous features is somewhat more complex since the result of
a test may depend on one or more events or other callbacks. The API provided
for testing these features is indended to be rather low-level but hopefully
applicable to many situations.
To create a test, one starts by getting a Test object using async_test:
async_test(name, properties)
e.g.
var t = async_test("Simple async test")
Assertions can be added to the test by calling the step method of the test
object with a function containing the test assertions:
t.step(function() {assert_true(true)});
When all the steps are complete, the done() method must be called:
t.done();
As a convenience, async_test can also takes a function as first argument.
This function is called with the test object as both its `this` object and
first argument. The above example can be rewritten as:
async_test(function(t) {
object.some_event = function() {
t.step(function (){assert_true(true); t.done();});
};
}, "Simple async test");
which avoids cluttering the global scope with references to async
tests instances.
The properties argument is identical to that for `test()`.
In many cases it is convenient to run a step in response to an event or a
callback. A convenient method of doing this is through the step_func method
which returns a function that, when called runs a test step. For example
object.some_event = t.step_func(function(e) {assert_true(e.a)});
For asynchronous callbacks that should never execute, `unreached_func` can
be used. For example:
object.some_event = t.unreached_func("some_event should not fire");
## Promise Tests ##
`promise_test` can be used to test APIs that are based on Promises:
promise_test(test_function, name, properties)
`test_function` is a function that receives a test as an argument and returns a
promise. The test completes when the returned promise resolves. The test fails
if the returned promise rejects.
E.g.:
function foo() {
return Promise.resolve("foo");
}
promise_test(function() {
return foo()
.then(function(result) {
assert_equals(result, "foo", "foo should return 'foo'");
});
}, "Simple example");
In the example above, `foo()` returns a Promise that resolves with the string
"foo". The `test_function` passed into `promise_test` invokes `foo` and attaches
a resolve reaction that verifies the returned value.
Note that in the promise chain constructed in `test_function` assertions don't
need to wrapped in `step` or `step_func` calls.
`promise_rejects` can be used to test Promises that need to reject:
promise_rejects(test_object, code, promise)
The `code` argument is equivalent to the same argument to the `assert_throws`
function.
Here's an example where the `bar()` function returns a Promise that rejects
with a TypeError:
function bar() {
return Promise.reject(new TypeError());
}
promise_test(function(t) {
return promise_rejects(t, new TypeError(), bar);
}, "Another example");
## Single Page Tests ##
Sometimes, particularly when dealing with asynchronous behaviour,
having exactly one test per page is desirable, and the overhead of
wrapping everything in functions for isolation becomes
burdensome. For these cases `testharness.js` support "single page
tests".
In order for a test to be interpreted as a single page test, the
it must simply not call `test()` or `async_test()` anywhere on the page, and
must call the `done()` function to indicate that the test is complete. All
the `assert_*` functions are avaliable as normal, but are called without
the normal step function wrapper. For example:
<!doctype html>
<title>Example single-page test</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
assert_equals(document.body, document.getElementsByTagName("body")[0])
done()
</script>
The test title for sinple page tests is always taken from `document.title`.
## Making assertions ##
Functions for making assertions start `assert_`. The full list of
asserts avaliable is documented in the [asserts](#asserts) section
below.. The general signature is
assert_something(actual, expected, description)
although not all assertions precisely match this pattern e.g. `assert_true`
only takes `actual` and `description` as arguments.
The description parameter is used to present more useful error messages when
a test fails
NOTE: All asserts must be located in a `test()` or a step of an
`async_test()`, unless the test is a single page test. Asserts outside
these places won't be detected correctly by the harness and may cause
unexpected exceptions that will lead to an error in the harness.
## Cleanup ##
Occasionally tests may create state that will persist beyond the test itself.
In order to ensure that tests are independent, such state should be cleaned
up once the test has a result. This can be achieved by adding cleanup
callbacks to the test. Such callbacks are registered using the `add_cleanup`
function on the test object. All registered callbacks will be run as soon as
the test result is known. For example
test(function() {
window.some_global = "example";
this.add_cleanup(function() {delete window.some_global});
assert_true(false);
});
## Harness Timeout ##
The overall harness admits two timeout values `"normal"` (the
default) and `"long"`, used for tests which have an unusually long
runtime. After the timeout is reached, the harness will stop
waiting for further async tests to complete. By default the
timeouts are set to 10s and 60s, respectively, but may be changed
when the test is run on hardware with different performance
characteristics to a common desktop computer. In order to opt-in
to the longer test timeout, the test must specify a meta element:
<meta name="timeout" content="long">
Occasionally tests may have a race between the harness timing out and
a particular test failing; typically when the test waits for some event
that never occurs. In this case it is possible to use `test.force_timeout()`
in place of `assert_unreached()`, to immediately fail the test but with a
status of `TIMEOUT`. This should only be used as a last resort when it is
not possible to make the test reliable in some other way.
## Setup ##
Sometimes tests require non-trivial setup that may fail. For this purpose
there is a `setup()` function, that may be called with one or two arguments.
The two argument version is:
setup(func, properties)
The one argument versions may omit either argument.
func is a function to be run synchronously. `setup()` becomes a no-op once
any tests have returned results. Properties are global properties of the test
harness. Currently recognised properties are:
`explicit_done` - Wait for an explicit call to done() before declaring all
tests complete (see below; implicitly true for single page tests)
`output_document` - The document to which results should be logged. By default
this is the current document but could be an ancestor document in some cases
e.g. a SVG test loaded in an HTML wrapper
`explicit_timeout` - disable file timeout; only stop waiting for results
when the `timeout()` function is called (typically for use when integrating
with some existing test framework that has its own timeout mechanism).
`allow_uncaught_exception` - don't treat an uncaught exception as an error;
needed when e.g. testing the `window.onerror` handler.
`timeout_multiplier` - Multiplier to apply to per-test timeouts.
## Determining when all tests are complete ##
By default the test harness will assume there are no more results to come
when:
1. There are no `Test` objects that have been created but not completed
2. The load event on the document has fired
This behaviour can be overridden by setting the `explicit_done` property to
true in a call to `setup()`. If `explicit_done` is true, the test harness will
not assume it is done until the global `done()` function is called. Once `done()`
is called, the two conditions above apply like normal.
Dedicated and shared workers don't have an event that corresponds to the `load`
event in a document. Therefore these worker tests always behave as if the
`explicit_done` property is set to true. Service workers depend on the
[install](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-install-event)
event which is fired following the completion of [running the
worker](https://html.spec.whatwg.org/multipage/workers.html#run-a-worker).
## Generating tests ##
There are scenarios in which is is desirable to create a large number of
(synchronous) tests that are internally similar but vary in the parameters
used. To make this easier, the `generate_tests` function allows a single
function to be called with each set of parameters in a list:
generate_tests(test_function, parameter_lists, properties)
For example:
generate_tests(assert_equals, [
["Sum one and one", 1+1, 2],
["Sum one and zero", 1+0, 1]
])
Is equivalent to:
test(function() {assert_equals(1+1, 2)}, "Sum one and one")
test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
Note that the first item in each parameter list corresponds to the name of
the test.
The properties argument is identical to that for `test()`. This may be a
single object (used for all generated tests) or an array.
## Callback API ##
The framework provides callbacks corresponding to 4 events:
* `start` - triggered when the first Test is created
* `test_state` - triggered when a test state changes
* `result` - triggered when a test result is recieved
* `complete` - triggered when all results are recieved
The page defining the tests may add callbacks for these events by calling
the following methods:
`add_start_callback(callback)` - callback called with no arguments
`add_test_state_callback(callback)` - callback called with a test argument
`add_result_callback(callback)` - callback called with a test argument
`add_completion_callback(callback)` - callback called with an array of tests
and an status object
tests have the following properties:
* `status` - A status code. This can be compared to the `PASS`, `FAIL`,
`TIMEOUT` and `NOTRUN` properties on the test object
* `message` - A message indicating the reason for failure. In the future this
will always be a string
The status object gives the overall status of the harness. It has the
following properties:
* `status` - Can be compared to the `OK`, `ERROR` and `TIMEOUT` properties
* `message` - An error message set when the status is `ERROR`
## External API ##
In order to collect the results of multiple pages containing tests, the test
harness will, when loaded in a nested browsing context, attempt to call
certain functions in each ancestor and opener browsing context:
* start - `start_callback`
* test\_state - `test_state_callback`
* result - `result_callback`
* complete - `completion_callback`
These are given the same arguments as the corresponding internal callbacks
described above.
## External API through cross-document messaging ##
Where supported, the test harness will also send messages using cross-document
messaging to each ancestor and opener browsing context. Since it uses the
wildcard keyword (\*), cross-origin communication is enabled and script on
different origins can collect the results.
This API follows similar conventions as those described above only slightly
modified to accommodate message event API. Each message is sent by the harness
is passed a single vanilla object, available as the `data` property of the event
object. These objects are structures as follows:
* start - `{ type: "start" }`
* test\_state - `{ type: "test_state", test: Test }`
* result - `{ type: "result", test: Test }`
* complete - `{ type: "complete", tests: [Test, ...], status: TestsStatus }`
## Web Workers ##
The `testharness.js` script can be used from within [dedicated workers, shared
workers](https://html.spec.whatwg.org/multipage/workers.html) and [service
workers](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/).
Testing from a worker script is different from testing from an HTML document in
several ways:
* Workers have no reporting capability since they are runing in the background.
Hence they rely on `testharness.js` running in a companion client HTML document
for reporting.
* Shared and service workers do not have a unique client document since there
could be more than one document that communicates with these workers. So a
client document needs to explicitly connect to a worker and fetch test results
from it using `fetch_tests_from_worker`. This is true even for a dedicated
worker. Once connected, the individual tests running in the worker (or those
that have already run to completion) will be automatically reflected in the
client document.
* The client document controls the timeout of the tests. All worker scripts act
as if they were started with the `explicit_timeout` option (see the [Harness
timeout](#harness-timeout) section).
* Dedicated and shared workers don't have an equivalent of an `onload` event.
Thus the test harness has no way to know when all tests have completed (see
[Determining when all tests are
complete](#determining-when-all-tests-are-complete)). So these worker tests
behave as if they were started with the `explicit_done` option. Service
workers depend on the
[oninstall](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-global-scope-install-event)
event and don't require an explicit `done` call.
Here's an example that uses a dedicated worker.
`worker.js`:
importScripts("/resources/testharness.js");
test(function(t) {
assert_true(true, "true is true");
}, "Simple test");
// done() is needed because the testharness is running as if explicit_done
// was specified.
done();
`test.html`:
<!DOCTYPE html>
<title>Simple test</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
fetch_tests_from_worker(new Worker("worker.js"));
</script>
The argument to the `fetch_tests_from_worker` function can be a
[`Worker`](https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-worker-interface),
a [`SharedWorker`](https://html.spec.whatwg.org/multipage/workers.html#shared-workers-and-the-sharedworker-interface)
or a [`ServiceWorker`](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-obj).
Once called, the containing document fetches all the tests from the worker and
behaves as if those tests were running in the containing document itself.
## List of Assertions ##
### `assert_true(actual, description)`
asserts that `actual` is strictly true
### `assert_false(actual, description)`
asserts that `actual` is strictly false
### `assert_equals(actual, expected, description)`
asserts that `actual` is the same value as `expected`
### `assert_not_equals(actual, expected, description)`
asserts that `actual` is a different value to `expected`.
This means that `expected` is a misnomer.
### `assert_in_array(actual, expected, description)`
asserts that `expected` is an Array, and `actual` is equal to one of the
members i.e. `expected.indexOf(actual) != -1`
### `assert_array_equals(actual, expected, description)`
asserts that `actual` and `expected` have the same
length and the value of each indexed property in `actual` is the strictly equal
to the corresponding property value in `expected`
### `assert_approx_equals(actual, expected, epsilon, description)`
asserts that `actual` is a number within +`- `epsilon` of `expected`
### `assert_less_than(actual, expected, description)`
asserts that `actual` is a number less than `expected`
### `assert_greater_than(actual, expected, description)`
asserts that `actual` is a number greater than `expected`
### `assert_between_exclusive(actual, lower, upper, description`
asserts that `actual` is a number between `lower` and `upper` but not
equal to either of them
### `assert_less_than_equal(actual, expected, description)`
asserts that `actual` is a number less than or equal to `expected`
### `assert_greater_than_equal(actual, expected, description)`
asserts that `actual` is a number greater than or equal to `expected`
### `assert_between_inclusive(actual, lower, upper, description`
asserts that `actual` is a number between `lower` and `upper` or
equal to either of them
### `assert_regexp_match(actual, expected, description)`
asserts that `actual` matches the regexp `expected`
### `assert_class_string(object, class_name, description)`
asserts that the class string of `object` as returned in
`Object.prototype.toString` is equal to `class_name`.
### `assert_own_property(object, property_name, description)`
assert that object has own property `property_name`
### `assert_inherits(object, property_name, description)`
assert that object does not have an own property named
`property_name` but that `property_name` is present in the prototype
chain for object
### `assert_idl_attribute(object, attribute_name, description)`
assert that an object that is an instance of some interface has the
attribute attribute_name following the conditions specified by WebIDL
### `assert_readonly(object, property_name, description)`
assert that property `property_name` on object is readonly
### `assert_throws(code, func, description)`
`code` - the expected exception. This can take several forms:
* string - the thrown exception must be a DOMException with the given
name, e.g., "TimeoutError" (for compatibility with existing
tests, a constant is also supported, e.g., "TIMEOUT_ERR")
* object - the thrown exception must have a property called "name" that
matches code.name
* null - allow any exception (in general, one of the options above
should be used)
`func` - a function that should throw
### `assert_unreached(description)`
asserts if called. Used to ensure that some codepath is *not* taken e.g.
an event does not fire.
### `assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)`
asserts that one `assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)`
is true for some `expected_array_N` in `expected_array`. This only works for `assert_func`
with signature `assert_func(actual, expected, args_1, ..., args_N)`. Note that tests
with multiple allowed pass conditions are bad practice unless the spec specifically
allows multiple behaviours. Test authors should not use this method simply to hide
UA bugs.
### `assert_exists(object, property_name, description)`
**deprecated**
asserts that object has an own property `property_name`
### `assert_not_exists(object, property_name, description)`
**deprecated**
assert that object does not have own property `property_name`
## Metadata ##
It is possible to add optional metadata to tests; this can be done in
one of two ways; either by adding `<meta>` elements to the head of the
document containing the tests, or by adding the metadata to individual
`[async_]test` calls, as properties.

View file

@ -0,0 +1,118 @@
## Introduction ##
`idlharness.js` automatically generates browser tests for WebIDL interfaces, using
the testharness.js framework. To use, first include the following:
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/resources/WebIDLParser.js></script>
<script src=/resources/idlharness.js></script>
Then you'll need some type of IDLs. Here's some script that can be run on a
spec written in HTML, which will grab all the elements with `class="idl"`,
concatenate them, and replace the body so you can copy-paste:
var s = "";
[].forEach.call(document.getElementsByClassName("idl"), function(idl) {
//https://www.w3.org/Bugs/Public/show_bug.cgi?id=14914
if (!idl.classList.contains("extract"))
{
s += idl.textContent + "\n\n";
}
});
document.body.innerHTML = '<pre></pre>';
document.body.firstChild.textContent = s;
Once you have that, put it in your script somehow. The easiest way is to
embed it literally in an HTML file with `<script type=text/plain>` or similar,
so that you don't have to do any escaping. Another possibility is to put it
in a separate .idl file that's fetched via XHR or similar. Sample usage:
var idl_array = new IdlArray();
idl_array.add_untested_idls("interface Node { readonly attribute DOMString nodeName; };");
idl_array.add_idls("interface Document : Node { readonly attribute DOMString URL; };");
idl_array.add_objects({Document: ["document"]});
idl_array.test();
This tests that `window.Document` exists and meets all the requirements of
WebIDL. It also tests that window.document (the result of evaluating the
string "document") has URL and nodeName properties that behave as they
should, and otherwise meets WebIDL's requirements for an object whose
primary interface is Document. It does not test that window.Node exists,
which is what you want if the Node interface is already tested in some other
specification's suite and your specification only extends or refers to it.
Of course, each IDL string can define many different things, and calls to
add_objects() can register many different objects for different interfaces:
this is a very simple example.
## Public methods of IdlArray ##
IdlArray objects can be obtained with `new IdlArray()`. Anything not
documented in this section should be considered an implementation detail,
and outside callers should not use it.
### `add_idls(idl_string)`
Parses `idl_string` (throwing on parse error) and adds the results to the
IdlArray. All the definitions will be tested when you run test(). If
some of the definitions refer to other definitions, those must be present
too. For instance, if `idl_string` says that `Document` inherits from `Node`,
the `Node` interface must also have been provided in some call to `add_idls()`
or `add_untested_idls()`.
### `add_untested_idls(idl_string)`
Like `add_idls()`, but the definitions will not be tested. If an untested
interface is added and then extended with a tested partial interface, the
members of the partial interface will still be tested. Also, all the
members will still be tested for objects added with `add_objects()`, because
you probably want to test that (for instance) window.document has all the
properties from `Node`, not just `Document`, even if the `Node` interface itself
is tested in a different test suite.
### `add_objects(dict)`
`dict` should be an object whose keys are the names of interfaces or
exceptions, and whose values are arrays of strings. When an interface or
exception is tested, every string registered for it with `add_objects()`
will be evaluated, and tests will be run on the result to verify that it
correctly implements that interface or exception. This is the only way to
test anything about `[NoInterfaceObject]` interfaces, and there are many
tests that can't be run on any interface without an object to fiddle with.
The interface has to be the *primary* interface of all the objects
provided. For example, don't pass `{Node: ["document"]}`, but rather
`{Document: ["document"]}`. Assuming the `Document` interface was declared to
inherit from `Node`, this will automatically test that document implements
the `Node` interface too.
Warning: methods will be called on any provided objects, in a manner that
WebIDL requires be safe. For instance, if a method has mandatory
arguments, the test suite will try calling it with too few arguments to
see if it throws an exception. If an implementation incorrectly runs the
function instead of throwing, this might have side effects, possibly even
preventing the test suite from running correctly.
### `prevent_multiple_testing(name)`
This is a niche method for use in case you're testing many objects that
implement the same interfaces, and don't want to retest the same
interfaces every single time. For instance, HTML defines many interfaces
that all inherit from `HTMLElement`, so the HTML test suite has something
like
`.add_objects({
HTMLHtmlElement: ['document.documentElement'],
HTMLHeadElement: ['document.head'],
HTMLBodyElement: ['document.body'],
...
})`
and so on for dozens of element types. This would mean that it would
retest that each and every one of those elements implements `HTMLElement`,
`Element`, and `Node`, which would be thousands of basically redundant tests.
The test suite therefore calls `prevent_multiple_testing("HTMLElement")`.
This means that once one object has been tested to implement `HTMLElement`
and its ancestors, no other object will be. Thus in the example code
above, the harness would test that `document.documentElement` correctly
implements `HTMLHtmlElement`, `HTMLElement`, `Element`, and `Node`; but
`document.head` would only be tested for `HTMLHeadElement`, and so on for
further objects.
### `test()`
Run all tests. This should be called after you've called all other
methods to add IDLs and objects.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
## Introdution ##
testharness.js provides a framework for writing low-level tests of
browser functionality in javascript. It provides a convenient API for
making assertions and is intended to work for both simple synchronous
tests and for tests of asynchronous behaviour.
## Getting Started ##
To use testharness.js you must include two scripts, in the order given:
``` html
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
```
## Full documentation ##
Full user documentation for the API is in the
[docs/api.md](https://github.com/w3c/testharness.js/blob/master/docs/api.md) file.
You can also read a tutorial on
[Using testharness.js](http://darobin.github.com/test-harness-tutorial/docs/using-testharness.html).

View file

@ -0,0 +1,102 @@
html {
font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;
}
#log .warning,
#log .warning a {
color: black;
background: yellow;
}
#log .error,
#log .error a {
color: white;
background: red;
}
section#summary {
margin-bottom:1em;
}
table#results {
border-collapse:collapse;
table-layout:fixed;
width:100%;
}
table#results th:first-child,
table#results td:first-child {
width:4em;
}
table#results th:last-child,
table#results td:last-child {
width:50%;
}
table#results.assertions th:last-child,
table#results.assertions td:last-child {
width:35%;
}
table#results th {
padding:0;
padding-bottom:0.5em;
border-bottom:medium solid black;
}
table#results td {
padding:1em;
padding-bottom:0.5em;
border-bottom:thin solid black;
}
tr.pass > td:first-child {
color:green;
}
tr.fail > td:first-child {
color:red;
}
tr.timeout > td:first-child {
color:red;
}
tr.notrun > td:first-child {
color:blue;
}
.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {
font-variant:small-caps;
}
table#results span {
display:block;
}
table#results span.expected {
font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
white-space:pre;
}
table#results span.actual {
font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
white-space:pre;
}
span.ok {
color:green;
}
tr.error {
color:red;
}
span.timeout {
color:red;
}
span.ok, span.timeout, span.error {
font-variant:small-caps;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var props = {output:%(output)d,
explicit_timeout: true};
if (window.opener && "timeout_multiplier" in window.opener) {
props["timeout_multiplier"] = window.opener.timeout_multiplier;
}
if (window.opener && window.opener.explicit_timeout) {
props["explicit_timeout"] = window.opener.explicit_timeout;
}
setup(props);
add_completion_callback(function() {
add_completion_callback(function(tests, status) {
window.opener.done(tests, status)
})
});

View file

@ -0,0 +1,4 @@
scratch
node_modules
lib-cov
browser-tests.html

View file

@ -0,0 +1,3 @@
[submodule "test/widlproc"]
path = test/widlproc
url = https://github.com/dontcallmedom/widlproc.git

View file

@ -0,0 +1,3 @@
language: node_js
node_js:
- "0.10"

View file

@ -0,0 +1,725 @@
# WebIDL 2
[![NPM version](https://badge.fury.io/js/webidl2.png)](http://badge.fury.io/js/webidl2)
Purpose
=======
This is a parser for the [WebIDL](http://dev.w3.org/2006/webapi/WebIDL/) language. If
you don't know what that is, then you probably don't need it. It is meant to be used
both in Node and in the browser (the parser likely works in other JS environments, but
not the test suite).
What of v1?
-----------
There was a previous incarnation of this project. I had written it in the most quick
and dirty manner that was handy because I required it as a dependency in an experiment.
As these things tend to happen, some people started using that, which then had to be
maintained. But since it was not built on solid foundations, it was painful to keep
up to date with the specification, which is a bit of a moving target.
So I started from scratch. Compared to the previous version (which used a parser generator)
this one is about 6x less code (which translates to 4x smaller minified or 2x smaller
minizipped) and 4x faster. The test suite is reasonably complete (95% coverage), much more
than previously. This version is up to date with WebIDL, rather than a couple years' behind.
It also has *far* better error reporting.
The AST you get from parsing is very similar to the one you got in v1, but some adjustments
have been made in order to be more systematic, and to map better to what's actually in the spec
now. If you used v1, you will need to tweak your code but the result ought to be simpler and
you ought to be able to be a fair bit less defensive against irregularities in the way
information is represented.
Installation
============
Just the usual. For Node:
npm install webidl2
In the browser:
<script src='webidl2.js'></script>
Documentation
=============
The API to WebIDL2 is trivial: you parse a string of WebIDL and it returns a syntax tree.
Parsing
-------
In Node, that happens with:
var WebIDL2 = require("webidl2");
var tree = WebIDL2.parse("string of WebIDL");
In the browser:
<script src='webidl2.js'></script>
<script>
var tree = WebIDL2.parse("string of WebIDL");
</script>
Errors
------
When there is a syntax error in the WebIDL, it throws an exception object with the following
properties:
* `message`: the error message
* `line`: the line at which the error occurred.
* `input`: a short peek at the text at the point where the error happened
* `tokens`: the five tokens at the point of error, as understood by the tokeniser
(this is the same content as `input`, but seen from the tokeniser's point of view)
The exception also has a `toString()` method that hopefully should produce a decent
error message.
AST (Abstract Syntax Tree)
--------------------------
The `parse()` method returns a tree object representing the parse tree of the IDL.
Comment and white space are not represented in the AST.
The root of this object is always an array of definitions (where definitions are
any of interfaces, exceptions, callbacks, etc. — anything that can occur at the root
of the IDL).
### IDL Type
This structure is used in many other places (operation return types, argument types, etc.).
It captures a WebIDL type with a number of options. Types look like this and are typically
attached to a field called `idlType`:
{
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "void"
}
Where the fields are as follows:
* `sequence`: Boolean indicating whether this is a sequence or not. Deprecated. Use
`generic` instead.
* `generic`: String indicating the generic type (e.g. "Promise", "sequence"). `null`
otherwise.
* `nullable`: Boolean indicating whether this is nullable or not.
* `array`: Either `false` to indicate that it is not an array, or a number for the level of
array nesting.
* `union`: Boolean indicating whether this is a union type or not.
* `idlType`: Can be different things depending on context. In most cases, this will just
be a string with the type name. But the reason this field isn't called "typeName" is
because it can take more complex values. If the type is a union, then this contains an
array of the types it unites. If it is a generic type, it contains the IDL type
description for the type in the sequence, the eventual value of the promise, etc.
#### Interactions between `nullable` and `array`
A more complex data model for our AST would likely represent `Foo[][][]` as a series of
nested types four levels deep with three anonymous array types eventually containing a
`Foo` type. But experience shows that such structures are cumbersome to use, and so we
have a simpler model in which the depth of the array is specified with the `array` field.
This is all fine and well, and in the vast majority of cases is actually simpler. But it
does run afoul of cases in which it is necessary to distinguish between `Foo[][][]?`,
`Foo?[][][]`, `Foo[][]?[]`, or even `Foo?[]?[]?[]?`.
For this, when a type is an array type an additional `nullableArray` field is made available
that captures which of the arrays contain nullable elements. It contains booleans that are
true if the given array depth contains nullable elements, and false otherwise (mapping that to
the syntax, and item is true if there is a `?` preceding the `[]`). These examples ought to
clarify the model:
Foo[][][]?
-> nullable: true
-> nullableArray: [false, false, false]
Foo?[][][]
-> nullable: false
-> nullableArray: [true, false, false]
Foo[][]?[]
-> nullable: false
-> nullableArray: [false, false, true]
Foo?[]?[]?[]?
-> nullable: true
-> nullableArray: [true, true, true]
Of particular importance, please note that the overall type is only `nullable` if there is
a `?` at the end.
### Interface
Interfaces look like this:
{
"type": "interface",
"name": "Animal",
"partial": false,
"members": [...],
"inheritance": null,
"extAttrs": [...]
},
{
"type": "interface",
"name": "Human",
"partial": false,
"members": [...],
"inheritance": "Animal",
"extAttrs": [...]
}
The fields are as follows:
* `type`: Always "interface".
* `name`: The name of the interface
* `partial`: A boolean indicating whether it's a partial interface.
* `members`: An array of interface members (attributes, operations, etc.). Empty if there are none.
* `inheritance`: A string giving the name of an interface this one inherits from, `null` otherwise.
**NOTE**: In v1 this was an array, but multiple inheritance is no longer supported so this didn't make
sense.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Callback Interfaces
These are captured by the same structure as [Interfaces](#interface) except that
their `type` field is "callback interface".
### Callback
A callback looks like this:
{
"type": "callback",
"name": "AsyncOperationCallback",
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "void"
},
"arguments": [...],
"extAttrs": []
}
The fields are as follows:
* `type`: Always "callback".
* `name`: The name of the callback.
* `idlType`: An [IDL Type](#idl-type) describing what the callback returns.
* `arguments`: A list of [arguments](#arguments), as in function paramters.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Dictionary
A dictionary looks like this:
{
"type": "dictionary",
"name": "PaintOptions",
"partial": false,
"members": [
{
"type": "field",
"name": "fillPattern",
"idlType": {
"sequence": false,
"generic": null,
"nullable": true,
"array": false,
"union": false,
"idlType": "DOMString"
},
"extAttrs": [],
"default": {
"type": "string",
"value": "black"
}
}
],
"inheritance": null,
"extAttrs": []
}
The fields are as follows:
* `type`: Always "dictionary".
* `name`: The dictionary name.
* `partial`: Boolean indicating whether it's a partial dictionary.
* `members`: An array of members (see below).
* `inheritance`: A string indicating which dictionary is being inherited from, `null` otherwise.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
All the members are fields as follows:
* `type`: Always "field".
* `name`: The name of the field.
* `idlType`: An [IDL Type](#idl-type) describing what field's type.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
* `default`: A [default value](#default-and-const-values), absent if there is none.
### Exception
An exception looks like this:
{
"type": "exception",
"name": "HierarchyRequestError",
"members": [
{
"type": "field",
"name": "code",
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "unsigned short"
},
"extAttrs": []
}
],
"inheritance": "DOMException",
"extAttrs": []
}
The fields are as follows:
* `type`: Always "exception".
* `name`: The exception name.
* `members`: An array of members (constants or fields, where fields are described below).
* `inheritance`: A string indicating which exception is being inherited from, `null` otherwise.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
Members that aren't [constants](#constants) have the following fields:
* `type`: Always "field".
* `name`: The field's name.
* `idlType`: An [IDL Type](#idl-type) describing what field's type.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Enum
An enum looks like this:
{
"type": "enum",
"name": "MealType",
"values": [
"rice",
"noodles",
"other"
],
"extAttrs": []
}
The fields are as follows:
* `type`: Always "enum".
* `name`: The enum's name.
* `value`: An array of values (strings).
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Typedef
A typedef looks like this:
{
"type": "typedef",
"typeExtAttrs": [],
"idlType": {
"sequence": true,
"generic": "sequence",
"nullable": false,
"array": false,
"union": false,
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "Point"
}
},
"name": "PointSequence",
"extAttrs": []
}
The fields are as follows:
* `type`: Always "typedef".
* `name`: The typedef's name.
* `idlType`: An [IDL Type](#idl-type) describing what typedef's type.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
* `typeExtAttrs`: A list of [extended attributes](#extended-attributes) that apply to the
type rather than to the typedef as a whole.
### Implements
An implements definition looks like this:
{
"type": "implements",
"target": "Node",
"implements": "EventTarget",
"extAttrs": []
}
The fields are as follows:
* `type`: Always "implements".
* `target`: The interface that implements another.
* `implements`: The interface that is being implemented by the target.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Operation Member
An operation looks like this:
{
"type": "operation",
"getter": false,
"setter": false,
"creator": false,
"deleter": false,
"legacycaller": false,
"static": false,
"stringifier": false,
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "void"
},
"name": "intersection",
"arguments": [
{
"optional": false,
"variadic": true,
"extAttrs": [],
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "long"
},
"name": "ints"
}
],
"extAttrs": []
}
The fields are as follows:
* `type`: Always "operation".
* `getter`: True if a getter operation.
* `setter`: True if a setter operation.
* `creator`: True if a creator operation.
* `deleter`: True if a deleter operation.
* `legacycaller`: True if a legacycaller operation.
* `static`: True if a static operation.
* `stringifier`: True if a stringifier operation.
* `idlType`: An [IDL Type](#idl-type) of what the operation returns. If a stringifier, may be absent.
* `name`: The name of the operation. If a stringifier, may be `null`.
* `arguments`: An array of [arguments](#arguments) for the operation.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Attribute Member
An attribute member looks like this:
{
"type": "attribute",
"static": false,
"stringifier": false,
"inherit": false,
"readonly": false,
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "RegExp"
},
"name": "regexp",
"extAttrs": []
}
The fields are as follows:
* `type`: Always "attribute".
* `name`: The attribute's name.
* `static`: True if it's a static attribute.
* `stringifier`: True if it's a stringifier attribute.
* `inherit`: True if it's an inherit attribute.
* `readonly`: True if it's a read-only attribute.
* `idlType`: An [IDL Type](#idl-type) for the attribute.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Constant Member
A constant member looks like this:
{
"type": "const",
"nullable": false,
"idlType": "boolean",
"name": "DEBUG",
"value": {
"type": "boolean",
"value": false
},
"extAttrs": []
}
The fields are as follows:
* `type`: Always "const".
* `nullable`: Whether its type is nullable.
* `idlType`: The type of the constant (a simple type, the type name).
* `name`: The name of the constant.
* `value`: The constant value as described by [Const Values](#default-and-const-values)
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Serializer Member
Serializers come in many shapes, which are best understood by looking at the
examples below that map the IDL to the produced AST.
// serializer;
{
"type": "serializer",
"extAttrs": []
}
// serializer DOMString serialize();
{
"type": "serializer",
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "DOMString"
},
"operation": {
"name": "serialize",
"arguments": []
},
"extAttrs": []
}
// serializer = { from, to, amount, description };
{
"type": "serializer",
"patternMap": true,
"names": [
"from",
"to",
"amount",
"description"
],
"extAttrs": []
}
// serializer = number;
{
"type": "serializer",
"name": "number",
"extAttrs": []
}
// serializer = [ name, number ];
{
"type": "serializer",
"patternList": true,
"names": [
"name",
"number"
],
"extAttrs": []
}
The common fields are as follows:
* `type`: Always "serializer".
* `extAttrs`: A list of [extended attributes](#extended-attributes).
For a simple serializer, that's all there is. If the serializer is an operation, it will
have:
* `idlType`: An [IDL Type](#idl-type) describing what the serializer returns.
* `operation`: An object with the following fields:
* `name`: The name of the operation.
* `arguments`: An array of [arguments](#arguments) for the operation.
If the serializer is a pattern map:
* `patternMap`: Always true.
* `names`: An array of names in the pattern map.
If the serializer is a pattern list:
* `patternList`: Always true.
* `names`: An array of names in the pattern list.
Finally, if the serializer is a named serializer:
* `name`: The serializer's name.
### Iterator Member
Iterator members look like this
{
"type": "iterator",
"getter": false,
"setter": false,
"creator": false,
"deleter": false,
"legacycaller": false,
"static": false,
"stringifier": false,
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "Session2"
},
"iteratorObject": "SessionIterator",
"extAttrs": []
}
* `type`: Always "iterator".
* `iteratorObject`: The string on the right-hand side; absent if there isn't one.
* the rest: same as on [operations](#operation-member).
### Arguments
The arguments (e.g. for an operation) look like this:
"arguments": [
{
"optional": false,
"variadic": true,
"extAttrs": [],
"idlType": {
"sequence": false,
"generic": null,
"nullable": false,
"array": false,
"union": false,
"idlType": "long"
},
"name": "ints"
}
]
The fields are as follows:
* `optional`: True if the argument is optional.
* `variadic`: True if the argument is variadic.
* `idlType`: An [IDL Type](#idl-type) describing the type of the argument.
* `name`: The argument's name.
* `extAttrs`: A list of [extended attributes](#extended-attributes).
### Extended Attributes
Extended attributes are arrays of items that look like this:
"extAttrs": [
{
"name": "TreatNullAs",
"arguments": null,
"rhs": {
"type": "identifier",
"value": "EmptyString"
}
}
]
The fields are as follows:
* `name`: The extended attribute's name.
* `arguments`: If the extended attribute takes arguments (e.g. `[Foo()]`) or if
its right-hand side does (e.g. `[NamedConstructor=Name(DOMString blah)]`) they
are listed here. Note that an empty arguments list will produce an empty array,
whereas the lack thereof will yield a `null`. If there is an `rhs` field then
they are the right-hand side's arguments, otherwise they apply to the extended
attribute directly.
* `rhs`: If there is a right-hand side, this will capture its `type` (always
"identifier" in practice, though it may be extended in the future) and its
`value`.
* `typePair`: If the extended attribute is a `MapClass` this will capture the
map's key type and value type respectively.
### Default and Const Values
Dictionary fields and operation arguments can take default values, and constants take
values, all of which have the following fields:
* `type`: One of string, number, boolean, null, Infinity, or NaN.
For string, number, and boolean:
* `value`: The value of the given type.
For Infinity:
* `negative`: Boolean indicating whether this is negative Infinity or not.
Testing
=======
In order to run the tests you need to ensure that the widlproc submodule inside `test` is
initialised and up to date:
git submodule init
git submodule update
Running
-------
The test runs with mocha and expect.js. Normally, running mocha in the root directory
should be enough once you're set up.
Coverage
--------
Current test coverage, as documented in `coverage.html`, is 95%. You can run your own
coverage analysis with:
jscoverage lib lib-cov
That will create the lib-cov directory with instrumented code; the test suite knows
to use that if needed. You can then run the tests with:
JSCOV=1 mocha --reporter html-cov > coverage.html
Note that I've been getting weirdly overescaped results from the html-cov reporter,
so you might wish to try this instead:
JSCOV=1 mocha --reporter html-cov | sed "s/&lt;/</g" | sed "s/&gt;/>/g" | sed "s/&quot;/\"/g" > coverage.html
Browser tests
-------------
In order to test in the browser, get inside `test/web` and run `make-web-tests.js`. This
will generate a `browser-tests.html` file that you can open in a browser. As of this
writing tests pass in the latest Firefox, Chrome, Opera, and Safari. Testing on IE
and older versions will happen progressively.
TODO
====
* add some tests to address coverage limitations
* add a push API for processors that need to process things like comments

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
module.exports = require("./lib/webidl2.js");

View file

@ -0,0 +1,924 @@
(function () {
var tokenise = function (str) {
var tokens = []
, re = {
"float": /^-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/
, "integer": /^-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/
, "identifier": /^[A-Z_a-z][0-9A-Z_a-z]*/
, "string": /^"[^"]*"/
, "whitespace": /^(?:[\t\n\r ]+|[\t\n\r ]*((\/\/.*|\/\*(.|\n|\r)*?\*\/)[\t\n\r ]*))+/
, "other": /^[^\t\n\r 0-9A-Z_a-z]/
}
, types = []
;
for (var k in re) types.push(k);
while (str.length > 0) {
var matched = false;
for (var i = 0, n = types.length; i < n; i++) {
var type = types[i];
str = str.replace(re[type], function (tok) {
tokens.push({ type: type, value: tok });
matched = true;
return "";
});
if (matched) break;
}
if (matched) continue;
throw new Error("Token stream not progressing");
}
return tokens;
};
var parse = function (tokens, opt) {
var line = 1;
tokens = tokens.slice();
var FLOAT = "float"
, INT = "integer"
, ID = "identifier"
, STR = "string"
, OTHER = "other"
;
var WebIDLParseError = function (str, line, input, tokens) {
this.message = str;
this.line = line;
this.input = input;
this.tokens = tokens;
};
WebIDLParseError.prototype.toString = function () {
return this.message + ", line " + this.line + " (tokens: '" + this.input + "')\n" +
JSON.stringify(this.tokens, null, 4);
};
var error = function (str) {
var tok = "", numTokens = 0, maxTokens = 5;
while (numTokens < maxTokens && tokens.length > numTokens) {
tok += tokens[numTokens].value;
numTokens++;
}
throw new WebIDLParseError(str, line, tok, tokens.slice(0, 5));
};
var last_token = null;
var consume = function (type, value) {
if (!tokens.length || tokens[0].type !== type) return;
if (typeof value === "undefined" || tokens[0].value === value) {
last_token = tokens.shift();
if (type === ID) last_token.value = last_token.value.replace(/^_/, "");
return last_token;
}
};
var ws = function () {
if (!tokens.length) return;
if (tokens[0].type === "whitespace") {
var t = tokens.shift();
t.value.replace(/\n/g, function (m) { line++; return m; });
return t;
}
};
var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types
var t = { type: "whitespace", value: "" };
while (true) {
var w = ws();
if (!w) break;
t.value += w.value;
}
if (t.value.length > 0) {
if (store) {
var w = t.value
, re = {
"ws": /^([\t\n\r ]+)/
, "line-comment": /^\/\/(.*)\n?/m
, "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\//
}
, wsTypes = []
;
for (var k in re) wsTypes.push(k);
while (w.length) {
var matched = false;
for (var i = 0, n = wsTypes.length; i < n; i++) {
var type = wsTypes[i];
w = w.replace(re[type], function (tok, m1) {
store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
matched = true;
return "";
});
if (matched) break;
}
if (matched) continue;
throw new Error("Surprising white space construct."); // this shouldn't happen
}
}
return t;
}
};
var integer_type = function () {
var ret = "";
all_ws();
if (consume(ID, "unsigned")) ret = "unsigned ";
all_ws();
if (consume(ID, "short")) return ret + "short";
if (consume(ID, "long")) {
ret += "long";
all_ws();
if (consume(ID, "long")) return ret + " long";
return ret;
}
if (ret) error("Failed to parse integer type");
};
var float_type = function () {
var ret = "";
all_ws();
if (consume(ID, "unrestricted")) ret = "unrestricted ";
all_ws();
if (consume(ID, "float")) return ret + "float";
if (consume(ID, "double")) return ret + "double";
if (ret) error("Failed to parse float type");
};
var primitive_type = function () {
var num_type = integer_type() || float_type();
if (num_type) return num_type;
all_ws();
if (consume(ID, "boolean")) return "boolean";
if (consume(ID, "byte")) return "byte";
if (consume(ID, "octet")) return "octet";
};
var const_value = function () {
if (consume(ID, "true")) return { type: "boolean", value: true };
if (consume(ID, "false")) return { type: "boolean", value: false };
if (consume(ID, "null")) return { type: "null" };
if (consume(ID, "Infinity")) return { type: "Infinity", negative: false };
if (consume(ID, "NaN")) return { type: "NaN" };
var ret = consume(FLOAT) || consume(INT);
if (ret) return { type: "number", value: 1 * ret.value };
var tok = consume(OTHER, "-");
if (tok) {
if (consume(ID, "Infinity")) return { type: "Infinity", negative: true };
else tokens.unshift(tok);
}
};
var type_suffix = function (obj) {
while (true) {
all_ws();
if (consume(OTHER, "?")) {
if (obj.nullable) error("Can't nullable more than once");
obj.nullable = true;
}
else if (consume(OTHER, "[")) {
all_ws();
consume(OTHER, "]") || error("Unterminated array type");
if (!obj.array) {
obj.array = 1;
obj.nullableArray = [obj.nullable];
}
else {
obj.array++;
obj.nullableArray.push(obj.nullable);
}
obj.nullable = false;
}
else return;
}
};
var single_type = function () {
var prim = primitive_type()
, ret = { sequence: false, generic: null, nullable: false, array: false, union: false }
, name
, value
;
if (prim) {
ret.idlType = prim;
}
else if (name = consume(ID)) {
value = name.value;
all_ws();
// Generic types
if (consume(OTHER, "<")) {
// backwards compat
if (value === "sequence") {
ret.sequence = true;
}
ret.generic = value;
ret.idlType = type() || error("Error parsing generic type " + value);
all_ws();
if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
all_ws();
if (consume(OTHER, "?")) ret.nullable = true;
return ret;
}
else {
ret.idlType = value;
}
}
else {
return;
}
type_suffix(ret);
if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable");
return ret;
};
var union_type = function () {
all_ws();
if (!consume(OTHER, "(")) return;
var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] };
var fst = type() || error("Union type with no content");
ret.idlType.push(fst);
while (true) {
all_ws();
if (!consume(ID, "or")) break;
var typ = type() || error("No type after 'or' in union type");
ret.idlType.push(typ);
}
if (!consume(OTHER, ")")) error("Unterminated union type");
type_suffix(ret);
return ret;
};
var type = function () {
return single_type() || union_type();
};
var argument = function (store) {
var ret = { optional: false, variadic: false };
ret.extAttrs = extended_attrs(store);
all_ws(store, "pea");
var opt_token = consume(ID, "optional");
if (opt_token) {
ret.optional = true;
all_ws();
}
ret.idlType = type();
if (!ret.idlType) {
if (opt_token) tokens.unshift(opt_token);
return;
}
var type_token = last_token;
if (!ret.optional) {
all_ws();
if (tokens.length >= 3 &&
tokens[0].type === "other" && tokens[0].value === "." &&
tokens[1].type === "other" && tokens[1].value === "." &&
tokens[2].type === "other" && tokens[2].value === "."
) {
tokens.shift();
tokens.shift();
tokens.shift();
ret.variadic = true;
}
}
all_ws();
var name = consume(ID);
if (!name) {
if (opt_token) tokens.unshift(opt_token);
tokens.unshift(type_token);
return;
}
ret.name = name.value;
if (ret.optional) {
all_ws();
ret["default"] = default_();
}
return ret;
};
var argument_list = function (store) {
var ret = []
, arg = argument(store ? ret : null)
;
if (!arg) return;
ret.push(arg);
while (true) {
all_ws(store ? ret : null);
if (!consume(OTHER, ",")) return ret;
var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
ret.push(nxt);
}
};
var type_pair = function () {
all_ws();
var k = type();
if (!k) return;
all_ws()
if (!consume(OTHER, ",")) return;
all_ws();
var v = type();
if (!v) return;
return [k, v];
};
var simple_extended_attr = function (store) {
all_ws();
var name = consume(ID);
if (!name) return;
var ret = {
name: name.value
, "arguments": null
};
all_ws();
var eq = consume(OTHER, "=");
if (eq) {
all_ws();
ret.rhs = consume(ID);
if (!ret.rhs) return error("No right hand side to extended attribute assignment");
}
all_ws();
if (consume(OTHER, "(")) {
var args, pair;
// [Constructor(DOMString str)]
if (args = argument_list(store)) {
ret["arguments"] = args;
}
// [MapClass(DOMString, DOMString)]
else if (pair = type_pair()) {
ret.typePair = pair;
}
// [Constructor()]
else {
ret["arguments"] = [];
}
all_ws();
consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
}
return ret;
};
// Note: we parse something simpler than the official syntax. It's all that ever
// seems to be used
var extended_attrs = function (store) {
var eas = [];
all_ws(store);
if (!consume(OTHER, "[")) return eas;
eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
all_ws();
while (consume(OTHER, ",")) {
eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
all_ws();
}
consume(OTHER, "]") || error("No end of extended attribute");
return eas;
};
var default_ = function () {
all_ws();
if (consume(OTHER, "=")) {
all_ws();
var def = const_value();
if (def) {
return def;
}
else {
var str = consume(STR) || error("No value for default");
str.value = str.value.replace(/^"/, "").replace(/"$/, "");
return str;
}
}
};
var const_ = function (store) {
all_ws(store, "pea");
if (!consume(ID, "const")) return;
var ret = { type: "const", nullable: false };
all_ws();
var typ = primitive_type();
if (!typ) {
typ = consume(ID) || error("No type for const");
typ = typ.value;
}
ret.idlType = typ;
all_ws();
if (consume(OTHER, "?")) {
ret.nullable = true;
all_ws();
}
var name = consume(ID) || error("No name for const");
ret.name = name.value;
all_ws();
consume(OTHER, "=") || error("No value assignment for const");
all_ws();
var cnt = const_value();
if (cnt) ret.value = cnt;
else error("No value for const");
all_ws();
consume(OTHER, ";") || error("Unterminated const");
return ret;
};
var inheritance = function () {
all_ws();
if (consume(OTHER, ":")) {
all_ws();
var inh = consume(ID) || error ("No type in inheritance");
return inh.value;
}
};
var operation_rest = function (ret, store) {
all_ws();
if (!ret) ret = {};
var name = consume(ID);
ret.name = name ? name.value : null;
all_ws();
consume(OTHER, "(") || error("Invalid operation");
ret["arguments"] = argument_list(store) || [];
all_ws();
consume(OTHER, ")") || error("Unterminated operation");
all_ws();
consume(OTHER, ";") || error("Unterminated operation");
return ret;
};
var callback = function (store) {
all_ws(store, "pea");
var ret;
if (!consume(ID, "callback")) return;
all_ws();
var tok = consume(ID, "interface");
if (tok) {
tokens.unshift(tok);
ret = interface_();
ret.type = "callback interface";
return ret;
}
var name = consume(ID) || error("No name for callback");
ret = { type: "callback", name: name.value };
all_ws();
consume(OTHER, "=") || error("No assignment in callback");
all_ws();
ret.idlType = return_type();
all_ws();
consume(OTHER, "(") || error("No arguments in callback");
ret["arguments"] = argument_list(store) || [];
all_ws();
consume(OTHER, ")") || error("Unterminated callback");
all_ws();
consume(OTHER, ";") || error("Unterminated callback");
return ret;
};
var attribute = function (store) {
all_ws(store, "pea");
var grabbed = []
, ret = {
type: "attribute"
, "static": false
, stringifier: false
, inherit: false
, readonly: false
};
if (consume(ID, "static")) {
ret["static"] = true;
grabbed.push(last_token);
}
else if (consume(ID, "stringifier")) {
ret.stringifier = true;
grabbed.push(last_token);
}
var w = all_ws();
if (w) grabbed.push(w);
if (consume(ID, "inherit")) {
if (ret["static"] || ret.stringifier) error("Cannot have a static or stringifier inherit");
ret.inherit = true;
grabbed.push(last_token);
var w = all_ws();
if (w) grabbed.push(w);
}
if (consume(ID, "readonly")) {
ret.readonly = true;
grabbed.push(last_token);
var w = all_ws();
if (w) grabbed.push(w);
}
if (!consume(ID, "attribute")) {
tokens = grabbed.concat(tokens);
return;
}
all_ws();
ret.idlType = type() || error("No type in attribute");
if (ret.idlType.sequence) error("Attributes cannot accept sequence types");
all_ws();
var name = consume(ID) || error("No name in attribute");
ret.name = name.value;
all_ws();
consume(OTHER, ";") || error("Unterminated attribute");
return ret;
};
var return_type = function () {
var typ = type();
if (!typ) {
if (consume(ID, "void")) {
return "void";
}
else error("No return type");
}
return typ;
};
var operation = function (store) {
all_ws(store, "pea");
var ret = {
type: "operation"
, getter: false
, setter: false
, creator: false
, deleter: false
, legacycaller: false
, "static": false
, stringifier: false
};
while (true) {
all_ws();
if (consume(ID, "getter")) ret.getter = true;
else if (consume(ID, "setter")) ret.setter = true;
else if (consume(ID, "creator")) ret.creator = true;
else if (consume(ID, "deleter")) ret.deleter = true;
else if (consume(ID, "legacycaller")) ret.legacycaller = true;
else break;
}
if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) {
all_ws();
ret.idlType = return_type();
operation_rest(ret, store);
return ret;
}
if (consume(ID, "static")) {
ret["static"] = true;
ret.idlType = return_type();
operation_rest(ret, store);
return ret;
}
else if (consume(ID, "stringifier")) {
ret.stringifier = true;
all_ws();
if (consume(OTHER, ";")) return ret;
ret.idlType = return_type();
operation_rest(ret, store);
return ret;
}
ret.idlType = return_type();
all_ws();
if (consume(ID, "iterator")) {
all_ws();
ret.type = "iterator";
if (consume(ID, "object")) {
ret.iteratorObject = "object";
}
else if (consume(OTHER, "=")) {
all_ws();
var name = consume(ID) || error("No right hand side in iterator");
ret.iteratorObject = name.value;
}
all_ws();
consume(OTHER, ";") || error("Unterminated iterator");
return ret;
}
else {
operation_rest(ret, store);
return ret;
}
};
var identifiers = function (arr) {
while (true) {
all_ws();
if (consume(OTHER, ",")) {
all_ws();
var name = consume(ID) || error("Trailing comma in identifiers list");
arr.push(name.value);
}
else break;
}
};
var serialiser = function (store) {
all_ws(store, "pea");
if (!consume(ID, "serializer")) return;
var ret = { type: "serializer" };
all_ws();
if (consume(OTHER, "=")) {
all_ws();
if (consume(OTHER, "{")) {
ret.patternMap = true;
all_ws();
var id = consume(ID);
if (id && id.value === "getter") {
ret.names = ["getter"];
}
else if (id && id.value === "inherit") {
ret.names = ["inherit"];
identifiers(ret.names);
}
else if (id) {
ret.names = [id.value];
identifiers(ret.names);
}
else {
ret.names = [];
}
all_ws();
consume(OTHER, "}") || error("Unterminated serializer pattern map");
}
else if (consume(OTHER, "[")) {
ret.patternList = true;
all_ws();
var id = consume(ID);
if (id && id.value === "getter") {
ret.names = ["getter"];
}
else if (id) {
ret.names = [id.value];
identifiers(ret.names);
}
else {
ret.names = [];
}
all_ws();
consume(OTHER, "]") || error("Unterminated serializer pattern list");
}
else {
var name = consume(ID) || error("Invalid serializer");
ret.name = name.value;
}
all_ws();
consume(OTHER, ";") || error("Unterminated serializer");
return ret;
}
else if (consume(OTHER, ";")) {
// noop, just parsing
}
else {
ret.idlType = return_type();
all_ws();
ret.operation = operation_rest(null, store);
}
return ret;
};
var interface_ = function (isPartial, store) {
all_ws(isPartial ? null : store, "pea");
if (!consume(ID, "interface")) return;
all_ws();
var name = consume(ID) || error("No name for interface");
var mems = []
, ret = {
type: "interface"
, name: name.value
, partial: false
, members: mems
};
if (!isPartial) ret.inheritance = inheritance() || null;
all_ws();
consume(OTHER, "{") || error("Bodyless interface");
while (true) {
all_ws(store ? mems : null);
if (consume(OTHER, "}")) {
all_ws();
consume(OTHER, ";") || error("Missing semicolon after interface");
return ret;
}
var ea = extended_attrs(store ? mems : null);
all_ws();
var cnt = const_(store ? mems : null);
if (cnt) {
cnt.extAttrs = ea;
ret.members.push(cnt);
continue;
}
var mem = serialiser(store ? mems : null) ||
attribute(store ? mems : null) ||
operation(store ? mems : null) ||
error("Unknown member");
mem.extAttrs = ea;
ret.members.push(mem);
}
};
var partial = function (store) {
all_ws(store, "pea");
if (!consume(ID, "partial")) return;
var thing = dictionary(true, store) ||
interface_(true, store) ||
error("Partial doesn't apply to anything");
thing.partial = true;
return thing;
};
var dictionary = function (isPartial, store) {
all_ws(isPartial ? null : store, "pea");
if (!consume(ID, "dictionary")) return;
all_ws();
var name = consume(ID) || error("No name for dictionary");
var mems = []
, ret = {
type: "dictionary"
, name: name.value
, partial: false
, members: mems
};
if (!isPartial) ret.inheritance = inheritance() || null;
all_ws();
consume(OTHER, "{") || error("Bodyless dictionary");
while (true) {
all_ws(store ? mems : null);
if (consume(OTHER, "}")) {
all_ws();
consume(OTHER, ";") || error("Missing semicolon after dictionary");
return ret;
}
var ea = extended_attrs(store ? mems : null);
all_ws(store ? mems : null, "pea");
var typ = type() || error("No type for dictionary member");
all_ws();
var name = consume(ID) || error("No name for dictionary member");
ret.members.push({
type: "field"
, name: name.value
, idlType: typ
, extAttrs: ea
, "default": default_()
});
all_ws();
consume(OTHER, ";") || error("Unterminated dictionary member");
}
};
var exception = function (store) {
all_ws(store, "pea");
if (!consume(ID, "exception")) return;
all_ws();
var name = consume(ID) || error("No name for exception");
var mems = []
, ret = {
type: "exception"
, name: name.value
, members: mems
};
ret.inheritance = inheritance() || null;
all_ws();
consume(OTHER, "{") || error("Bodyless exception");
while (true) {
all_ws(store ? mems : null);
if (consume(OTHER, "}")) {
all_ws();
consume(OTHER, ";") || error("Missing semicolon after exception");
return ret;
}
var ea = extended_attrs(store ? mems : null);
all_ws(store ? mems : null, "pea");
var cnt = const_();
if (cnt) {
cnt.extAttrs = ea;
ret.members.push(cnt);
}
else {
var typ = type();
all_ws();
var name = consume(ID);
all_ws();
if (!typ || !name || !consume(OTHER, ";")) error("Unknown member in exception body");
ret.members.push({
type: "field"
, name: name.value
, idlType: typ
, extAttrs: ea
});
}
}
};
var enum_ = function (store) {
all_ws(store, "pea");
if (!consume(ID, "enum")) return;
all_ws();
var name = consume(ID) || error("No name for enum");
var vals = []
, ret = {
type: "enum"
, name: name.value
, values: vals
};
all_ws();
consume(OTHER, "{") || error("No curly for enum");
var saw_comma = false;
while (true) {
all_ws(store ? vals : null);
if (consume(OTHER, "}")) {
all_ws();
if (saw_comma) error("Trailing comma in enum");
consume(OTHER, ";") || error("No semicolon after enum");
return ret;
}
var val = consume(STR) || error("Unexpected value in enum");
ret.values.push(val.value.replace(/"/g, ""));
all_ws(store ? vals : null);
if (consume(OTHER, ",")) {
if (store) vals.push({ type: "," });
all_ws(store ? vals : null);
saw_comma = true;
}
else {
saw_comma = false;
}
}
};
var typedef = function (store) {
all_ws(store, "pea");
if (!consume(ID, "typedef")) return;
var ret = {
type: "typedef"
};
all_ws();
ret.typeExtAttrs = extended_attrs();
all_ws(store, "tpea");
ret.idlType = type() || error("No type in typedef");
all_ws();
var name = consume(ID) || error("No name in typedef");
ret.name = name.value;
all_ws();
consume(OTHER, ";") || error("Unterminated typedef");
return ret;
};
var implements_ = function (store) {
all_ws(store, "pea");
var target = consume(ID);
if (!target) return;
var w = all_ws();
if (consume(ID, "implements")) {
var ret = {
type: "implements"
, target: target.value
};
all_ws();
var imp = consume(ID) || error("Incomplete implements statement");
ret["implements"] = imp.value;
all_ws();
consume(OTHER, ";") || error("No terminating ; for implements statement");
return ret;
}
else {
// rollback
tokens.unshift(w);
tokens.unshift(target);
}
};
var definition = function (store) {
return callback(store) ||
interface_(false, store) ||
partial(store) ||
dictionary(false, store) ||
exception(store) ||
enum_(store) ||
typedef(store) ||
implements_(store)
;
};
var definitions = function (store) {
if (!tokens.length) return [];
var defs = [];
while (true) {
var ea = extended_attrs(store ? defs : null)
, def = definition(store ? defs : null);
if (!def) {
if (ea.length) error("Stray extended attributes");
break;
}
def.extAttrs = ea;
defs.push(def);
}
return defs;
};
var res = definitions(opt.ws);
if (tokens.length) error("Unrecognised tokens");
return res;
};
var inNode = typeof module !== "undefined" && module.exports
, obj = {
parse: function (str, opt) {
if (!opt) opt = {};
var tokens = tokenise(str);
return parse(tokens, opt);
}
};
if (inNode) module.exports = obj;
else self.WebIDL2 = obj;
}());

View file

@ -0,0 +1,236 @@
(function () {
var write = function (ast, opt) {
var curPea = ""
, curTPea = ""
, opt = opt || {}
, noop = function (str) { return str; }
, optNames = "type".split(" ")
, context = []
;
for (var i = 0, n = optNames.length; i < n; i++) {
var o = optNames[i];
if (!opt[o]) opt[o] = noop;
}
var literal = function (it) {
return it.value;
};
var wsPea = function (it) {
curPea += it.value;
return "";
};
var wsTPea = function (it) {
curTPea += it.value;
return "";
};
var lineComment = function (it) {
return "//" + it.value + "\n";
};
var multilineComment = function (it) {
return "/*" + it.value + "*/";
};
var type = function (it) {
if (typeof it === "string") return opt.type(it); // XXX should maintain some context
if (it.union) return "(" + it.idlType.map(type).join(" or ") + ")";
var ret = "";
if (it.sequence) ret += "sequence<";
ret += type(it.idlType);
if (it.array) {
for (var i = 0, n = it.nullableArray.length; i < n; i++) {
var val = it.nullableArray[i];
if (val) ret += "?";
ret += "[]";
}
}
if (it.sequence) ret += ">";
if (it.nullable) ret += "?";
return ret;
};
var const_value = function (it) {
var tp = it. type;
if (tp === "boolean") return it.value ? "true" : "false";
else if (tp === "null") return "null";
else if (tp === "Infinity") return (it.negative ? "-" : "") + "Infinity";
else if (tp === "NaN") return "NaN";
else if (tp === "number") return it.value;
else return '"' + it.value + '"';
};
var argument = function (arg, pea) {
var ret = extended_attributes(arg.extAttrs, pea);
if (arg.optional) ret += "optional ";
ret += type(arg.idlType);
if (arg.variadic) ret += "...";
ret += " " + arg.name;
if (arg["default"]) ret += " = " + const_value(arg["default"]);
return ret;
};
var args = function (its) {
var res = ""
, pea = ""
;
for (var i = 0, n = its.length; i < n; i++) {
var arg = its[i];
if (arg.type === "ws") res += arg.value;
else if (arg.type === "ws-pea") pea += arg.value;
else {
res += argument(arg, pea);
if (i < n - 1) res += ",";
pea = "";
}
}
return res;
};
var make_ext_at = function (it) {
if (it["arguments"] === null) return it.name;
context.unshift(it);
var ret = it.name + "(" + (it["arguments"].length ? args(it["arguments"]) : "") + ")";
context.shift(); // XXX need to add more contexts, but not more than needed for ReSpec
return ret;
};
var extended_attributes = function (eats, pea) {
if (!eats || !eats.length) return "";
return "[" + eats.map(make_ext_at).join(", ") + "]" + pea;
};
var modifiers = "getter setter creator deleter legacycaller stringifier static".split(" ");
var operation = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
if (it.stringifier && !it.idlType) return "stringifier;";
for (var i = 0, n = modifiers.length; i < n; i++) {
var mod = modifiers[i];
if (it[mod]) ret += mod + " ";
}
ret += type(it.idlType) + " ";
if (it.name) ret += it.name;
ret += "(" + args(it["arguments"]) + ");";
return ret;
};
var attribute = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
if (it["static"]) ret += "static ";
if (it.stringifier) ret += "stringifier ";
if (it.readonly) ret += "readonly ";
if (it.inherit) ret += "inherit ";
ret += "attribute " + type(it.idlType) + " " + it.name + ";";
return ret;
};
var interface_ = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
if (it.partial) ret += "partial ";
ret += "interface " + it.name + " ";
if (it.inheritance) ret += ": " + it.inheritance + " ";
ret += "{" + iterate(it.members) + "};";
return ret;
};
var dictionary = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
if (it.partial) ret += "partial ";
ret += "dictionary " + it.name + " ";
ret += "{" + iterate(it.members) + "};";
return ret;
};
var field = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
ret += type(it.idlType) + " " + it.name;
if (it["default"]) ret += " = " + const_value(it["default"]);
ret += ";";
return ret;
};
var exception = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
ret += "exception " + it.name + " ";
if (it.inheritance) ret += ": " + it.inheritance + " ";
ret += "{" + iterate(it.members) + "};";
return ret;
};
var const_ = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
return ret + "const " + type(it.idlType) + " " + it.name + " = " + const_value(it.value) + ";";
};
var typedef = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
ret += "typedef " + extended_attributes(it.typeExtAttrs, curTPea);
curTPea = "";
return ret + type(it.idlType) + " " + it.name + ";";
};
var implements_ = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
return ret + it.target + " implements " + it["implements"] + ";";
};
var callback = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
return ret + "callback " + it.name + " = " + type(it.idlType) +
"(" + args(it["arguments"]) + ");";
};
var enum_ = function (it) {
var ret = extended_attributes(it.extAttrs, curPea);
curPea = "";
ret += "enum " + it.name + " {";
for (var i = 0, n = it.values.length; i < n; i++) {
var v = it.values[i];
if (typeof v === "string") ret += '"' + v + '"';
else if (v.type === "ws") ret += v.value;
else if (v.type === ",") ret += ",";
}
return ret + "};";
};
var table = {
ws: literal
, "ws-pea": wsPea
, "ws-tpea": wsTPea
, "line-comment": lineComment
, "multiline-comment": multilineComment
, "interface": interface_
, operation: operation
, attribute: attribute
, dictionary: dictionary
, field: field
, exception: exception
, "const": const_
, typedef: typedef
, "implements": implements_
, callback: callback
, "enum": enum_
};
var dispatch = function (it) {
return table[it.type](it);
};
var iterate = function (things) {
if (!things) return;
var ret = "";
for (var i = 0, n = things.length; i < n; i++) ret += dispatch(things[i]);
return ret;
};
return iterate(ast);
};
var inNode = typeof module !== "undefined" && module.exports
, obj = {
write: function (ast, opt) {
if (!opt) opt = {};
return write(ast, opt);
}
};
if (inNode) module.exports = obj;
else window.WebIDL2Writer = obj;
}());

View file

@ -0,0 +1,22 @@
{
"name": "webidl2"
, "description": "A WebIDL Parser"
, "version": "2.0.4"
, "author": "Robin Berjon <robin@berjon.com>"
, "license": "MIT"
, "dependencies": {
}
, "devDependencies": {
"mocha": "1.7.4"
, "expect.js": "0.2.0"
, "underscore": "1.4.3"
, "jsondiffpatch": "0.0.5"
, "benchmark": "*"
, "microtime": "*"
}
, "scripts": {
"test": "mocha"
}
, "repository": "git://github.com/darobin/webidl2.js"
, "main": "index"
}

View file

@ -0,0 +1,42 @@
// NOTES:
// - the errors actually still need to be reviewed to check that they
// are fully correct interpretations of the IDLs
var wp = process.env.JSCOV ? require("../lib-cov/webidl2") : require("../lib/webidl2")
, expect = require("expect.js")
, pth = require("path")
, fs = require("fs")
;
describe("Parses all of the invalid IDLs to check that they blow up correctly", function () {
var dir = pth.join(__dirname, "invalid/idl")
, skip = {}
, idls = fs.readdirSync(dir)
.filter(function (it) { return (/\.w?idl$/).test(it) && !skip[it]; })
.map(function (it) { return pth.join(dir, it); })
, errors = idls.map(function (it) { return pth.join(__dirname, "invalid", "json", pth.basename(it).replace(/\.w?idl/, ".json")); })
;
for (var i = 0, n = idls.length; i < n; i++) {
var idl = idls[i], error = JSON.parse(fs.readFileSync(errors[i], "utf8"));
var func = (function (idl, err) {
return function () {
var error;
try {
var ast = wp.parse(fs.readFileSync(idl, "utf8"));
console.log(JSON.stringify(ast, null, 4));
}
catch (e) {
error = e;
}
finally {
expect(error).to.be.ok();
expect(error.message).to.equal(err.message);
expect(error.line).to.equal(err.line);
}
};
}(idl, error));
it("should produce the right error for " + idl, func);
}
});

View file

@ -0,0 +1 @@
enum foo { 1, 2, 3};

View file

@ -0,0 +1,25 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
module gfx {
module geom {
interface Shape { /* ... */ };
interface Rectangle : Shape { /* ... */ };
interface Path : Shape { /* ... */ };
};
interface GraphicsContext {
void fillShape(geom::Shape s);
void strokeShape(geom::Shape s);
};
};
module gui {
interface Widget { /* ... */ };
interface Window : Widget {
gfx::GraphicsContext getGraphicsContext();
};
interface Button : Widget { /* ... */ };
};

View file

@ -0,0 +1,3 @@
interface NonNullable {
attribute any? foo;
};

View file

@ -0,0 +1,5 @@
interface Foo {};
interface NonNullable {
attribute Foo?? foo;
};

View file

@ -0,0 +1,18 @@
// getraises and setraises are not longer valid Web IDL
interface Person {
// An attribute that can raise an exception if it is set to an invalid value.
attribute DOMString name setraises (InvalidName);
// An attribute whose value cannot be assigned to, and which can raise an
// exception some circumstances.
readonly attribute DOMString petName getraises (NoSuchPet);
};
exception SomeException {
};
interface ExceptionThrower {
// This attribute always throws a SomeException and never returns a value.
attribute long valueOf getraises(SomeException);
};

View file

@ -0,0 +1,2 @@
// scoped names are no longer valid in WebIDL
typedef gfx::geom::geom2d::Point Point;

View file

@ -0,0 +1,3 @@
interface sequenceAsAttribute {
attribute sequence<short> invalid;
};

View file

@ -0,0 +1,8 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
// omittable is no longer a recognized keywoard as of 20110905
interface Dictionary {
readonly attribute unsigned long propertyCount;
omittable getter float getProperty(DOMString propertyName);
omittable setter void setProperty(DOMString propertyName, float propertyValue);
};

View file

@ -0,0 +1,3 @@
interface Util {
const DOMString hello = "world";
};

View file

@ -0,0 +1,4 @@
{
"message": "Unexpected value in enum"
, "line": 1
}

View file

@ -0,0 +1,4 @@
{
"message": "Unrecognised tokens"
, "line": 2
}

View file

@ -0,0 +1,4 @@
{
"message": "Type any cannot be made nullable"
, "line": 2
}

View file

@ -0,0 +1,4 @@
{
"message": "Can't nullable more than once"
, "line": 4
}

View file

@ -0,0 +1,4 @@
{
"message": "Unterminated attribute"
, "line": 5
}

View file

@ -0,0 +1,4 @@
{
"message": "No name in typedef"
, "line": 2
}

View file

@ -0,0 +1,4 @@
{
"message": "Attributes cannot accept sequence types"
, "line": 2
}

View file

@ -0,0 +1,4 @@
{
"message": "Invalid operation"
, "line": 6
}

View file

@ -0,0 +1,4 @@
{
"message": "No value for const"
, "line": 2
}

View file

@ -0,0 +1 @@
--reporter spec

View file

@ -0,0 +1,36 @@
var wp = process.env.JSCOV ? require("../lib-cov/webidl2") : require("../lib/webidl2")
, expect = require("expect.js")
, pth = require("path")
, fs = require("fs")
, jdp = require("jsondiffpatch")
, debug = true
;
describe("Parses all of the IDLs to produce the correct ASTs", function () {
var dir = pth.join(__dirname, "syntax/idl")
, skip = {} // use if we have a broken test
, idls = fs.readdirSync(dir)
.filter(function (it) { return (/\.widl$/).test(it) && !skip[it]; })
.map(function (it) { return pth.join(dir, it); })
, jsons = idls.map(function (it) { return pth.join(__dirname, "syntax/json", pth.basename(it).replace(".widl", ".json")); })
;
for (var i = 0, n = idls.length; i < n; i++) {
var idl = idls[i], json = jsons[i];
var func = (function (idl, json) {
return function () {
try {
var diff = jdp.diff(JSON.parse(fs.readFileSync(json, "utf8")),
wp.parse(fs.readFileSync(idl, "utf8")));
if (diff && debug) console.log(JSON.stringify(diff, null, 4));
expect(diff).to.be(undefined);
}
catch (e) {
console.log(e.toString());
throw e;
}
};
}(idl, json));
it("should produce the same AST for " + idl, func);
}
});

View file

@ -0,0 +1,6 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface B {
void g();
void g(B b);
void g([AllowAny] DOMString s);
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[Constructor]
interface LotteryResults {
readonly attribute unsigned short[][] numbers;
};

View file

@ -0,0 +1,14 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
exception InvalidName {
DOMString reason;
};
exception NoSuchPet { };
interface Person {
// A simple attribute that can be set to any value the range an unsigned
// short can take.
attribute unsigned short age;
};

View file

@ -0,0 +1,5 @@
callback AsyncOperationCallback = void (DOMString status);
callback interface EventHandler {
void eventOccurred(DOMString details);
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface NumberQuadrupler {
// This operation simply returns four times the given number x.
legacycaller float compute(float x);
};

View file

@ -0,0 +1,18 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Util {
const boolean DEBUG = false;
const short negative = -1;
const octet LF = 10;
const unsigned long BIT_MASK = 0x0000fc00;
const float AVOGADRO = 6.022e23;
const unrestricted float sobig = Infinity;
const unrestricted double minusonedividedbyzero = -Infinity;
const short notanumber = NaN;
};
exception Error {
const short ERR_UNKNOWN = 0;
const short ERR_OUT_OF_MEMORY = 1;
short errorCode;
};

View file

@ -0,0 +1,9 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[Constructor,
Constructor(float radius)]
interface Circle {
attribute float r;
attribute float cx;
attribute float cy;
readonly attribute float circumference;
};

View file

@ -0,0 +1,9 @@
dictionary PaintOptions {
DOMString? fillPattern = "black";
DOMString? strokePattern = null;
Point position;
};
dictionary WetPaintOptions : PaintOptions {
float hydrometry;
};

View file

@ -0,0 +1,11 @@
// Extracted from Web IDL editors draft May 31 2011
dictionary PaintOptions {
DOMString? fillPattern = "black";
DOMString? strokePattern = null;
Point position;
};
partial dictionary A {
long h;
long d;
};

View file

@ -0,0 +1,33 @@
/**
* \brief Testing documentation features
*
* This is a
* single paragraph
*
* <p>This is valid.</p>
* <p>This is <em>valid</em>.</p>
* <p>This is <b>valid</b>.</p>
* <p>This is <a href=''>valid</a>.</p>
* <ul>
* <li>This</li>
* <li>is</li>
* <li>valid</li>
* </ul>
* <dl>
* <dt>This</dt>
* <dd>valid</dd>
* </dl>
* <table>
* <tr>
* <td>this</td>
* <td>is</td>
* </tr>
* <tr>
* <td>valid</td>
* </tr>
* </table>
* <p>This is <br> valid.</p>
* <p>This is <br /> valid.</p>
* <p>This is <br/> valid.</p>
*/
interface Documentation {};

View file

@ -0,0 +1,34 @@
/**
* \brief Testing documentation features
*
* This is a
* single paragraph
*
* <p>This is valid.</p>
* <p>This is <em>valid</em>.</p>
* <p>This is <b>valid</b>.</p>
* <p>This is <a href=''>valid</a>.</p>
* <ul>
* <li>This</li>
* <li>is</li>
* <li>valid</li>
* </ul>
* <dl>
* <dt>This</dt>
* <dd>valid</dd>
* </dl>
* <table>
* <tr>
* <td>this</td>
* <td>is</td>
* </tr>
* <tr>
* <td>valid</td>
* </tr>
* </table>
* <p>This is <br> valid.</p>
* <p>This is <br /> valid.</p>
* <p>This is <br/> valid.</p>
* <p><img src="foo.png" alt="Valid"/></p>
*/
interface Documentation {};

View file

@ -0,0 +1,8 @@
enum MealType { "rice", "noodles", "other" };
interface Meal {
attribute MealType type;
attribute float size; // in grams
void initialize(MealType type, float size);
};

View file

@ -0,0 +1,18 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Dictionary {
readonly attribute unsigned long propertyCount;
getter float getProperty(DOMString propertyName);
setter void setProperty(DOMString propertyName, float propertyValue);
};
interface Dictionary {
readonly attribute unsigned long propertyCount;
float getProperty(DOMString propertyName);
void setProperty(DOMString propertyName, float propertyValue);
getter float (DOMString propertyName);
setter void (DOMString propertyName, float propertyValue);
};

View file

@ -0,0 +1,7 @@
// from http://lists.w3.org/Archives/Public/public-script-coord/2010OctDec/0112.html
exception DOMException {
unsigned short code;
};
exception HierarchyRequestError : DOMException { };
exception NoModificationAllowedError : DOMException { };

View file

@ -0,0 +1,8 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Dahut {
attribute DOMString type;
};
exception SomeException {
};

View file

@ -0,0 +1,17 @@
interface Foo {
Promise<ResponsePromise<sequence<DOMString?>>> bar();
};
// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-08
interface ServiceWorkerClients {
Promise<Client[]?> getServiced();
Promise<any> reloadAll();
};
// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-13
interface FetchEvent : Event {
ResponsePromise<any> default();
};

View file

@ -0,0 +1,7 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Dictionary {
readonly attribute unsigned long propertyCount;
getter float (DOMString propertyName);
setter void (DOMString propertyName, float propertyValue);
};

View file

@ -0,0 +1,44 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
// Typedef identifier: "number"
// Qualified name: "::framework::number"
typedef float number;
// Exception identifier: "FrameworkException"
// Qualified name: "::framework::FrameworkException"
exception FrameworkException {
// Constant identifier: "ERR_NOT_FOUND"
// Qualified name: "::framework::FrameworkException::ERR_NOT_FOUND"
const long ERR_NOT_FOUND = 1;
// Exception field identifier: "code"
long code;
};
// Interface identifier: "System"
// Qualified name: "::framework::System"
interface System {
// Operation identifier: "createObject"
// Operation argument identifier: "interface"
object createObject(DOMString _interface);
// Operation has no identifier; it declares a getter.
getter DOMString (DOMString keyName);
};
// Interface identifier: "TextField"
// Qualified name: "::framework::gui::TextField"
interface TextField {
// Attribute identifier: "const"
attribute boolean _const;
// Attribute identifier: "value"
attribute DOMString? _value;
};
interface Foo {
void op(object interface);
};

View file

@ -0,0 +1,14 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Node {
readonly attribute unsigned short nodeType;
// ...
};
interface EventTarget {
void addEventListener(DOMString type,
EventListener listener,
boolean useCapture);
// ...
};
Node implements EventTarget;

View file

@ -0,0 +1,12 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface OrderedMap {
readonly attribute unsigned long size;
getter any getByIndex(unsigned long index);
setter void setByIndex(unsigned long index, any value);
deleter void removeByIndex(unsigned long index);
getter any get(DOMString name);
setter creator void set(DOMString name, any value);
deleter void remove(DOMString name);
};

View file

@ -0,0 +1,16 @@
interface Animal {
// A simple attribute that can be set to any string value.
readonly attribute DOMString name;
};
interface Person : Animal {
// An attribute whose value cannot be assigned to.
readonly attribute unsigned short age;
// An attribute that can raise an exception if it is set to an invalid value.
// Its getter behavior is inherited from Animal, and need not be specified
// the description of Person.
inherit attribute DOMString name;
};

View file

@ -0,0 +1,12 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Animal {
attribute DOMString name;
};
interface Human : Animal {
attribute Dog pet;
};
interface Dog : Animal {
attribute Human owner;
};

View file

@ -0,0 +1,35 @@
interface SessionManager {
Session getSessionForUser(DOMString username);
readonly attribute unsigned long sessionCount;
Session iterator;
};
interface Session {
readonly attribute DOMString username;
// ...
};
interface SessionManager2 {
Session2 getSessionForUser(DOMString username);
readonly attribute unsigned long sessionCount;
Session2 iterator = SessionIterator;
};
interface Session2 {
readonly attribute DOMString username;
// ...
};
interface SessionIterator {
readonly attribute unsigned long remainingSessions;
};
interface NodeList {
Node iterator = NodeIterator;
};
interface NodeIterator {
Node iterator object;
};

View file

@ -0,0 +1,5 @@
// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-06
[MapClass(DOMString, DOMString)]
interface HeaderMap {
};

View file

@ -0,0 +1,6 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[NamedConstructor=Audio,
NamedConstructor=Audio(DOMString src)]
interface HTMLAudioElement : HTMLMediaElement {
// ...
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[NoInterfaceObject]
interface Query {
any lookupEntry(unsigned long key);
};

View file

@ -0,0 +1,9 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface MyConstants {
const boolean? ARE_WE_THERE_YET = false;
};
interface Node {
readonly attribute DOMString? namespaceURI;
// ...
};

View file

@ -0,0 +1,13 @@
// Extracted from WebIDL spec 2011-05-23
interface A {
// ...
};
interface B {
// ...
};
interface C {
void f(A? x);
void f(B? x);
};

View file

@ -0,0 +1,4 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface ColorCreator {
object createColor(float v1, float v2, float v3, optional float alpha = 3.5);
};

View file

@ -0,0 +1,20 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface A {
// ...
};
interface B {
// ...
};
interface C {
void f(A x);
void f(B x);
};
interface A {
/* f1 */ void f(DOMString a);
/* f2 */ void f([AllowAny] DOMString a, DOMString b, float... c);
/* f3 */ void f();
/* f4 */ void f(long a, DOMString b, optional DOMString c, float... d);
};

View file

@ -0,0 +1,6 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[OverrideBuiltins]
interface StringMap2 {
readonly attribute unsigned long length;
getter DOMString lookup(DOMString key);
};

View file

@ -0,0 +1,7 @@
interface Foo {
attribute DOMString bar;
};
partial interface Foo {
attribute DOMString quux;
};

View file

@ -0,0 +1,19 @@
interface Primitives {
attribute boolean truth;
attribute byte character;
attribute octet value;
attribute short number;
attribute unsigned short positive;
attribute long big;
attribute unsigned long bigpositive;
attribute long long bigbig;
attribute unsigned long long bigbigpositive;
attribute float real;
attribute double bigreal;
attribute unrestricted float realwithinfinity;
attribute unrestricted double bigrealwithinfinity;
attribute DOMString string;
attribute ByteString bytes;
attribute Date date;
attribute RegExp regexp;
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[PrototypeRoot]
interface Node {
readonly attribute unsigned short nodeType;
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Person {
[PutForwards=full] readonly attribute Name name;
attribute unsigned short age;
};

View file

@ -0,0 +1,17 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Dimensions {
attribute unsigned long width;
attribute unsigned long height;
};
exception NoPointerDevice { };
interface Button {
// An operation that takes no arguments, returns a boolean
boolean isMouseOver();
// Overloaded operations.
void setDimensions(Dimensions size);
void setDimensions(unsigned long width, unsigned long height);
};

View file

@ -0,0 +1,5 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Counter {
[Replaceable] readonly attribute unsigned long value;
void increment();
};

View file

@ -0,0 +1,12 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
// edited to remove sequence as attributes, now invalid
interface Canvas {
void drawPolygon(sequence<float> coordinates);
sequence<float> getInflectionPoints();
// ...
};
// Make sure sequence can still be registered as a type.
interface Foo {
sequence bar();
};

View file

@ -0,0 +1,64 @@
interface Transaction {
readonly attribute Account from;
readonly attribute Account to;
readonly attribute float amount;
readonly attribute DOMString description;
readonly attribute unsigned long number;
serializer;
};
interface Account {
attribute DOMString name;
attribute unsigned long number;
serializer DOMString serialize();
};
interface Transaction2 {
readonly attribute Account2 from;
readonly attribute Account2 to;
readonly attribute float amount;
readonly attribute DOMString description;
readonly attribute unsigned long number;
serializer = { from, to, amount, description };
};
interface Account2 {
attribute DOMString name;
attribute unsigned long number;
serializer = number;
};
interface Account3 {
attribute DOMString name;
attribute unsigned long number;
serializer = { attribute };
};
interface Account4 {
getter object getItem(unsigned long index);
serializer = { getter };
};
interface Account5 : Account {
attribute DOMString secondname;
serializer = { inherit, secondname };
};
interface Account6 : Account {
attribute DOMString secondname;
serializer = { inherit, attribute };
};
interface Account7 {
attribute DOMString name;
attribute unsigned long number;
serializer = [ name, number ];
};
interface Account8 {
getter object getItem(unsigned long index);
serializer = [ getter ];
};

View file

@ -0,0 +1,11 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
interface Point { /* ... */ };
interface Circle {
attribute float cx;
attribute float cy;
attribute float radius;
static readonly attribute long triangulationCount;
static Point triangulate(Circle c1, Circle c2, Circle c3);
};

View file

@ -0,0 +1,6 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[Constructor]
interface Student {
attribute unsigned long id;
stringifier attribute DOMString name;
};

View file

@ -0,0 +1,9 @@
// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
[Constructor]
interface Student {
attribute unsigned long id;
attribute DOMString? familyName;
attribute DOMString givenName;
stringifier DOMString ();
};

Some files were not shown because too many files have changed in this diff Show more