aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2016-03-23 22:46:49 -0400
committerJosh Matthews <josh@joshmatthews.net>2016-05-02 14:32:56 -0400
commitcb5bad63dc2e91adfc36e49af60fe49456427f20 (patch)
tree56f25880677b2077998d94bc856e98b4b0e53495
parent88059acd7efb4dceda48ec305528a247fd520c4f (diff)
downloadservo-cb5bad63dc2e91adfc36e49af60fe49456427f20.tar.gz
servo-cb5bad63dc2e91adfc36e49af60fe49456427f20.zip
Implement hiding of interface members via Pref annotations.
-rw-r--r--components/script/dom/bindings/codegen/CodegenRust.py113
-rw-r--r--components/script/dom/bindings/interface.rs30
-rw-r--r--components/script/dom/bindings/utils.rs27
-rw-r--r--components/script/dom/testbinding.rs8
-rw-r--r--components/script/dom/webidls/TestBinding.webidl22
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json6
-rw-r--r--tests/wpt/mozilla/meta/mozilla/interface_member_exposed.html.ini4
-rw-r--r--tests/wpt/mozilla/tests/mozilla/interface_member_exposed.html41
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>