Skip to content

Commit

Permalink
Auto merge of servo#10081 - jdm:interfacepref, r=nox
Browse files Browse the repository at this point in the history
Support controlling interface and member visibility via preferences

This was easier to throw together than per-attribute/method support, and it gets rid of some nonstandard properties from our globals.

Fixes servo#7626.

r? @Ms2ger

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10081)
<!-- Reviewable:end -->
  • Loading branch information
bors-servo committed May 2, 2016
2 parents a54deda + 2a9bf70 commit 8255e74
Show file tree
Hide file tree
Showing 28 changed files with 352 additions and 53 deletions.
183 changes: 165 additions & 18 deletions components/script/dom/bindings/codegen/CodegenRust.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -1340,8 +1367,26 @@ def __str__(self):
# 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.
Expand All @@ -1360,17 +1405,53 @@ def generatePrefableArray(self, array, name, specTemplate, specTerminator,
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)

return (("const %s: &'static [%s] = &[\n" +
",\n".join(specs) + "\n" +
"];\n") % (name, specType))
specsArray = ("const %s_specs: &'static [&'static[%s]] = &[\n" +
",\n".join(specs) + "\n" +
"];\n") % (name, specType)

prefArray = ("const %s: &'static [Prefable<%s>] = &[\n" +
",\n".join(prefableSpecs) + "\n" +
"];\n") % (name, specType)
return specsArray + prefArray


# The length of a method is the minimum of the lengths of the
Expand Down Expand Up @@ -1404,14 +1485,17 @@ def __init__(self, descriptor, name, static, unforgeable):
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:
Expand All @@ -1421,13 +1505,17 @@ def __init__(self, descriptor, name, static, unforgeable):
"name": "toString",
"nativeName": stringifier.identifier.name,
"length": 0,
"condition": PropertyDefiner.getControllingCondition(stringifier, descriptor)
})
self.unforgeable = unforgeable

def generateArray(self, array, name):
if len(array) == 0:
return ""

def condition(m, d):
return m["condition"]

flags = "JSPROP_ENUMERATE"
if self.unforgeable:
flags += " | JSPROP_PERMANENT | JSPROP_READONLY"
Expand Down Expand Up @@ -1475,7 +1563,7 @@ def specData(m):
' selfHostedName: 0 as *const libc::c_char\n'
' }',
'JSFunctionSpec',
specData)
condition, specData)


class AttrDefiner(PropertyDefiner):
Expand Down Expand Up @@ -1553,7 +1641,7 @@ def specData(attr):
' setter: JSNativeWrapper { op: None, info: 0 as *const JSJitInfo }\n'
' }',
'JSPropertySpec',
specData)
PropertyDefiner.getControllingCondition, specData)


class ConstDefiner(PropertyDefiner):
Expand All @@ -1578,7 +1666,7 @@ def specData(const):
' 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
Expand Down Expand Up @@ -1925,7 +2013,10 @@ class CGList(CGThing):
"""
def __init__(self, children, joiner=""):
CGThing.__init__(self)
self.children = children
# Make a copy of the kids into a list, because if someone passes in a
# generator we won't be able to both declare and define ourselves, or
# define ourselves more than once!
self.children = list(children)
self.joiner = joiner

def append(self, child):
Expand All @@ -1934,12 +2025,15 @@ def append(self, child):
def prepend(self, child):
self.children.insert(0, child)

def join(self, generator):
return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator)))
def join(self, iterable):
return self.joiner.join(s for s in iterable if len(s) > 0)

def define(self):
return self.join(child.define() for child in self.children if child is not None)

def __len__(self):
return len(self.children)


class CGIfElseWrapper(CGList):
def __init__(self, condition, ifTrue, ifFalse):
Expand Down Expand Up @@ -2145,6 +2239,49 @@ def definition_body(self):
raise NotImplementedError # Override me!


class CGConstructorEnabled(CGAbstractMethod):
"""
A method for testing whether we should be exposing this interface
object or navigator property. This can perform various tests
depending on what conditions are specified on the interface.
"""
def __init__(self, descriptor):
CGAbstractMethod.__init__(self, descriptor,
'ConstructorEnabled', 'bool',
[Argument("*mut JSContext", "aCx"),
Argument("HandleObject", "aObj")])

def definition_body(self):
body = CGList([], "\n")

conditions = []
iface = self.descriptor.interface

pref = iface.getExtendedAttribute("Pref")
if pref:
assert isinstance(pref, list) and len(pref) == 1
conditions.append('prefs::get_pref("%s").as_boolean().unwrap_or(false)' % pref[0])
func = iface.getExtendedAttribute("Func")
if func:
assert isinstance(func, list) and len(func) == 1
conditions.append("%s(aCx, aObj)" % func[0])
# We should really have some conditions
assert len(body) or len(conditions)

conditionsWrapper = ""
if len(conditions):
conditionsWrapper = CGWrapper(CGList((CGGeneric(cond) for cond in conditions),
" &&\n"),
pre="return ",
post=";\n",
reindent=True)
else:
conditionsWrapper = CGGeneric("return true;\n")

body.append(conditionsWrapper)
return body


def CreateBindingJSObject(descriptor, parent=None):
create = "let raw = Box::into_raw(object);\nlet _rt = RootedTraceable::new(&*raw);\n"
if descriptor.proxy:
Expand Down Expand Up @@ -2191,8 +2328,8 @@ def InitUnforgeablePropertiesOnHolder(descriptor, properties):
"""
unforgeables = []

