mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement hiding of interface members via Pref annotations.
This commit is contained in:
parent
88059acd7e
commit
cb5bad63dc
8 changed files with 225 additions and 26 deletions
|
@ -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}',
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[interface_member_exposed.html]
|
||||
type: testharness
|
||||
prefs: [dom.testbinding.enabled:true,
|
||||
dom.testbinding.prefcontrolled2.enabled:true]
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue