Skip to content

Commit

Permalink
Initial implementation of ownPropertyKeys proxy handler
Browse files Browse the repository at this point in the history
Generates `SupportedPropertyNames` on DOM structs that should implement
it. Most of them are unimplemented now (which can be implemented in
later PRs), with the exception of `HTMLCollection`. Also added a couple
relevant WPT tests.

Closes servo#6390

Closes servo#2215
  • Loading branch information
frewsxcv committed Aug 19, 2015
1 parent 3d0951c commit c71ae23
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 17 deletions.
71 changes: 68 additions & 3 deletions components/script/dom/bindings/codegen/CodegenRust.py
Expand Up @@ -2472,7 +2472,7 @@ def definition_body(self):
enter: None,
getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor),
defineProperty: Some(%s),
ownPropertyKeys: Some(proxyhandler::own_property_keys),
ownPropertyKeys: Some(own_property_keys),
delete_: Some(%s),
enumerate: None,
preventExtensions: Some(proxyhandler::prevent_extensions),
Expand Down Expand Up @@ -4177,6 +4177,60 @@ def definition_body(self):
return CGGeneric(self.getBody())


class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod):
def __init__(self, descriptor):
args = [Argument('*mut JSContext', 'cx'),
Argument('HandleObject', 'proxy'),
Argument('*mut AutoIdVector', 'props')]
CGAbstractExternMethod.__init__(self, descriptor, "own_property_keys", "u8", args)
self.descriptor = descriptor

def getBody(self):
body = dedent(
"""
let unwrapped_proxy = UnwrapProxy(proxy);
""")

if self.descriptor.operations['IndexedGetter']:
body += dedent(
"""
for i in 0..(*unwrapped_proxy).Length() {
let rooted_jsid = RootedId::new(cx, int_to_jsid(i as i32));
AppendToAutoIdVector(props, rooted_jsid.handle().get());
}
""")

if self.descriptor.operations['NamedGetter']:
body += dedent(
"""
for name in (*unwrapped_proxy).SupportedPropertyNames() {
let cstring = CString::new(name).unwrap();
let jsstring = JS_InternString(cx, cstring.as_ptr());
let mut rooted = RootedString::new(cx, jsstring);
let jsid = INTERNED_STRING_TO_JSID(cx, rooted.handle().get());
let rooted_jsid = RootedId::new(cx, jsid);
AppendToAutoIdVector(props, rooted_jsid.handle().get());
}
""")

body += dedent(
"""
let expando = get_expando_object(proxy);
if !expando.is_null() {
let rooted_expando = RootedObject::new(cx, expando);
// FIXME: remove the magic hex numbers below: https://github.com/servo/servo/issues/7278
GetPropertyKeys(cx, rooted_expando.handle(), 0x8 | 0x10 | 0x20, props);
}
return JSTrue;
""")

return body

def definition_body(self):
return CGGeneric(self.getBody())


