Auto merge of #23500 - sreeise:exposed_binding_gen, r=jdm

Make bindings aware of exposed members/partial interfaces

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [ ] These changes fix #23332

<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23500)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2019-07-14 12:53:14 -04:00 committed by GitHub
commit 95b304b786
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 151 additions and 78 deletions

View file

@ -1513,7 +1513,7 @@ def getRetvalDeclarationForType(returnType, descriptorProvider):
returnType)
def MemberCondition(pref, func):
def MemberCondition(pref, func, exposed):
"""
A string representing the condition for a member to actually be exposed.
Any of the arguments can be None. If not None, they should have the
@ -1521,14 +1521,18 @@ def MemberCondition(pref, func):
pref: The name of the preference.
func: The name of the function.
exposed: One or more names of an exposed global.
"""
assert pref is None or isinstance(pref, str)
assert func is None or isinstance(func, str)
assert func is None or pref is None
assert exposed is None or isinstance(exposed, set)
assert func is None or pref is None or exposed is None
if pref:
return 'Condition::Pref("%s")' % pref
if func:
return 'Condition::Func(%s)' % func
if exposed:
return ["Condition::Exposed(InterfaceObjectMap::Globals::%s)" % camel_to_upper_snake(i) for i in exposed]
return "Condition::Satisfied"
@ -1571,7 +1575,8 @@ class PropertyDefiner:
PropertyDefiner.getStringAttr(interfaceMember,
"Pref"),
PropertyDefiner.getStringAttr(interfaceMember,
"Func"))
"Func"),
interfaceMember.exposedSet())
def generateGuardedArray(self, array, name, specTemplate, specTerminator,
specType, getCondition, getDataTuple):
@ -1609,8 +1614,13 @@ class PropertyDefiner:
if specTerminator:
currentSpecs.append(specTerminator)
specs.append("&[\n" + ",\n".join(currentSpecs) + "]\n")
prefableSpecs.append(
prefableTemplate % (cond, name + "_specs", len(specs) - 1))
if isinstance(cond, list):
for i in cond:
prefableSpecs.append(
prefableTemplate % (i, name + "_specs", len(specs) - 1))
else:
prefableSpecs.append(
prefableTemplate % (cond, name + "_specs", len(specs) - 1))
specsArray = ("const %s_specs: &'static [&'static[%s]] = &[\n" +
",\n".join(specs) + "\n" +
@ -2640,8 +2650,8 @@ def InitUnforgeablePropertiesOnHolder(descriptor, properties):
"""
unforgeables = []
defineUnforgeableAttrs = "define_guarded_properties(cx, unforgeable_holder.handle(), %s);"
defineUnforgeableMethods = "define_guarded_methods(cx, unforgeable_holder.handle(), %s);"
defineUnforgeableAttrs = "define_guarded_properties(cx, unforgeable_holder.handle(), %s, global);"
defineUnforgeableMethods = "define_guarded_methods(cx, unforgeable_holder.handle(), %s, global);"
unforgeableMembers = [
(defineUnforgeableAttrs, properties.unforgeable_attrs),
@ -2751,7 +2761,7 @@ class CGWrapGlobalMethod(CGAbstractMethod):
("define_guarded_methods", self.properties.methods),
("define_guarded_constants", self.properties.consts)
]
members = ["%s(cx, obj.handle(), %s);" % (function, array.variableName())
members = ["%s(cx, obj.handle(), %s, obj.handle());" % (function, array.variableName())
for (function, array) in pairs if array.length() > 0]
values["members"] = "\n".join(members)
@ -2966,6 +2976,7 @@ assert!(!prototype_proto.is_null());""" % getPrototypeProto)]
code.append(CGGeneric("""
rooted!(in(cx) let mut prototype = ptr::null_mut::<JSObject>());
create_interface_prototype_object(cx,
global.into(),
prototype_proto.handle().into(),
&PrototypeClass,
%(methods)s,
@ -7543,6 +7554,7 @@ impl %(base)s {
for m in descriptor.interface.members:
if PropertyDefiner.getStringAttr(m, 'Pref') or \
PropertyDefiner.getStringAttr(m, 'Func') or \
PropertyDefiner.getStringAttr(m, 'Exposed') or \
(m.isMethod() and m.isIdentifierLess()):
continue
display = m.identifier.name + ('()' if m.isMethod() else '')

View file

@ -3723,6 +3723,7 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
IDLExposureMixins.__init__(self, location)
self.tag = tag
self.exposed = set()
if extendedAttrDict is None:
self._extendedAttrDict = {}
else:
@ -3756,12 +3757,16 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
def getExtendedAttribute(self, name):
return self._extendedAttrDict.get(name, None)
def exposedSet(self):
return self.exposed
def finish(self, scope):
# We better be exposed _somewhere_.
if (len(self._exposureGlobalNames) == 0):
print(self.identifier.name)
assert len(self._exposureGlobalNames) != 0
IDLExposureMixins.finish(self, scope)
globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposed)
def validate(self):
if self.isAttr() or self.isMethod():

View file

@ -0,0 +1,27 @@
--- WebIDL.py
+++ WebIDL.py
@@ -3653,6 +3653,7 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
IDLExposureMixins.__init__(self, location)
self.tag = tag
+ self.exposed = set()
if extendedAttrDict is None:
self._extendedAttrDict = {}
else:
@@ -3686,12 +3687,16 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
def getExtendedAttribute(self, name):
return self._extendedAttrDict.get(name, None)
+ def exposedSet(self):
+ return self.exposed
+
def finish(self, scope):
# We better be exposed _somewhere_.
if (len(self._exposureGlobalNames) == 0):
print self.identifier.name
assert len(self._exposureGlobalNames) != 0
IDLExposureMixins.finish(self, scope)
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposed)
def validate(self):
if self.isAttr() or self.isMethod():

View file

@ -4,6 +4,7 @@ patch < debug.patch
patch < callback-location.patch
patch < union-typedef.patch
patch < inline.patch
patch < exposed-globals.patch
wget https://hg.mozilla.org/mozilla-central/archive/tip.tar.gz/dom/bindings/parser/tests/ -O tests.tar.gz
rm -r tests

View file

@ -4,6 +4,8 @@
//! Machinery to conditionally expose things.
use crate::dom::bindings::codegen::InterfaceObjectMap;
use crate::dom::bindings::interface::is_exposed_in;
use js::jsapi::JSContext;
use js::rust::HandleObject;
use servo_config::prefs;
@ -26,8 +28,13 @@ impl<T: Clone + Copy> Guard<T> {
/// Expose the value if the condition is satisfied.
///
/// The passed handle is the object on which the value may be exposed.
pub unsafe fn expose(&self, cx: *mut JSContext, obj: HandleObject) -> Option<T> {
if self.condition.is_satisfied(cx, obj) {
pub unsafe fn expose(
&self,
cx: *mut JSContext,
obj: HandleObject,
global: HandleObject,
) -> Option<T> {
if self.condition.is_satisfied(cx, obj, global) {
Some(self.value)
} else {
None
@ -41,15 +48,23 @@ pub enum Condition {
Func(unsafe fn(*mut JSContext, HandleObject) -> bool),
/// The condition is satisfied if the preference is set.
Pref(&'static str),
// The condition is satisfied if the interface is exposed in the global.
Exposed(InterfaceObjectMap::Globals),
/// The condition is always satisfied.
Satisfied,
}
impl Condition {
unsafe fn is_satisfied(&self, cx: *mut JSContext, obj: HandleObject) -> bool {
unsafe fn is_satisfied(
&self,
cx: *mut JSContext,
obj: HandleObject,
global: HandleObject,
) -> bool {
match *self {
Condition::Pref(name) => prefs::pref_map().get(name).as_bool().unwrap_or(false),
Condition::Func(f) => f(cx, obj),
Condition::Exposed(globals) => is_exposed_in(global, globals),
Condition::Satisfied => true,
}
}

View file

@ -173,7 +173,7 @@ pub unsafe fn create_callback_interface_object(
assert!(!constants.is_empty());
rval.set(JS_NewObject(cx, ptr::null()));
assert!(!rval.is_null());
define_guarded_constants(cx, rval.handle(), constants);
define_guarded_constants(cx, rval.handle(), constants, global);
define_name(cx, rval.handle(), name);
define_on_global_object(cx, global, name, rval.handle());
}
@ -181,6 +181,7 @@ pub unsafe fn create_callback_interface_object(
/// Create the interface prototype object of a non-callback interface.
pub unsafe fn create_interface_prototype_object(
cx: *mut JSContext,
global: HandleObject,
proto: HandleObject,
class: &'static JSClass,
regular_methods: &[Guard<&'static [JSFunctionSpec]>],
@ -191,6 +192,7 @@ pub unsafe fn create_interface_prototype_object(
) {
create_object(
cx,
global,
proto,
class,
regular_methods,
@ -233,6 +235,7 @@ pub unsafe fn create_noncallback_interface_object(
) {
create_object(
cx,
global,
proto,
class.as_jsclass(),
static_methods,
@ -288,6 +291,7 @@ pub unsafe fn create_named_constructors(
/// Create a new object with a unique type.
pub unsafe fn create_object(
cx: *mut JSContext,
global: HandleObject,
proto: HandleObject,
class: &'static JSClass,
methods: &[Guard<&'static [JSFunctionSpec]>],
@ -297,9 +301,9 @@ pub unsafe fn create_object(
) {
rval.set(JS_NewObjectWithUniqueType(cx, class, proto));
assert!(!rval.is_null());
define_guarded_methods(cx, rval.handle(), methods);
define_guarded_properties(cx, rval.handle(), properties);
define_guarded_constants(cx, rval.handle(), constants);
define_guarded_methods(cx, rval.handle(), methods, global);
define_guarded_properties(cx, rval.handle(), properties, global);
define_guarded_constants(cx, rval.handle(), constants, global);
}
/// Conditionally define constants on an object.
@ -307,9 +311,10 @@ pub unsafe fn define_guarded_constants(
cx: *mut JSContext,
obj: HandleObject,
constants: &[Guard<&[ConstantSpec]>],
global: HandleObject,
) {
for guard in constants {
if let Some(specs) = guard.expose(cx, obj) {
if let Some(specs) = guard.expose(cx, obj, global) {
define_constants(cx, obj, specs);
}
}
@ -320,9 +325,10 @@ pub unsafe fn define_guarded_methods(
cx: *mut JSContext,
obj: HandleObject,
methods: &[Guard<&'static [JSFunctionSpec]>],
global: HandleObject,
) {
for guard in methods {
if let Some(specs) = guard.expose(cx, obj) {
if let Some(specs) = guard.expose(cx, obj, global) {
define_methods(cx, obj, specs).unwrap();
}
}
@ -333,9 +339,10 @@ pub unsafe fn define_guarded_properties(
cx: *mut JSContext,
obj: HandleObject,
properties: &[Guard<&'static [JSPropertySpec]>],
global: HandleObject,
) {
for guard in properties {
if let Some(specs) = guard.expose(cx, obj) {
if let Some(specs) = guard.expose(cx, obj, global) {
define_properties(cx, obj, specs).unwrap();
}
}

View file

@ -37,6 +37,6 @@ pub unsafe fn create_namespace_object(
name: &[u8],
rval: MutableHandleObject,
) {
create_object(cx, proto, &class.0, methods, &[], &[], rval);
create_object(cx, global, proto, &class.0, methods, &[], &[], rval);
define_on_global_object(cx, global, name, rval.handle());
}

View file

@ -1087,6 +1087,18 @@ impl TestBindingMethods for TestBinding {
fn IncumbentGlobal(&self) -> DomRoot<GlobalScope> {
GlobalScope::incumbent().unwrap()
}
fn SemiExposedBoolFromInterface(&self) -> bool {
true
}
fn BoolFromSemiExposedPartialInterface(&self) -> bool {
true
}
fn SemiExposedBoolFromPartialInterface(&self) -> bool {
true
}
}
impl TestBinding {

View file

@ -9,7 +9,7 @@
[
ExceptionClass,
Exposed=(Window,Worker),
Exposed=(Window,Worker,Worklet,DissimilarOriginWindow),
Constructor(optional DOMString message="", optional DOMString name="Error")
]
interface DOMException {

View file

@ -14,7 +14,8 @@
// way to enforce security policy.
// https://html.spec.whatwg.org/multipage/#location
[Unforgeable, NoInterfaceObject] interface DissimilarOriginLocation {
[Exposed=(Window,DissimilarOriginWindow), Unforgeable, NoInterfaceObject]
interface DissimilarOriginLocation {
[Throws] attribute USVString href;
[Throws] void assign(USVString url);
[Throws] void replace(USVString url);

View file

@ -13,7 +13,7 @@
// way to enforce security policy.
// https://html.spec.whatwg.org/multipage/#window
[Global, NoInterfaceObject]
[Global, Exposed=(Window,DissimilarOriginWindow), NoInterfaceObject]
interface DissimilarOriginWindow : GlobalScope {
[Unforgeable] readonly attribute WindowProxy window;
[BinaryName="Self_", Replaceable] readonly attribute WindowProxy self;

View file

@ -5,7 +5,7 @@
* https://dom.spec.whatwg.org/#interface-eventtarget
*/
[Constructor, Exposed=(Window,Worker,Worklet)]
[Constructor, Exposed=(Window,Worker,Worklet,DissimilarOriginWindow)]
interface EventTarget {
void addEventListener(
DOMString type,

View file

@ -5,6 +5,6 @@
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[Exposed=(Window,Worker,Worklet),
[Exposed=(Window,Worker,Worklet,DissimilarOriginWindow),
Inline]
interface GlobalScope : EventTarget {};

View file

@ -556,6 +556,19 @@ interface TestBinding {
GlobalScope entryGlobal();
GlobalScope incumbentGlobal();
[Exposed=(Window)]
readonly attribute boolean semiExposedBoolFromInterface;
};
[Exposed=(Window)]
partial interface TestBinding {
readonly attribute boolean boolFromSemiExposedPartialInterface;
};
partial interface TestBinding {
[Exposed=(Window)]
readonly attribute boolean semiExposedBoolFromPartialInterface;
};
callback SimpleCallback = void(any value);

View file

@ -3,5 +3,5 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
// https://html.spec.whatwg.org/multipage/#the-windowproxy-exotic-object
[NoInterfaceObject]
[Exposed=(Window,DissimilarOriginWindow), NoInterfaceObject]
interface WindowProxy {};

View file

@ -689369,7 +689369,7 @@
"testharness"
],
"xhr/abort-after-send.any.js": [
"41922c915a653ee96e949a3c8ce2aeeb4fd0630c",
"0ffd8877f87f9255668409c1fc9e973d006e6ae9",
"testharness"
],
"xhr/abort-after-stop.any.js": [

View file

@ -630,24 +630,6 @@
[WorkerNavigator interface: self.navigator must not have property "taintEnabled"]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "vendor"]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "vendorSub"]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "productSub"]
expected: FAIL
[WorkerNavigator interface: member vendor]
expected: FAIL
[WorkerNavigator interface: member vendorSub]
expected: FAIL
[WorkerNavigator interface: member productSub]
expected: FAIL
[OffscreenCanvasRenderingContext2D interface: operation arc(unrestricted double, unrestricted double, unrestricted double, unrestricted double, unrestricted double, boolean)]
expected: FAIL

View file

@ -30,24 +30,6 @@
[WorkerNavigator interface: attribute onLine]
expected: FAIL
[WorkerNavigator interface: member productSub]
expected: FAIL
[WorkerNavigator interface: member vendor]
expected: FAIL
[WorkerNavigator interface: member vendorSub]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "productSub"]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "vendor"]
expected: FAIL
[WorkerNavigator interface: self.navigator must not have property "vendorSub"]
expected: FAIL
[WorkerNavigator interface: self.navigator must inherit property "languages" with the proper type (7)]
expected: FAIL

View file

@ -12,9 +12,6 @@
[Resource timing seems to work in workers]
expected: FAIL
[performance.timing is not available in workers]
expected: FAIL
[performance.toJSON is available in workers]
expected: FAIL

View file

@ -18,12 +18,6 @@
[idlharness]
expected: FAIL
[XMLHttpRequest interface: new XMLHttpRequest() must not have property "responseXML"]
expected: FAIL
[XMLHttpRequest interface: member responseXML]
expected: FAIL
[Testing Symbol.iterator property of iterable interface FormData]
expected: FAIL

View file

@ -1,5 +0,0 @@
[responseXML-unavailable-in-worker.html]
type: testharness
[XMLHttpRequest's responseXML property should not be exposed in workers.]
expected: FAIL

View file

@ -9735,6 +9735,9 @@
"mozilla/webgl/tex_image_2d_simple_ref.html": [
[]
],
"mozilla/worker_member_test.js": [
[]
],
"mozilla/worklets/syntax_error.js": [
[]
],
@ -18895,7 +18898,7 @@
"testharness"
],
"mozilla/interface_member_exposed.html": [
"dd637cf92a894e4569e8fb0baf11eea6968033af",
"f408f9c3dae4b78b49bf77b5ad32c0d5ee406f7e",
"testharness"
],
"mozilla/interfaces.html": [
@ -19594,6 +19597,10 @@
"d5c75899eb546d7243d65b6f55e876c5008c6292",
"testharness"
],
"mozilla/worker_member_test.js": [
"abca5cd280ac07914cb21ee4968ac4d27e7feb68",
"support"
],
"mozilla/worklets/syntax_error.js": [
"4adade8939ce62eb5e83d73d4faf2261b264d809",
"support"

View file

@ -43,4 +43,22 @@ for (var i = 0; i < staticMembers.length; i++) {
test_member(name + 'Enabled', true, function(o) { return o; });
test_member(name + 'Disabled', false, function(o) { return o; });
}
members = [
'semiExposedBoolFromInterface',
'boolFromSemiExposedPartialInterface',
'semiExposedBoolFromPartialInterface',
];
for (const member of members) {
var interface = new TestBinding();
async_test(function(t) {
assert_true(member in interface);
assert_true(interface[member]);
let w = new Worker("worker_member_test.js?" + member);
w.onmessage = t.step_func(function(e) {
assert_equals(e.data, undefined);
t.done();
});
}, member);
}
</script>

View file

@ -0,0 +1,3 @@
let member = location.search.slice(1);
var binding = new TestBinding();
postMessage(binding[member]);

View file

@ -9,7 +9,9 @@
client.addEventListener("readystatechange", test.step_func(function() {
if(client.readyState == 4) {
control_flag = true
assert_equals(client.responseXML, null)
if (self.GLOBAL.isWindow()) {
assert_equals(client.responseXML, null)
}
assert_equals(client.responseText, "")
assert_equals(client.status, 0)
assert_equals(client.statusText, "")