Add window.setTimeout. Convert all content node bindings to use RCU write pointer. Store a bundle of Node + NodeScope in each node JS object.

This commit is contained in:
Josh Matthews 2012-08-22 16:29:53 -07:00
parent d4b2810af1
commit 4db345c3b3
9 changed files with 184 additions and 54 deletions

View file

@ -4,7 +4,7 @@
"]
export ContentTask;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer;
export PingMsg, PongMsg;
import std::arc::{arc, clone};
@ -24,7 +24,7 @@ import layout::layout_task;
import layout_task::{LayoutTask, BuildMsg};
import jsrt = js::rust::rt;
import js::rust::methods;
import js::rust::{cx, methods};
import js::global::{global_class, debug_fns};
import either::{Either, Left, Right};
@ -40,9 +40,16 @@ import url_to_str = std::net::url::to_str;
import util::url::make_url;
import task::{task, SingleThreaded};
import js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
import js::JSVAL_NULL;
import js::jsapi::jsval;
import js::jsapi::bindgen::JS_CallFunctionValue;
import ptr::null;
enum ControlMsg {
ParseMsg(url),
ExecuteMsg(url),
Timer(~dom::bindings::window::TimerData),
ExitMsg
}
@ -79,6 +86,7 @@ struct Content<C:Compositor> {
let scope: NodeScope;
let jsrt: jsrt;
let cx: cx;
let mut document: option<@Document>;
let mut window: option<@Window>;
@ -86,6 +94,8 @@ struct Content<C:Compositor> {
let resource_task: ResourceTask;
let compartment: option<compartment>;
new(layout_task: LayoutTask, +compositor: C, from_master: Port<ControlMsg>,
resource_task: ResourceTask) {
self.layout_task = layout_task;
@ -95,6 +105,7 @@ struct Content<C:Compositor> {
self.scope = NodeScope();
self.jsrt = jsrt();
self.cx = self.jsrt.cx();
self.document = none;
self.window = none;
@ -103,6 +114,13 @@ struct Content<C:Compositor> {
self.compositor.add_event_listener(self.event_port.chan());
self.resource_task = resource_task;
self.cx.set_default_options_and_version();
self.cx.set_logging_error_reporter();
self.compartment = match self.cx.new_compartment(global_class) {
ok(c) => some(c),
err(()) => none
};
}
fn start() {
@ -140,30 +158,42 @@ struct Content<C:Compositor> {
#debug["js_scripts: %?", js_scripts];
let document = Document(root, css_rules);
let window = Window();
let document = Document(root, self.scope, css_rules);
let window = Window(self.from_master);
self.relayout(document, &url);
self.document = some(@document);
self.window = some(@window);
self.doc_url = some(copy url);
//XXXjdm it was easier to duplicate the relevant ExecuteMsg code;
// they should be merged somehow in the future.
let compartment = option::expect(self.compartment, ~"TODO error checking");
compartment.define_functions(debug_fns);
define_bindings(*compartment,
option::get(self.document),
option::get(self.window));
for vec::each(js_scripts) |bytes| {
let cx = self.jsrt.cx();
cx.set_default_options_and_version();
cx.set_logging_error_reporter();
cx.new_compartment(global_class).chain(|compartment| {
compartment.define_functions(debug_fns);
define_bindings(*compartment, option::get(self.document),
option::get(self.window));
cx.evaluate_script(compartment.global_obj, bytes, ~"???", 1u)
});
self.cx.evaluate_script(compartment.global_obj, bytes, ~"???", 1u);
}
return true;
}
Timer(timerData) => {
let compartment = option::expect(self.compartment, ~"TODO error checking");
let thisValue = if timerData.args.len() > 0 {
RUST_JSVAL_TO_OBJECT(unsafe { timerData.args.shift() })
} else {
compartment.global_obj.ptr
};
let _rval = JSVAL_NULL;
//TODO: support extra args. requires passing a *jsval argv
JS_CallFunctionValue(self.cx.ptr, thisValue, timerData.funval,
0, null(), ptr::addr_of(_rval));
self.relayout(*option::get(self.document), &option::get(self.doc_url));
return true;
}
ExecuteMsg(url) => {
#debug["content: Received url `%s` to execute", url_to_str(url)];
@ -172,13 +202,9 @@ struct Content<C:Compositor> {
println(#fmt["Error opening %s: %s", url_to_str(url), msg]);
}
ok(bytes) => {
let cx = self.jsrt.cx();
cx.set_default_options_and_version();
cx.set_logging_error_reporter();
cx.new_compartment(global_class).chain(|compartment| {
compartment.define_functions(debug_fns);
cx.evaluate_script(compartment.global_obj, bytes, url.path, 1u)
});
let compartment = option::expect(self.compartment, ~"TODO error checking");
compartment.define_functions(debug_fns);
self.cx.evaluate_script(compartment.global_obj, bytes, url.path, 1u);
}
}
return true;

View file

@ -14,20 +14,44 @@ import ptr::null;
import bindings;
import std::arc::arc;
import style::Stylesheet;
import comm::{Port, Chan};
import content::content_task::{ControlMsg, Timer};
enum TimerControlMsg {
Fire(~dom::bindings::window::TimerData),
Close
}
struct Window {
let unused: int;
new() {
self.unused = 0;
let timer_chan: Chan<TimerControlMsg>;
new(content_port: Port<ControlMsg>) {
let content_chan = chan(content_port);
self.timer_chan = do task::spawn_listener |timer_port: Port<TimerControlMsg>| {
loop {
match timer_port.recv() {
Close => break,
Fire(td) => {
content_chan.send(Timer(td));
}
}
}
};
}
drop {
self.timer_chan.send(Close);
}
}
struct Document {
let root: Node;
let scope: NodeScope;
let css_rules: arc<Stylesheet>;
new(root: Node, -css_rules: Stylesheet) {
new(root: Node, scope: NodeScope, -css_rules: Stylesheet) {
self.root = root;
self.scope = scope;
self.css_rules = arc(css_rules);
}
}

View file

@ -61,8 +61,10 @@ enum Element = int;
}*/
extern fn getDocumentElement(cx: *JSContext, obj: *JSObject, _id: jsid, rval: *mut jsval) -> JSBool unsafe {
let node = (*unwrap(obj)).payload.root;
*rval = RUST_OBJECT_TO_JSVAL(node::create(cx, node).ptr);
let box = unwrap(obj);
let node = (*box).payload.root;
let scope = (*box).payload.scope;
*rval = RUST_OBJECT_TO_JSVAL(node::create(cx, node, scope).ptr);
return 1;
}

View file

@ -9,7 +9,8 @@ import js::jsapi::bindgen::*;
import js::glue::bindgen::*;
import js::crust::{JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ConvertStub};
import dom::base::{Node, Element};
import dom::base::{Node, NodeScope, Element};
import node::NodeBundle;
import utils::{rust_box, squirrel_away_unique, get_compartment, domstring_to_jsval, str};
import libc::c_uint;
import ptr::null;
@ -22,7 +23,7 @@ extern fn finalize(_fop: *JSFreeOp, obj: *JSObject) {
#debug("element finalize!");
unsafe {
let val = JS_GetReservedSlot(obj, 0);
let _node: ~Node = unsafe::reinterpret_cast(RUST_JSVAL_TO_PRIVATE(val));
let _node: ~NodeBundle = unsafe::reinterpret_cast(RUST_JSVAL_TO_PRIVATE(val));
}
}
@ -62,7 +63,8 @@ fn init(compartment: bare_compartment) {
extern fn HTMLImageElement_getWidth(_cx: *JSContext, obj: *JSObject, _id: jsid,
rval: *mut jsval) -> JSBool unsafe {
let width = (*unwrap(obj)).payload.read(|nd| {
let bundle = unwrap(obj);
let width = (*bundle).payload.scope.write((*bundle).payload.node, |nd| {
match nd.kind {
~Element(ed) => {
match ed.kind {
@ -80,7 +82,8 @@ extern fn HTMLImageElement_getWidth(_cx: *JSContext, obj: *JSObject, _id: jsid,
extern fn HTMLImageElement_setWidth(_cx: *JSContext, obj: *JSObject, _id: jsid,
_strict: JSBool, vp: *jsval) -> JSBool unsafe {
let width = (*unwrap(obj)).payload.read(|nd| {
let bundle = unwrap(obj);
do (*bundle).payload.scope.write((*bundle).payload.node) |nd| {
match nd.kind {
~Element(ed) => {
match ed.kind {
@ -91,14 +94,15 @@ extern fn HTMLImageElement_setWidth(_cx: *JSContext, obj: *JSObject, _id: jsid,
}
_ => fail ~"why is this not an element?"
}
});
};
return 1;
}
extern fn getTagName(cx: *JSContext, obj: *JSObject, _id: jsid, rval: *mut jsval)
-> JSBool {
unsafe {
(*unwrap(obj)).payload.read(|nd| {
let bundle = unwrap(obj);
do (*bundle).payload.scope.write((*bundle).payload.node) |nd| {
match nd.kind {
~Element(ed) => {
let s = str(copy ed.tag_name);
@ -109,13 +113,13 @@ extern fn getTagName(cx: *JSContext, obj: *JSObject, _id: jsid, rval: *mut jsval
*rval = JSVAL_NULL;
}
}
});
};
}
return 1;
}
fn create(cx: *JSContext, node: Node) -> jsobj unsafe {
let proto = node.read(|nd| {
fn create(cx: *JSContext, node: Node, scope: NodeScope) -> jsobj unsafe {
let proto = scope.write(node, |nd| {
match nd.kind {
~Element(ed) => {
match ed.kind {
@ -138,7 +142,8 @@ fn create(cx: *JSContext, node: Node) -> jsobj unsafe {
(*compartment).global_obj.ptr));
unsafe {
let raw_ptr: *libc::c_void = unsafe::reinterpret_cast(squirrel_away_unique(~node));
let raw_ptr: *libc::c_void =
unsafe::reinterpret_cast(squirrel_away_unique(~NodeBundle(node, scope)));
JS_SetReservedSlot(obj.ptr, 0, RUST_PRIVATE_TO_JSVAL(raw_ptr));
}
return obj;

View file

@ -9,7 +9,7 @@ import js::jsapi::bindgen::*;
import js::glue::bindgen::*;
import js::crust::{JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ConvertStub};
import dom::base::{Node, Element, Text};
import dom::base::{Node, NodeScope, Element, Text};
import utils::{rust_box, squirrel_away_unique, get_compartment, domstring_to_jsval, str};
import libc::c_uint;
import ptr::null;
@ -35,11 +35,11 @@ fn init(compartment: bare_compartment) {
});
}
fn create(cx: *JSContext, node: Node) -> jsobj unsafe {
do node.read |nd| {
fn create(cx: *JSContext, node: Node, scope: NodeScope) -> jsobj unsafe {
do scope.write(node) |nd| {
match nd.kind {
~Element(ed) => {
element::create(cx, node)
element::create(cx, node, scope)
}
~Text(s) => {
@ -49,41 +49,53 @@ fn create(cx: *JSContext, node: Node) -> jsobj unsafe {
}
}
unsafe fn unwrap(obj: *JSObject) -> *rust_box<Node> {
struct NodeBundle {
let node: Node;
let scope: NodeScope;
new(n: Node, s: NodeScope) {
self.node = n;
self.scope = s;
}
}
unsafe fn unwrap(obj: *JSObject) -> *rust_box<NodeBundle> {
let val = JS_GetReservedSlot(obj, 0);
unsafe::reinterpret_cast(RUST_JSVAL_TO_PRIVATE(val))
}
extern fn getFirstChild(cx: *JSContext, obj: *JSObject, _id: jsid, rval: *mut jsval) -> JSBool {
unsafe {
(*unwrap(obj)).payload.read(|nd| {
let bundle = unwrap(obj);
do (*bundle).payload.scope.write((*bundle).payload.node) |nd| {
match nd.tree.first_child {
some(n) => {
let obj = create(cx, n).ptr;
let obj = create(cx, n, (*bundle).payload.scope).ptr;
*rval = RUST_OBJECT_TO_JSVAL(obj);
}
none => {
*rval = JSVAL_NULL;
}
}
});
};
}
return 1;
}
extern fn getNextSibling(cx: *JSContext, obj: *JSObject, _id: jsid, rval: *mut jsval) -> JSBool {
unsafe {
(*unwrap(obj)).payload.read(|nd| {
let bundle = unwrap(obj);
do (*bundle).payload.scope.write((*bundle).payload.node) |nd| {
match nd.tree.next_sibling {
some(n) => {
let obj = create(cx, n).ptr;
let obj = create(cx, n, (*bundle).payload.scope).ptr;
*rval = RUST_OBJECT_TO_JSVAL(obj);
}
none => {
*rval = JSVAL_NULL;
}
}
});
};
}
return 1;
}

View file

@ -1,6 +1,6 @@
import js::rust::{bare_compartment, methods};
import js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL, JS_THIS_OBJECT,
JS_SET_RVAL};
import js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, JSVAL_NULL,
JS_THIS_OBJECT, JS_SET_RVAL};
import js::jsapi::{JSContext, jsval, JSObject, JSBool, jsid, JSClass, JSFreeOp};
import js::jsapi::bindgen::{JS_ValueToString, JS_GetStringCharsZAndLength, JS_ReportError,
JS_GetReservedSlot, JS_SetReservedSlot, JS_NewStringCopyN,
@ -8,12 +8,14 @@ import js::jsapi::bindgen::{JS_ValueToString, JS_GetStringCharsZAndLength, JS_Re
import js::glue::bindgen::*;
import js::global::jsval_to_rust_str;
import js::crust::{JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ConvertStub, JS_ResolveStub};
import js::glue::bindgen::RUST_JSVAL_TO_INT;
import result::{result, ok, err};
import ptr::null;
import libc::c_uint;
import utils::{rust_box, squirrel_away, jsval_to_str};
import bindings::node::create;
import base::{Node, Window};
import dvec::{DVec, dvec};
extern fn alert(cx: *JSContext, argc: c_uint, vp: *jsval) -> JSBool {
unsafe {
@ -28,6 +30,40 @@ extern fn alert(cx: *JSContext, argc: c_uint, vp: *jsval) -> JSBool {
1_i32
}
// Holder for the various JS values associated with setTimeout
// (ie. function value to invoke and all arguments to pass
// to the function when calling it)
struct TimerData {
let funval: jsval;
let args: DVec<jsval>;
new(argc: c_uint, argv: *jsval) unsafe {
self.funval = *argv;
self.args = dvec();
let mut i = 2;
while i < argc as uint {
self.args.push(*ptr::offset(argv, i));
i += 1;
};
}
}
extern fn setTimeout(cx: *JSContext, argc: c_uint, vp: *jsval) -> JSBool unsafe {
let argv = JS_ARGV(cx, vp);
assert (argc >= 2);
//TODO: don't crash when passed a non-integer value for the timeout
// Post a delayed message to the per-window timer task; it will dispatch it
// to the relevant content handler that will deal with it.
std::timer::delayed_send(std::uv_global_loop::get(),
RUST_JSVAL_TO_INT(*ptr::offset(argv, 1)) as uint,
(*unwrap(JS_THIS_OBJECT(cx, vp))).payload.timer_chan,
base::Fire(~TimerData(argc, argv)));
JS_SET_RVAL(cx, vp, JSVAL_NULL);
return 1;
}
unsafe fn unwrap(obj: *JSObject) -> *rust_box<Window> {
let val = JS_GetReservedSlot(obj, 0);
unsafe::reinterpret_cast(RUST_JSVAL_TO_PRIVATE(val))
@ -53,6 +89,10 @@ fn init(compartment: bare_compartment, win: @Window) {
let methods = ~[{name: compartment.add_name(~"alert"),
call: alert,
nargs: 1,
flags: 0},
{name: compartment.add_name(~"setTimeout"),
call: setTimeout,
nargs: 2,
flags: 0}];
vec::as_buf(methods, |fns, _len| {

View file

@ -1,6 +1,15 @@
function setWidth(w, i) {
var elem = document.documentElement.firstChild;
elem.width = w;
debug(elem.width);
w += i;
if (w == 0 || w == 1000)
i *= -1;
window.setTimeout(function() { setWidth(w, i); }, 50);
}
var elem = document.documentElement.firstChild;
debug(elem.tagName);
debug(elem instanceof HTMLImageElement);
debug(elem.width);
elem.width = 1000;
debug(elem.width);
setWidth(1000, -10);

View file

@ -0,0 +1 @@
<div></div><script src="test_timeout.js"></script>

11
src/test/test_timeout.js Normal file
View file

@ -0,0 +1,11 @@
function foo(i) {
window.alert("timeout " + i);
if (i == 10)
window.alert("timeouts finished");
else
window.setTimeout(function() { foo(i + 1); }, 1000);
}
window.alert("beginning timeouts");
window.setTimeout(function() { foo(0); }, 1000);
window.alert("timeouts begun");