class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod):
def __init__(self, descriptor):
args = [Argument('*mut JSContext', 'cx'), Argument('HandleObject', 'proxy'),
Expand Down Expand Up @@ -4495,6 +4549,14 @@ def members():
infallible = 'infallible' in descriptor.getExtendedAttributes(operation)
if operation.isGetter():
arguments = method_arguments(descriptor, rettype, arguments, trailing=("found", "&mut bool"))

# If this interface 'supports named properties', then we
# should be able to access 'supported property names'
#
# WebIDL, Second Draft, section 3.2.4.5
# https://heycam.github.io/webidl/#idl-named-properties
if operation.isNamed():
yield "SupportedPropertyNames", [], "Vec<DOMString>"
else:
arguments = method_arguments(descriptor, rettype, arguments)
rettype = return_type(descriptor, rettype, infallible)
Expand Down Expand Up @@ -4599,6 +4661,7 @@ def __init__(self, descriptor):
# cgThings.append(CGProxyIsProxy(descriptor))
cgThings.append(CGProxyUnwrap(descriptor))
cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
cgThings.append(CGDOMJSProxyHandler_ownPropertyKeys(descriptor))
cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor))
cgThings.append(CGDOMJSProxyHandler_className(descriptor))
cgThings.append(CGDOMJSProxyHandler_get(descriptor))
Expand Down Expand Up @@ -4962,20 +5025,22 @@ def __init__(self, config, prefix, webIDLFile):
'js::jsapi::{JSClass, FreeOp, JSFreeOp, JSFunctionSpec, jsid}',
'js::jsapi::{MutableHandleValue, MutableHandleObject, HandleObject, HandleValue, RootedObject}',
'js::jsapi::{RootedValue, JSNativeWrapper, JSNative, JSObject, JSPropertyDescriptor}',
'js::jsapi::{RootedId, JS_InternString, RootedString, INTERNED_STRING_TO_JSID}',
'js::jsapi::{JSPropertySpec}',
'js::jsapi::{JSString, JSTracer, JSJitInfo, JSJitInfo_OpType, JSJitInfo_AliasSet}',
'js::jsapi::{MutableHandle, Handle, HandleId, JSType, JSValueType}',
'js::jsapi::{SymbolCode, ObjectOpResult, HandleValueArray}',
'js::jsapi::{JSJitGetterCallArgs, JSJitSetterCallArgs, JSJitMethodCallArgs, CallArgs}',
'js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_ComputeThis}',
'js::jsapi::GetGlobalForObjectCrossCompartment',
'js::jsapi::{GetGlobalForObjectCrossCompartment, AutoIdVector}',
'js::jsval::JSVal',
'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}',
'js::jsval::{NullValue, UndefinedValue}',
'js::glue::{CallJitMethodOp, CallJitGetterOp, CallJitSetterOp, CreateProxyHandler}',
'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}',
'js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO}',
'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}',
'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING, int_to_jsid}',
'js::glue::{AppendToAutoIdVector, GetPropertyKeys}',
'js::rust::GCMethods',
'js::{JSTrue, JSFalse}',
'dom::bindings',
Expand Down
10 changes: 0 additions & 10 deletions components/script/dom/bindings/proxyhandler.rs
Expand Up @@ -13,7 +13,6 @@ use js::jsapi::{JS_GetPropertyDescriptorById};
use js::jsapi::{JS_DefinePropertyById6, JS_NewObjectWithGivenProto};
use js::jsapi::{JS_StrictPropertyStub, JSErrNum};
use js::jsapi::{Handle, HandleObject, HandleId, MutableHandle, RootedObject, ObjectOpResult};
use js::jsapi::AutoIdVector;
use js::jsapi::GetObjectProto;
use js::jsval::ObjectValue;
use js::glue::GetProxyExtra;
Expand Down Expand Up @@ -83,15 +82,6 @@ pub unsafe extern fn delete(cx: *mut JSContext, proxy: HandleObject, id: HandleI
delete_property_by_id(cx, expando.handle(), id, bp)
}

/// Stub for ownPropertyKeys
pub unsafe extern fn own_property_keys(_cx: *mut JSContext,
_proxy: HandleObject,
_props: *mut AutoIdVector) -> u8 {
// FIXME: implement this
// https://github.com/servo/servo/issues/6390
JSTrue
}

/// Controls whether the Extensible bit can be changed
pub unsafe extern fn prevent_extensions(_cx: *mut JSContext,
_proxy: HandleObject,
Expand Down
6 changes: 6 additions & 0 deletions components/script/dom/document.rs
Expand Up @@ -1912,6 +1912,12 @@ impl<'a> DocumentMethods for &'a Document {
collection.r().reflector().get_jsobject().get()
}

// https://html.spec.whatwg.org/#document
fn SupportedPropertyNames(self) -> Vec<DOMString> {
// FIXME: unimplemented
vec![]
}

global_event_handlers!();
event_handler!(readystatechange, GetOnreadystatechange, SetOnreadystatechange);
}
Expand Down
7 changes: 6 additions & 1 deletion components/script/dom/domstringmap.rs
Expand Up @@ -67,5 +67,10 @@ impl<'a> DOMStringMapMethods for &'a DOMStringMap {
}
}
}
}