defineUnforgeableAttrs = "define_properties(cx, unforgeable_holder.handle(), %s).unwrap();"
defineUnforgeableMethods = "define_methods(cx, unforgeable_holder.handle(), %s).unwrap();"
defineUnforgeableAttrs = "define_prefable_properties(cx, unforgeable_holder.handle(), %s);"
defineUnforgeableMethods = "define_prefable_methods(cx, unforgeable_holder.handle(), %s);"

unforgeableMembers = [
(defineUnforgeableAttrs, properties.unforgeable_attrs),
Expand Down Expand Up @@ -2684,15 +2821,21 @@ def define(self):
return CGAbstractMethod.define(self)

def definition_body(self):
def getCheck(desc):
if not desc.isExposedConditionally():
return ""
else:
return "if !ConstructorEnabled(cx, global) { return; }"
if self.descriptor.interface.isCallback():
function = "GetConstructorObject"
else:
function = "GetProtoObject"
return CGGeneric("""\
assert!(!global.get().is_null());
%s
let mut proto = RootedObject::new(cx, ptr::null_mut());
%s(cx, global, proto.handle_mut());
assert!(!proto.ptr.is_null());""" % function)
assert!(!proto.ptr.is_null());""" % (getCheck(self.descriptor), function))


def needCx(returnType, arguments, considerTypes):
Expand Down Expand Up @@ -5003,6 +5146,8 @@ def __init__(self, descriptor):

if descriptor.interface.hasInterfaceObject():
cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
if descriptor.isExposedConditionally():
cgThings.append(CGConstructorEnabled(descriptor))

if descriptor.proxy:
cgThings.append(CGDefineProxyHandler(descriptor))
Expand Down Expand Up @@ -5405,12 +5550,13 @@ def __init__(self, config, prefix, webIDLFile):
'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}',
'dom::bindings::js::{OptionalRootedReference}',
'dom::bindings::reflector::{Reflectable}',
'dom::bindings::utils::{DOMClass, DOMJSClass}',
'dom::bindings::utils::{DOMClass, DOMJSClass, Prefable}',
'dom::bindings::utils::{DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, JSCLASS_DOM_GLOBAL}',
'dom::bindings::utils::{ProtoOrIfaceArray, create_dom_global}',
'dom::bindings::utils::{enumerate_global, finalize_global, find_enum_string_index}',
Expand Down Expand Up @@ -5444,6 +5590,7 @@ def __init__(self, config, prefix, webIDLFile):
'dom::bindings::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}',
'mem::heap_size_of_raw_self_and_children',
'libc',
'util::prefs',
'util::str::DOMString',
'std::borrow::ToOwned',
'std::cmp',
Expand Down
9 changes: 9 additions & 0 deletions components/script/dom/bindings/codegen/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ def __init__(self, config, interface, desc):
DescriptorProvider.__init__(self, config)
self.interface = interface

if not self.isExposedConditionally():
if interface.parent and interface.parent.isExposedConditionally():
raise TypeError("%s is not conditionally exposed but inherits from "
"%s which is" %
(interface.identifier.name, interface.parent.identifier.name))

# Read the desc, and fill in the relevant defaults.
ifaceName = self.interface.identifier.name

Expand Down Expand Up @@ -351,6 +357,9 @@ def shouldHaveGetConstructorObjectMethod(self):
assert self.interface.hasInterfaceObject()
return self.interface.isCallback() or self.hasDescendants()

def isExposedConditionally(self):
return self.interface.isExposedConditionally()

def isGlobal(self):
"""
Returns true if this is the primary interface for a global object
Expand Down
Loading

0 comments on commit 8255e74

Please sign in to comment.