From 4db345c3b33c766a13103d43ee5e26b836052ca9 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Wed, 22 Aug 2012 16:29:53 -0700 Subject: [PATCH] Add window.setTimeout. Convert all content node bindings to use RCU write pointer. Store a bundle of Node + NodeScope in each node JS object. --- src/servo/content/content_task.rs | 70 ++++++++++++++++++++---------- src/servo/dom/base.rs | 32 ++++++++++++-- src/servo/dom/bindings/document.rs | 6 ++- src/servo/dom/bindings/element.rs | 25 ++++++----- src/servo/dom/bindings/node.rs | 34 ++++++++++----- src/servo/dom/bindings/window.rs | 46 ++++++++++++++++++-- src/test/test_image_getter.js | 13 +++++- src/test/test_timeout.html | 1 + src/test/test_timeout.js | 11 +++++ 9 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 src/test/test_timeout.html create mode 100644 src/test/test_timeout.js diff --git a/src/servo/content/content_task.rs b/src/servo/content/content_task.rs index c7a76e7c433..990a2e0534f 100644 --- a/src/servo/content/content_task.rs +++ b/src/servo/content/content_task.rs @@ -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 { 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 { let resource_task: ResourceTask; + let compartment: option; + new(layout_task: LayoutTask, +compositor: C, from_master: Port, resource_task: ResourceTask) { self.layout_task = layout_task; @@ -95,6 +105,7 @@ struct Content { self.scope = NodeScope(); self.jsrt = jsrt(); + self.cx = self.jsrt.cx(); self.document = none; self.window = none; @@ -103,6 +114,13 @@ struct Content { 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 { #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 { 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; diff --git a/src/servo/dom/base.rs b/src/servo/dom/base.rs index ca85138d247..69144c9be9f 100644 --- a/src/servo/dom/base.rs +++ b/src/servo/dom/base.rs @@ -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; + + new(content_port: Port) { + let content_chan = chan(content_port); + + self.timer_chan = do task::spawn_listener |timer_port: Port| { + 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; - 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); } } diff --git a/src/servo/dom/bindings/document.rs b/src/servo/dom/bindings/document.rs index 4ec4ef81d66..a034e10ab21 100644 --- a/src/servo/dom/bindings/document.rs +++ b/src/servo/dom/bindings/document.rs @@ -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; } diff --git a/src/servo/dom/bindings/element.rs b/src/servo/dom/bindings/element.rs index 922381f9c17..6f90c79a051 100644 --- a/src/servo/dom/bindings/element.rs +++ b/src/servo/dom/bindings/element.rs @@ -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; diff --git a/src/servo/dom/bindings/node.rs b/src/servo/dom/bindings/node.rs index 4f378bcd8c8..138535054f7 100644 --- a/src/servo/dom/bindings/node.rs +++ b/src/servo/dom/bindings/node.rs @@ -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 { +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 { 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; } diff --git a/src/servo/dom/bindings/window.rs b/src/servo/dom/bindings/window.rs index 2c9f4b15c51..eac0ca75e1e 100644 --- a/src/servo/dom/bindings/window.rs +++ b/src/servo/dom/bindings/window.rs @@ -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; + 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 { let val = JS_GetReservedSlot(obj, 0); unsafe::reinterpret_cast(RUST_JSVAL_TO_PRIVATE(val)) @@ -53,8 +89,12 @@ 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| { JS_DefineFunctions(compartment.cx.ptr, proto.ptr, fns); }); diff --git a/src/test/test_image_getter.js b/src/test/test_image_getter.js index 55ba55f3ce8..cd94d71639a 100644 --- a/src/test/test_image_getter.js +++ b/src/test/test_image_getter.js @@ -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); \ No newline at end of file diff --git a/src/test/test_timeout.html b/src/test/test_timeout.html new file mode 100644 index 00000000000..a06832f07a1 --- /dev/null +++ b/src/test/test_timeout.html @@ -0,0 +1 @@ +
diff --git a/src/test/test_timeout.js b/src/test/test_timeout.js new file mode 100644 index 00000000000..f3383744d09 --- /dev/null +++ b/src/test/test_timeout.js @@ -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"); \ No newline at end of file