mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1446246 part 1. Use a single handwritten HTMLConstructor implementation, instead of code-generating lots of very similar implementations. r=peterv
The codegen changes are mostly a backout of the changes made in bug 1274159. The HTMLConstructor implementation is mostly copied from one of the code-generated ones, with a few modifications: * Derive the interface name from the proto id instead of hardcoding it. * Use the proto/constructor ids to get constructor and prototype objects. * Use ErrorResult instead of FastErrorResult; we don't want the precedent of using FastErrorResult in non-generated code. There will be further changes to combine HTMLConstructor and CreateXULOrHTMLElement, in a separate changeset. MozReview-Commit-ID: 44D0qw23ioP
This commit is contained in:
parent
6e860b3623
commit
753e5af2f1
@ -3511,6 +3511,102 @@ GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor
|
||||
namespace binding_detail {
|
||||
bool
|
||||
HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
|
||||
constructors::id::ID aConstructorId,
|
||||
prototypes::id::ID aProtoId,
|
||||
CreateInterfaceObjectsMethod aCreator)
|
||||
{
|
||||
JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
|
||||
JS::Rooted<JSObject*> obj(aCx, &args.callee());
|
||||
if (!args.isConstructing()) {
|
||||
return ThrowConstructorWithoutNew(aCx,
|
||||
NamesOfInterfacesWithProtos(aProtoId));
|
||||
}
|
||||
|
||||
GlobalObject global(aCx, obj);
|
||||
if (global.Failed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The newTarget might be a cross-compartment wrapper. Get the underlying object
|
||||
// so we can do the spec's object-identity checks.
|
||||
JS::Rooted<JSObject*> newTarget(aCx, js::CheckedUnwrap(&args.newTarget().toObject()));
|
||||
if (!newTarget) {
|
||||
return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
|
||||
}
|
||||
|
||||
// Step 2 of https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor.
|
||||
// Enter the compartment of our underlying newTarget object, so we end
|
||||
// up comparing to the constructor object for our interface from that global.
|
||||
{
|
||||
JSAutoCompartment ac(aCx, newTarget);
|
||||
JS::Handle<JSObject*> constructor =
|
||||
GetPerInterfaceObjectHandle(aCx, aConstructorId, aCreator,
|
||||
true);
|
||||
if (!constructor) {
|
||||
return false;
|
||||
}
|
||||
if (newTarget == constructor) {
|
||||
return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> desiredProto(aCx);
|
||||
if (!GetDesiredProto(aCx, args, &desiredProto)) {
|
||||
return false;
|
||||
}
|
||||
if (!desiredProto) {
|
||||
// Step 7 of https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor.
|
||||
// This fallback behavior is designed to match analogous behavior for the
|
||||
// JavaScript built-ins. So we enter the compartment of our underlying
|
||||
// newTarget object and fall back to the prototype object from that global.
|
||||
// XXX The spec says to use GetFunctionRealm(), which is not actually
|
||||
// the same thing as what we have here (e.g. in the case of scripted callable proxies
|
||||
// whose target is not same-compartment with the proxy, or bound functions, etc).
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1317658
|
||||
{
|
||||
JSAutoCompartment ac(aCx, newTarget);
|
||||
desiredProto = GetPerInterfaceObjectHandle(aCx, aProtoId, aCreator, true);
|
||||
if (!desiredProto) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// desiredProto is in the compartment of the underlying newTarget object.
|
||||
// Wrap it into the context compartment.
|
||||
if (!JS_WrapObject(aCx, &desiredProto)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool objIsXray = xpc::WrapperFactory::IsXrayWrapper(obj);
|
||||
Maybe<JSAutoCompartment> ac;
|
||||
if (objIsXray) {
|
||||
obj = js::CheckedUnwrap(obj);
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
ac.emplace(aCx, obj);
|
||||
if (!JS_WrapObject(aCx, &desiredProto)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ErrorResult rv;
|
||||
RefPtr<Element> result = CreateXULOrHTMLElement(global, args, desiredProto, rv);
|
||||
if (MOZ_UNLIKELY(rv.MaybeSetPendingException(aCx))) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
|
||||
if (!GetOrCreateDOMReflector(aCx, result, args.rval(), desiredProto)) {
|
||||
MOZ_ASSERT(JS_IsExceptionPending(aCx));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace binding_detail
|
||||
|
||||
already_AddRefed<Element>
|
||||
CreateXULOrHTMLElement(const GlobalObject& aGlobal, const JS::CallArgs& aCallArgs,
|
||||
JS::Handle<JSObject*> aGivenProto, ErrorResult& aRv)
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "mozilla/dom/Exceptions.h"
|
||||
#include "mozilla/dom/NonRefcountedDOMObject.h"
|
||||
#include "mozilla/dom/Nullable.h"
|
||||
#include "mozilla/dom/PrototypeList.h"
|
||||
#include "mozilla/dom/RootedDictionary.h"
|
||||
#include "mozilla/SegmentedVector.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
@ -3447,6 +3448,13 @@ namespace binding_detail {
|
||||
// understanding of all the code that will run while we're using the return
|
||||
// value, including the SpiderMonkey parts.
|
||||
JSObject* UnprivilegedJunkScopeOrWorkerGlobal();
|
||||
|
||||
// Implementation of the [HTMLConstructor] extended attribute.
|
||||
bool
|
||||
HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
|
||||
constructors::id::ID aConstructorId,
|
||||
prototypes::id::ID aProtoId,
|
||||
CreateInterfaceObjectsMethod aCreator);
|
||||
} // namespace binding_detail
|
||||
|
||||
} // namespace dom
|
||||
|
@ -1825,6 +1825,26 @@ class CGClassConstructor(CGAbstractStaticMethod):
|
||||
return self.generate_code()
|
||||
|
||||
def generate_code(self):
|
||||
if self._ctor.isHTMLConstructor():
|
||||
# We better have a prototype object. Otherwise our proto
|
||||
# id won't make sense.
|
||||
assert self.descriptor.interface.hasInterfacePrototypeObject()
|
||||
# We also better have a constructor object, if this is
|
||||
# getting called!
|
||||
assert self.descriptor.interface.hasInterfaceObject()
|
||||
# We can't just pass null for the CreateInterfaceObjects callback,
|
||||
# because our newTarget might be in a different compartment, in
|
||||
# which case we'll need to look up constructor objects in that
|
||||
# compartment.
|
||||
return fill(
|
||||
"""
|
||||
return binding_detail::HTMLConstructor(cx, argc, vp,
|
||||
constructors::id::${name},
|
||||
prototypes::id::${name},
|
||||
CreateInterfaceObjects);
|
||||
""",
|
||||
name=self.descriptor.name)
|
||||
|
||||
# [ChromeOnly] interfaces may only be constructed by chrome.
|
||||
chromeOnlyCheck = ""
|
||||
if isChromeOnly(self._ctor):
|
||||
@ -1848,71 +1868,6 @@ class CGClassConstructor(CGAbstractStaticMethod):
|
||||
else:
|
||||
ctorName = self.descriptor.interface.identifier.name
|
||||
|
||||
# [HTMLConstructor] for custom element
|
||||
# This needs to live in bindings code because it directly examines
|
||||
# newtarget and the callee function to do HTMLConstructor specific things.
|
||||
if self._ctor.isHTMLConstructor():
|
||||
htmlConstructorSanityCheck = dedent("""
|
||||
// The newTarget might be a cross-compartment wrapper. Get the underlying object
|
||||
// so we can do the spec's object-identity checks.
|
||||
JS::Rooted<JSObject*> newTarget(cx, js::CheckedUnwrap(&args.newTarget().toObject()));
|
||||
if (!newTarget) {
|
||||
return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR);
|
||||
}
|
||||
|
||||
// Step 2 of https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor.
|
||||
// Enter the compartment of our underlying newTarget object, so we end
|
||||
// up comparing to the constructor object for our interface from that global.
|
||||
{
|
||||
JSAutoCompartment ac(cx, newTarget);
|
||||
JS::Handle<JSObject*> constructor(GetConstructorObjectHandle(cx));
|
||||
if (!constructor) {
|
||||
return false;
|
||||
}
|
||||
if (newTarget == constructor) {
|
||||
return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR);
|
||||
}
|
||||
}
|
||||
|
||||
""")
|
||||
|
||||
# If we are unable to get desired prototype from newTarget, then we
|
||||
# fall back to the interface prototype object from newTarget's realm.
|
||||
htmlConstructorFallback = dedent("""
|
||||
if (!desiredProto) {
|
||||
// Step 7 of https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor.
|
||||
// This fallback behavior is designed to match analogous behavior for the
|
||||
// JavaScript built-ins. So we enter the compartment of our underlying
|
||||
// newTarget object and fall back to the prototype object from that global.
|
||||
// XXX The spec says to use GetFunctionRealm(), which is not actually
|
||||
// the same thing as what we have here (e.g. in the case of scripted callable proxies
|
||||
// whose target is not same-compartment with the proxy, or bound functions, etc).
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1317658
|
||||
{
|
||||
JSAutoCompartment ac(cx, newTarget);
|
||||
desiredProto = GetProtoObjectHandle(cx);
|
||||
if (!desiredProto) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// desiredProto is in the compartment of the underlying newTarget object.
|
||||
// Wrap it into the context compartment.
|
||||
if (!JS_WrapObject(cx, &desiredProto)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
""")
|
||||
else:
|
||||
htmlConstructorSanityCheck = ""
|
||||
htmlConstructorFallback = ""
|
||||
|
||||
|
||||
# If we're a constructor, "obj" may not be a function, so calling
|
||||
# XrayAwareCalleeGlobal() on it is not safe. Of course in the
|
||||
# constructor case either "obj" is an Xray or we're already in the
|
||||
# content compartment, not the Xray compartment, so just
|
||||
# constructing the GlobalObject from "obj" is fine.
|
||||
preamble = fill(
|
||||
"""
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
@ -1924,40 +1879,19 @@ class CGClassConstructor(CGAbstractStaticMethod):
|
||||
return ThrowConstructorWithoutNew(cx, "${ctorName}");
|
||||
}
|
||||
|
||||
GlobalObject global(cx, obj);
|
||||
if (global.Failed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$*{htmlConstructorSanityCheck}
|
||||
JS::Rooted<JSObject*> desiredProto(cx);
|
||||
if (!GetDesiredProto(cx, args, &desiredProto)) {
|
||||
return false;
|
||||
}
|
||||
$*{htmlConstructorFallback}
|
||||
""",
|
||||
chromeOnlyCheck=chromeOnlyCheck,
|
||||
ctorName=ctorName,
|
||||
htmlConstructorSanityCheck=htmlConstructorSanityCheck,
|
||||
htmlConstructorFallback=htmlConstructorFallback)
|
||||
ctorName=ctorName)
|
||||
|
||||
if self._ctor.isHTMLConstructor():
|
||||
signatures = self._ctor.signatures()
|
||||
assert len(signatures) == 1
|
||||
# Given that HTMLConstructor takes no args, we can just codegen a
|
||||
# call to CreateXULOrHTMLElement() in BindingUtils which reuses the
|
||||
# factory thing in HTMLContentSink. Then we don't have to implement
|
||||
# Constructor on all the HTML elements.
|
||||
callGenerator = CGPerSignatureCall(signatures[0][0], signatures[0][1],
|
||||
"CreateXULOrHTMLElement", True,
|
||||
self.descriptor, self._ctor,
|
||||
isConstructor=True)
|
||||
else:
|
||||
name = self._ctor.identifier.name
|
||||
nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
|
||||
callGenerator = CGMethodCall(nativeName, True, self.descriptor,
|
||||
self._ctor, isConstructor=True,
|
||||
constructorName=ctorName)
|
||||
name = self._ctor.identifier.name
|
||||
nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
|
||||
callGenerator = CGMethodCall(nativeName, True, self.descriptor,
|
||||
self._ctor, isConstructor=True,
|
||||
constructorName=ctorName)
|
||||
return preamble + "\n" + callGenerator.define()
|
||||
|
||||
def profiler_label_and_jscontext(self):
|
||||
@ -7762,23 +7696,26 @@ class CGPerSignatureCall(CGThing):
|
||||
|
||||
argsPre = []
|
||||
if idlNode.isStatic():
|
||||
# If we're a constructor, the GlobalObject struct will be created in
|
||||
# CGClassConstructor.
|
||||
if not isConstructor:
|
||||
cgThings.append(CGGeneric(dedent(
|
||||
"""
|
||||
GlobalObject global(cx, xpc::XrayAwareCalleeGlobal(obj));
|
||||
if (global.Failed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
""")))
|
||||
# If we're a constructor, "obj" may not be a function, so calling
|
||||
# XrayAwareCalleeGlobal() on it is not safe. Of course in the
|
||||
# constructor case either "obj" is an Xray or we're already in the
|
||||
# content compartment, not the Xray compartment, so just
|
||||
# constructing the GlobalObject from "obj" is fine.
|
||||
if isConstructor:
|
||||
objForGlobalObject = "obj"
|
||||
else:
|
||||
objForGlobalObject = "xpc::XrayAwareCalleeGlobal(obj)"
|
||||
cgThings.append(CGGeneric(fill(
|
||||
"""
|
||||
GlobalObject global(cx, ${obj});
|
||||
if (global.Failed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
""",
|
||||
obj=objForGlobalObject)))
|
||||
argsPre.append("global")
|
||||
|
||||
if isConstructor and idlNode.isHTMLConstructor():
|
||||
argsPre.extend(["args", "desiredProto"])
|
||||
|
||||
# For JS-implemented interfaces we do not want to base the
|
||||
# needsCx decision on the types involved, just on our extended
|
||||
# attributes. Also, JSContext is not needed for the static case
|
||||
|
Loading…
Reference in New Issue
Block a user