// https://html.spec.whatwg.org/multipage/#domstringmap
fn SupportedPropertyNames(self) -> Vec<DOMString> {
// FIXME: unimplemented
vec![]
}
}
27 changes: 26 additions & 1 deletion components/script/dom/htmlcollection.rs
Expand Up @@ -227,5 +227,30 @@ impl<'a> HTMLCollectionMethods for &'a HTMLCollection {
*found = maybe_elem.is_some();
maybe_elem
}
}

// https://dom.spec.whatwg.org/#interface-htmlcollection
fn SupportedPropertyNames(self) -> Vec<DOMString> {
// Step 1
let mut result = vec![];

// Step 2
let ref filter = self.collection.1;
let root = self.collection.0.root();
let elems = HTMLCollection::traverse(root.r()).filter(|element| filter.filter(element.r(), root.r()));
for elem in elems {
// Step 2.1
let id_attr = elem.get_string_attribute(&atom!("id"));
if !id_attr.is_empty() && !result.contains(&id_attr) {
result.push(id_attr)
}
// Step 2.2
let name_attr = elem.get_string_attribute(&atom!("name"));
if !name_attr.is_empty() && !result.contains(&name_attr) && *elem.namespace() == ns!(HTML) {
result.push(name_attr)
}
}

// Step 3
result
}
}
6 changes: 5 additions & 1 deletion components/script/dom/namednodemap.rs
Expand Up @@ -105,5 +105,9 @@ impl<'a> NamedNodeMapMethods for &'a NamedNodeMap {
*found = item.is_some();
item
}
}

fn SupportedPropertyNames(self) -> Vec<DOMString> {
// FIXME: unimplemented
vec![]
}
}
5 changes: 5 additions & 0 deletions components/script/dom/storage.rs
Expand Up @@ -134,6 +134,11 @@ impl<'a> StorageMethods for &'a Storage {
fn NamedDeleter(self, name: DOMString) {
self.RemoveItem(name);
}

fn SupportedPropertyNames(self) -> Vec<DOMString> {
// FIXME: unimplemented
vec![]
}
}

