diff options
author | Josh Matthews <josh@joshmatthews.net> | 2016-03-23 22:46:49 -0400 |
---|---|---|
committer | Josh Matthews <josh@joshmatthews.net> | 2016-05-02 14:32:56 -0400 |
commit | cb5bad63dc2e91adfc36e49af60fe49456427f20 (patch) | |
tree | 56f25880677b2077998d94bc856e98b4b0e53495 | |
parent | 88059acd7efb4dceda48ec305528a247fd520c4f (diff) | |
download | servo-cb5bad63dc2e91adfc36e49af60fe49456427f20.tar.gz servo-cb5bad63dc2e91adfc36e49af60fe49456427f20.zip |
Implement hiding of interface members via Pref annotations.
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 113 | ||||
-rw-r--r-- | components/script/dom/bindings/interface.rs | 30 | ||||
-rw-r--r-- | components/script/dom/bindings/utils.rs | 27 | ||||
-rw-r--r-- | components/script/dom/testbinding.rs | 8 | ||||
-rw-r--r-- | components/script/dom/webidls/TestBinding.webidl | 22 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/mozilla/interface_member_exposed.html.ini | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/interface_member_exposed.html | 41 |
8 files changed, 225 insertions, 26 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index ce524583930..a19000ec01e 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1317,6 +1317,33 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): returnType) +class MemberCondition: + """ + An object representing the condition for a member to actually be + exposed. Any of the arguments can be None. If not + None, they should have the following types: + + pref: The name of the preference. + func: The name of the function. + """ + def __init__(self, pref=None, func=None): + assert pref is None or isinstance(pref, str) + assert func is None or isinstance(func, str) + self.pref = pref + + def toFuncPtr(val): + if val is None: + return "None" + return "Some(%s)" % val + self.func = toFuncPtr(func) + + def __eq__(self, other): + return (self.pref == other.pref and self.func == other.func) + + def __ne__(self, other): + return not self.__eq__(other) + + class PropertyDefiner: """ A common superclass for defining things on prototype objects. @@ -1340,8 +1367,26 @@ class PropertyDefiner: # up used via ResolveProperty or EnumerateProperties. return self.generateArray(self.regular, self.variableName()) + @staticmethod + def getStringAttr(member, name): + attr = member.getExtendedAttribute(name) + if attr is None: + return None + # It's a list of strings + assert len(attr) == 1 + assert attr[0] is not None + return attr[0] + + @staticmethod + def getControllingCondition(interfaceMember, descriptor): + return MemberCondition( + PropertyDefiner.getStringAttr(interfaceMember, + "Pref"), + PropertyDefiner.getStringAttr(interfaceMember, + "Func")) + def generatePrefableArray(self, array, name, specTemplate, specTerminator, - specType, getDataTuple): + specType, getCondition, getDataTuple): """ This method generates our various arrays. @@ -1360,24 +1405,52 @@ class PropertyDefiner: returns a tuple suitable for substitution into specTemplate. """ + # We generate an all-encompassing list of lists of specs, with each sublist + # representing a group of members that share a common pref name. That will + # make sure the order of the properties as exposed on the interface and + # interface prototype objects does not change when pref control is added to + # members while still allowing us to define all the members in the smallest + # number of JSAPI calls. assert len(array) != 0 + # So we won't put a specTerminator at the very front of the list: + lastCondition = getCondition(array[0], self.descriptor) specs = [] + currentSpecs = [] + prefableSpecs = [] + + prefableTemplate = ' Prefable { pref: %s, specs: %s[%d], terminator: %s }' + + def switchToCondition(props, condition): + prefableSpecs.append(prefableTemplate % + ('Some("%s")' % condition.pref if condition.pref else 'None', + name + "_specs", + len(specs), + 'true' if specTerminator else 'false')) + specs.append("&[\n" + ",\n".join(currentSpecs) + "]\n") + del currentSpecs[:] for member in array: - specs.append(specTemplate % getDataTuple(member)) + curCondition = getCondition(member, self.descriptor) + if lastCondition != curCondition: + # Terminate previous list + if specTerminator: + currentSpecs.append(specTerminator) + # And switch to our new pref + switchToCondition(self, lastCondition) + lastCondition = curCondition + # And the actual spec + currentSpecs.append(specTemplate % getDataTuple(member)) if specTerminator: - specs.append(specTerminator) + currentSpecs.append(specTerminator) + switchToCondition(self, lastCondition) - specsArray = ("const %s_specs: &'static [%s] = &[\n" + + specsArray = ("const %s_specs: &'static [&'static[%s]] = &[\n" + ",\n".join(specs) + "\n" + "];\n") % (name, specType) prefArray = ("const %s: &'static [Prefable<%s>] = &[\n" + - " Prefable {\n" + - " pref: None,\n" + - " specs: &%s_specs,\n" + - " },\n" + - "];\n") % (name, specType, name) + ",\n".join(prefableSpecs) + "\n" + + "];\n") % (name, specType) return specsArray + prefArray @@ -1412,14 +1485,17 @@ class MethodDefiner(PropertyDefiner): methods = [] self.regular = [{"name": m.identifier.name, "methodInfo": not m.isStatic(), - "length": methodLength(m)} for m in methods] + "length": methodLength(m), + "condition": PropertyDefiner.getControllingCondition(m, descriptor)} + for m in methods] # FIXME Check for an existing iterator on the interface first. if any(m.isGetter() and m.isIndexed() for m in methods): self.regular.append({"name": '@@iterator', "methodInfo": False, "selfHostedName": "ArrayValues", - "length": 0}) + "length": 0, + "condition": MemberCondition()}) isUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("Unforgeable")) if not static and unforgeable == isUnforgeableInterface: @@ -1429,6 +1505,7 @@ class MethodDefiner(PropertyDefiner): "name": "toString", "nativeName": stringifier.identifier.name, "length": 0, + "condition": PropertyDefiner.getControllingCondition(stringifier, descriptor) }) self.unforgeable = unforgeable @@ -1436,6 +1513,9 @@ class MethodDefiner(PropertyDefiner): if len(array) == 0: return "" + def condition(m, d): + return m["condition"] + flags = "JSPROP_ENUMERATE" if self.unforgeable: flags += " | JSPROP_PERMANENT | JSPROP_READONLY" @@ -1483,7 +1563,7 @@ class MethodDefiner(PropertyDefiner): ' selfHostedName: 0 as *const libc::c_char\n' ' }', 'JSFunctionSpec', - specData) + condition, specData) class AttrDefiner(PropertyDefiner): @@ -1561,7 +1641,7 @@ class AttrDefiner(PropertyDefiner): ' setter: JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }\n' ' }', 'JSPropertySpec', - specData) + PropertyDefiner.getControllingCondition, specData) class ConstDefiner(PropertyDefiner): @@ -1586,7 +1666,7 @@ class ConstDefiner(PropertyDefiner): ' ConstantSpec { name: %s, value: %s }', None, 'ConstantSpec', - specData) + PropertyDefiner.getControllingCondition, specData) # We'll want to insert the indent at the beginnings of lines, but we # don't want to indent empty lines. So only indent lines that have a @@ -2248,8 +2328,8 @@ def InitUnforgeablePropertiesOnHolder(descriptor, properties): """ unforgeables = [] - defineUnforgeableAttrs = "define_properties(cx, unforgeable_holder.handle(), %s_specs).unwrap();" - defineUnforgeableMethods = "define_methods(cx, unforgeable_holder.handle(), %s_specs).unwrap();" + defineUnforgeableAttrs = "define_prefable_properties(cx, unforgeable_holder.handle(), %s);" + defineUnforgeableMethods = "define_prefable_methods(cx, unforgeable_holder.handle(), %s);" unforgeableMembers = [ (defineUnforgeableAttrs, properties.unforgeable_attrs), @@ -5470,6 +5550,7 @@ class CGBindingRoot(CGThing): 'dom::bindings::interface::{InterfaceConstructorBehavior, NonCallbackInterfaceObjectClass}', 'dom::bindings::interface::{create_callback_interface_object, create_interface_prototype_object}', 'dom::bindings::interface::{create_named_constructors, create_noncallback_interface_object}', + 'dom::bindings::interface::{define_prefable_methods, define_prefable_properties}', 'dom::bindings::interface::{ConstantSpec, NonNullJSNative}', 'dom::bindings::interface::ConstantVal::{IntVal, UintVal}', 'dom::bindings::js::{JS, Root, RootedReference}', diff --git a/components/script/dom/bindings/interface.rs b/components/script/dom/bindings/interface.rs index eef2913dabd..54ad8115fda 100644 --- a/components/script/dom/bindings/interface.rs +++ b/components/script/dom/bindings/interface.rs @@ -219,7 +219,7 @@ pub unsafe fn create_callback_interface_object( rval.set(JS_NewObject(cx, ptr::null())); assert!(!rval.ptr.is_null()); for prefable in constants { - define_constants(cx, rval.handle(), prefable.specs); + define_constants(cx, rval.handle(), prefable.specs()); } define_name(cx, rval.handle(), name); define_on_global_object(cx, receiver, name, rval.handle()); @@ -364,17 +364,31 @@ unsafe fn create_object( rval.set(JS_NewObjectWithUniqueType(cx, class, proto)); assert!(!rval.ptr.is_null()); if let Some(methods) = methods { - for prefable in methods { - define_methods(cx, rval.handle(), prefable.specs).unwrap(); - } + define_prefable_methods(cx, rval.handle(), methods); } if let Some(properties) = properties { - for prefable in properties { - define_properties(cx, rval.handle(), prefable.specs).unwrap(); - } + define_prefable_properties(cx, rval.handle(), properties); } for prefable in constants { - define_constants(cx, rval.handle(), prefable.specs); + define_constants(cx, rval.handle(), prefable.specs()); + } +} + +/// Conditionally define methods on an object. +pub unsafe fn define_prefable_methods(cx: *mut JSContext, + obj: HandleObject, + methods: &'static [Prefable<JSFunctionSpec>]) { + for prefable in methods { + define_methods(cx, obj, prefable.specs()).unwrap(); + } +} + +/// Conditionally define properties on an object. +pub unsafe fn define_prefable_properties(cx: *mut JSContext, + obj: HandleObject, + properties: &'static [Prefable<JSPropertySpec>]) { + for prefable in properties { + define_properties(cx, obj, prefable.specs()).unwrap(); } } diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 1e2cf5c40f6..29eb33bdefd 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -41,6 +41,7 @@ use std::os::raw::c_void; use std::ptr; use std::slice; use util::non_geckolib::jsstring_to_str; +use util::prefs; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); @@ -558,8 +559,30 @@ pub const DOM_CALLBACKS: DOMCallbacks = DOMCallbacks { instanceClassMatchesProto: Some(instance_class_has_proto_at_depth), }; -#[allow(missing_docs)] +/// A container around JS member specifications that are conditionally enabled. pub struct Prefable<T: 'static> { + /// If present, the name of the preference used to conditionally enable these specs. pub pref: Option<&'static str>, - pub specs: &'static [T] + /// The underlying slice of specifications. + pub specs: &'static [T], + /// Whether the specifications contain special terminating entries that should be + /// included or not. + pub terminator: bool, +} + +impl<T> Prefable<T> { + /// Retrieve the slice represented by this container, unless the condition + /// guarding it is false. + pub fn specs(&self) -> &'static [T] { + if let Some(pref) = self.pref { + if !prefs::get_pref(pref).as_boolean().unwrap_or(false) { + return if self.terminator { + &self.specs[self.specs.len() - 1..] + } else { + &[] + }; + } + } + self.specs + } } diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index f69b844f4ed..04116b29534 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -493,10 +493,18 @@ impl TestBindingMethods for TestBinding { fn StringMozPreference(&self, pref_name: DOMString) -> DOMString { get_pref(pref_name.as_ref()).as_string().map(|s| DOMString::from(s)).unwrap_or_else(|| DOMString::new()) } + fn PrefControlledAttributeDisabled(&self) -> bool { false } + fn PrefControlledAttributeEnabled(&self) -> bool { false } + fn PrefControlledMethodDisabled(&self) {} + fn PrefControlledMethodEnabled(&self) {} } impl TestBinding { pub fn BooleanAttributeStatic(_: GlobalRef) -> bool { false } pub fn SetBooleanAttributeStatic(_: GlobalRef, _: bool) {} pub fn ReceiveVoidStatic(_: GlobalRef) {} + pub fn PrefControlledStaticAttributeDisabled(_: GlobalRef) -> bool { false } + pub fn PrefControlledStaticAttributeEnabled(_: GlobalRef) -> bool { false } + pub fn PrefControlledStaticMethodDisabled(_: GlobalRef) {} + pub fn PrefControlledStaticMethodEnabled(_: GlobalRef) {} } diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index 4b4265f326a..d0bfbbfef2a 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -402,4 +402,26 @@ interface TestBinding { static void receiveVoidStatic(); boolean BooleanMozPreference(DOMString pref_name); DOMString StringMozPreference(DOMString pref_name); + + [Pref="dom.testbinding.prefcontrolled.enabled"] + readonly attribute boolean prefControlledAttributeDisabled; + [Pref="dom.testbinding.prefcontrolled.enabled"] + static readonly attribute boolean prefControlledStaticAttributeDisabled; + [Pref="dom.testbinding.prefcontrolled.enabled"] + void prefControlledMethodDisabled(); + [Pref="dom.testbinding.prefcontrolled.enabled"] + static void prefControlledStaticMethodDisabled(); + [Pref="dom.testbinding.prefcontrolled.enabled"] + const unsigned short prefControlledConstDisabled = 0; + + [Pref="dom.testbinding.prefcontrolled2.enabled"] + readonly attribute boolean prefControlledAttributeEnabled; + [Pref="dom.testbinding.prefcontrolled2.enabled"] + static readonly attribute boolean prefControlledStaticAttributeEnabled; + [Pref="dom.testbinding.prefcontrolled2.enabled"] + void prefControlledMethodEnabled(); + [Pref="dom.testbinding.prefcontrolled2.enabled"] + static void prefControlledStaticMethodEnabled(); + [Pref="dom.testbinding.prefcontrolled2.enabled"] + const unsigned short prefControlledConstEnabled = 0; }; diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index b57efe99a79..c89463c0a0c 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -6406,6 +6406,12 @@ "url": "/_mozilla/mozilla/innerHTML.html" } ], + "mozilla/interface_member_exposed.html": [ + { + "path": "mozilla/interface_member_exposed.html", + "url": "/_mozilla/mozilla/interface_member_exposed.html" + } + ], "mozilla/interfaces.html": [ { "path": "mozilla/interfaces.html", diff --git a/tests/wpt/mozilla/meta/mozilla/interface_member_exposed.html.ini b/tests/wpt/mozilla/meta/mozilla/interface_member_exposed.html.ini new file mode 100644 index 00000000000..19543bd7a73 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/interface_member_exposed.html.ini @@ -0,0 +1,4 @@ +[interface_member_exposed.html] + type: testharness + prefs: [dom.testbinding.enabled:true, + dom.testbinding.prefcontrolled2.enabled:true] diff --git a/tests/wpt/mozilla/tests/mozilla/interface_member_exposed.html b/tests/wpt/mozilla/tests/mozilla/interface_member_exposed.html new file mode 100644 index 00000000000..d455e8e5586 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/interface_member_exposed.html @@ -0,0 +1,41 @@ +<!doctype html> +<meta charset="utf-8"> +<title></title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +function test_member(name, enabled, target) { + var status = enabled ? "Enabled" : "Disabled"; + var verb = enabled ? "shows" : "hides"; + test(function() { + var interface = target(window.TestBinding); + var descriptor = Object.getOwnPropertyDescriptor(interface, name); + if (enabled) { + assert_not_equals(descriptor, undefined); + } else { + assert_equals(descriptor, undefined); + } + }, status + " preference " + verb + " member controlled by that preference: " + name); +} + +var members = [ + 'prefControlledAttribute', + 'prefControlledMethod' +]; +var staticMembers = [ + 'prefControlledStaticAttribute', + 'prefControlledStaticMethod', + 'prefControlledConst' +]; + +for (var i = 0; i < members.length; i++) { + var name = members[i]; + test_member(name + 'Enabled', true, function(o) { return Object.getPrototypeOf(new o()) }); + test_member(name + 'Disabled', false, function(o) { return Object.getPrototypeOf(new o()) }); +} +for (var i = 0; i < staticMembers.length; i++) { + var name = staticMembers[i]; + test_member(name + 'Enabled', true, function(o) { return o; }); + test_member(name + 'Disabled', false, function(o) { return o; }); +} +</script> |