Source code

Revision control

Copy as Markdown

Other Tools

/**
* This script implements the $262 host provider API (test262-provider.js) for Test262
* tests running in WPT. It provides the necessary environment for Test262 tests to
* execute and communicate with the WPT runner.
*
* See Test262 INTERPRETING.md for the specification of this API:
*/
function installAPI(global) {
global.$262 = {
/**
* Creates a new ECMAScript Realm (iframe), defines this API on it,
* and returns the $262 object of the new realm.
*/
createRealm: function() {
const iframe = global.document.createElement('iframe');
iframe.style.display = 'none';
const container = global.document.body;
if (!container) {
// Should not happen.
throw new Error('Test262 Host API: createRealm() called before document.body was available.');
}
container.appendChild(iframe);
return installAPI(iframe.contentWindow);
},
/**
* Executes a string as an ECMAScript script.
*/
evalScript: function(src) {
const script = global.document.createElement('script');
script.text = src;
window.__test262_evalScript_error_ = undefined;
window.__test262_evalScript_active_ = true;
try {
global.document.head.appendChild(script);
} finally {
window.__test262_evalScript_active_ = false;
}
// Errors in the above appendChild bubble up to the global error handler.
// test262-reporter.js stashes them in a global var for rethrowing.
if (window.__test262_evalScript_error_) {
const err = window.__test262_evalScript_error_;
window.__test262_evalScript_error_ = undefined;
throw err;
}
},
/**
* Detaches an ArrayBuffer.
*/
detachArrayBuffer: function(buffer) {
// Transfer and thus detach the buffer.
postMessage(null, '*', [buffer]);
},
/**
* Triggers garbage collection if supported by the host.
*/
gc: function() {
try {
TestUtils.gc();
} catch (e) {
throw new Error('Test262 Host API: gc() failed or not supported: ' + (e.message || e));
}
},
/**
* Reference to the %AbstractModuleSource% constructor.
*/
AbstractModuleSource: function() {
throw new Error('Test262 Host API: AbstractModuleSource not available');
},
agent: (function () {
const workers = [];
let i32a = null;
const pendingReports = [];
// Agents call Atomics.wait on this location to sleep.
const SLEEP_LOC = 0;
// 1 if the started worker is ready, 0 otherwise.
const START_LOC = 1;
// The number of workers that have received the broadcast.
const BROADCAST_LOC = 2;
// Each worker has a count of outstanding reports; worker N uses memory
// location [WORKER_REPORT_LOC + N].
const WORKER_REPORT_LOC = 3;
function workerScript(script) {
return `
let index;
let i32a = null;
let broadcasts = [];
let pendingReceiver = null;
function handleBroadcast() {
if (pendingReceiver && broadcasts.length > 0) {
pendingReceiver.apply(null, broadcasts.shift());
pendingReceiver = null;
}
};
self.onmessage = function({data:msg}) {
switch (msg.kind) {
case 'start':
i32a = msg.i32a;
index = msg.index;
(0, eval)(\`${script}\`);
break;
case 'broadcast':
Atomics.add(i32a, ${BROADCAST_LOC}, 1);
broadcasts.push([msg.sab, msg.id]);
handleBroadcast();
break;
}
};
self.$262 = {
agent: {
receiveBroadcast(receiver) {
pendingReceiver = receiver;
handleBroadcast();
},
report(msg) {
postMessage(String(msg));
Atomics.add(i32a, ${WORKER_REPORT_LOC} + index, 1);
},
sleep(s) { Atomics.wait(i32a, ${SLEEP_LOC}, 0, s); },
leaving() {},
monotonicNow() {
return performance.now();
}
}
};`;
}
const agent = {
start(script) {
if (i32a === null) {
i32a = new Int32Array(new SharedArrayBuffer(256));
}
const w = new Worker(URL.createObjectURL(new Blob([workerScript(script)], {type: 'text/javascript'})));
w.index = workers.length;
w.reports = [];
w.onmessage = function(e) {
w.reports.push(e.data);
};
w.postMessage({kind: 'start', i32a: i32a, index: w.index});
workers.push(w);
},
broadcast(sab, id) {
if (!(sab instanceof SharedArrayBuffer)) {
throw new TypeError('sab must be a SharedArrayBuffer.');
}
Atomics.store(i32a, BROADCAST_LOC, 0);
for (const w of workers) {
w.postMessage({kind: 'broadcast', sab: sab, id: id|0});
}
while (Atomics.load(i32a, BROADCAST_LOC) != workers.length) {}
},
getReport() {
for (const w of workers) {
while (Atomics.load(i32a, WORKER_REPORT_LOC + w.index) > 0) {
const msg = w.reports.shift();
if (msg !== undefined) {
pendingReports.push(msg);
}
Atomics.sub(i32a, WORKER_REPORT_LOC + w.index, 1);
}
}
return pendingReports.shift() || null;
},
sleep(s) { Atomics.wait(i32a, SLEEP_LOC, 0, s); },
monotonicNow() {
return performance.now();
}
};
return agent;
})(),
global: global
};
// Ensure print is available in this realm if it's available in the parent.
global.print = window.print;
// Placeholder $DONE for non-async tests.
// Async tests will have this overwritten by doneprintHandle.js.
// If called in a non-async test, we throw to fail fast and loudly:
// - If err is passed, throwing it ensures the test fails with that error.
// - If no err is passed, it signals that a sync test incorrectly tried to use async signaling.
global.$DONE = function(err) {
if (err) {
throw err;
}
throw new Error('Test262 Host API: $DONE called in a non-async test.');
};
return global.$262;
}
installAPI(window);