trait PrivateStorageHelpers {
Expand Down
3 changes: 2 additions & 1 deletion components/script/dom/testbindingproxy.rs
Expand Up @@ -16,7 +16,8 @@ pub struct TestBindingProxy {
}

impl<'a> TestBindingProxyMethods for &'a TestBindingProxy {

fn Length(self) -> u32 {0}
fn SupportedPropertyNames(self) -> Vec<DOMString> {vec![]}
fn GetNamedItem(self, _: DOMString) -> DOMString {"".to_owned()}
fn SetNamedItem(self, _: DOMString, _: DOMString) -> () {}
fn GetItem(self, _: u32) -> DOMString {"".to_owned()}
Expand Down
1 change: 1 addition & 0 deletions components/script/dom/webidls/TestBindingProxy.webidl
Expand Up @@ -13,6 +13,7 @@
// web pages.

interface TestBindingProxy : TestBinding {
readonly attribute unsigned long length;

getter DOMString getNamedItem(DOMString name);

Expand Down
8 changes: 8 additions & 0 deletions tests/wpt/metadata/MANIFEST.json
Expand Up @@ -13071,6 +13071,10 @@
"path": "dom/collections/HTMLCollection-empty-name.html",
"url": "/dom/collections/HTMLCollection-empty-name.html"
},
{
"path": "dom/collections/HTMLCollection-supported-property-names.html",
"url": "/dom/collections/HTMLCollection-supported-property-names.html"
},
{
"path": "dom/events/Event-constants.html",
"url": "/dom/events/Event-constants.html"
Expand Down Expand Up @@ -18071,6 +18075,10 @@
"path": "js/builtins/Object.prototype.freeze.html",
"url": "/js/builtins/Object.prototype.freeze.html"
},
{
"path": "js/builtins/Object.prototype.getOwnPropertyNames.html",
"url": "/js/builtins/Object.prototype.getOwnPropertyNames.html"
},
{
"path": "js/builtins/Object.prototype.hasOwnProperty-order.html",
"url": "/js/builtins/Object.prototype.hasOwnProperty-order.html"
Expand Down
@@ -0,0 +1,56 @@
<!doctype html>
<meta charset=utf-8>
<link rel=help href=https://dom.spec.whatwg.org/#interface-htmlcollection>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>

<div id=log></div>

<!-- with no attribute -->
<span></span>

<!-- with `id` attribute -->
<span id=''></span>
<span id='some-id'></span>
<span id='some-id'></span><!-- to ensure no duplicates -->

<!-- with `name` attribute -->
<span name=''></span>
<span name='some-name'></span>
<span name='some-name'></span><!-- to ensure no duplicates -->

<!-- with `name` and `id` attribute -->
<span id='another-id' name='another-name'></span>

<script>
test(function () {
var elements = document.getElementsByTagName("span");
assert_array_equals(
Object.getOwnPropertyNames(elements),
['0', '1', '2', '3', '4', '5', '6', '7', 'some-id', 'some-name', 'another-id', 'another-name']
);
}, 'Object.getOwnPropertyNames on HTMLCollection');

test(function () {
var elem = document.createElementNS('some-random-namespace', 'foo');
elem.setAttribute("name", "some-name");
document.body.appendChild(elem);

var elements = document.getElementsByTagName("foo");
assert_array_equals(Object.getOwnPropertyNames(elements), ['0']);

elem.remove();
}, 'Object.getOwnPropertyNames on HTMLCollection with non-HTML namespace');

test(function () {
var elem = document.createElement('foo');
document.body.appendChild(elem);

var elements = document.getElementsByTagName("foo");
elements.someProperty = "some value";

assert_array_equals(Object.getOwnPropertyNames(elements), ['0', 'someProperty']);

elem.remove();
}, 'Object.getOwnPropertyNames on HTMLCollection with expando object');
</script>
@@ -0,0 +1,56 @@
<!doctype html>
<title>Object.prototype.getOwnPropertyNames</title>
<link rel=help href=http://es5.github.io/#x15.2.3.4>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>

<div id=log></div>
<script>
test(function () {
var obj = {0: 'a', 1: 'b', 2: 'c'};
assert_array_equals(
Object.getOwnPropertyNames(obj).sort(),
['0', '1', '2']
);
}, "object");

test(function () {
var arr = ['a', 'b', 'c'];
assert_array_equals(
Object.getOwnPropertyNames(arr).sort(),
['0', '1', '2', 'length']
);
}, "array-like");

test(function () {
var obj = Object.create({}, {
getFoo: {
value: function() { return this.foo; },
enumerable: false
}
});
obj.foo = 1;
assert_array_equals(
Object.getOwnPropertyNames(obj).sort(),
['foo', 'getFoo']
);
}, "non-enumerable property");

test(function() {
function ParentClass() {}
ParentClass.prototype.inheritedMethod = function() {};

function ChildClass() {
this.prop = 5;
this.method = function() {};
}
ChildClass.prototype = new ParentClass;
ChildClass.prototype.prototypeMethod = function() {};

var obj = new ChildClass;
assert_array_equals(
Object.getOwnPropertyNames(obj).sort(),
['method', 'prop']
);
}, 'items on the prototype chain are not listed');
</script>

0 comments on commit c71ae23

Please sign in to comment.