mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-30 18:56:40 +00:00
#399: ChildNode-ParentNode DOM4 M911477 M1301777 M1308922 M1104955 M1054759 M1258163
This commit is contained in:
parent
956fd74ca6
commit
82f1d5895c
@ -1650,6 +1650,168 @@ nsINode::GetNextElementSibling() const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static already_AddRefed<nsINode>
|
||||||
|
GetNodeFromNodeOrString(const OwningNodeOrString& aNode,
|
||||||
|
nsIDocument* aDocument)
|
||||||
|
{
|
||||||
|
if (aNode.IsNode()) {
|
||||||
|
nsCOMPtr<nsINode> node = aNode.GetAsNode();
|
||||||
|
return node.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aNode.IsString()){
|
||||||
|
RefPtr<nsTextNode> textNode =
|
||||||
|
aDocument->CreateTextNode(aNode.GetAsString());
|
||||||
|
return textNode.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_CRASH("Impossible type");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement the algorithm specified at
|
||||||
|
* https://dom.spec.whatwg.org/#converting-nodes-into-a-node for |prepend()|,
|
||||||
|
* |append()|, |before()|, |after()|, and |replaceWith()| APIs.
|
||||||
|
*/
|
||||||
|
static already_AddRefed<nsINode>
|
||||||
|
ConvertNodesOrStringsIntoNode(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
nsIDocument* aDocument,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (aNodes.Length() == 1) {
|
||||||
|
return GetNodeFromNodeOrString(aNodes[0], aDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> fragment = aDocument->CreateDocumentFragment();
|
||||||
|
|
||||||
|
for (const auto& node : aNodes) {
|
||||||
|
nsCOMPtr<nsINode> childNode = GetNodeFromNodeOrString(node, aDocument);
|
||||||
|
fragment->AppendChild(*childNode, aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
InsertNodesIntoHashset(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
nsTHashtable<nsPtrHashKey<nsINode>>& aHashset)
|
||||||
|
{
|
||||||
|
for (const auto& node : aNodes) {
|
||||||
|
if (node.IsNode()) {
|
||||||
|
aHashset.PutEntry(node.GetAsNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static nsINode*
|
||||||
|
FindViablePreviousSibling(const nsINode& aNode,
|
||||||
|
const Sequence<OwningNodeOrString>& aNodes)
|
||||||
|
{
|
||||||
|
nsTHashtable<nsPtrHashKey<nsINode>> nodeSet(16);
|
||||||
|
InsertNodesIntoHashset(aNodes, nodeSet);
|
||||||
|
|
||||||
|
nsINode* viablePreviousSibling = nullptr;
|
||||||
|
for (nsINode* sibling = aNode.GetPreviousSibling(); sibling;
|
||||||
|
sibling = sibling->GetPreviousSibling()) {
|
||||||
|
if (!nodeSet.Contains(sibling)) {
|
||||||
|
viablePreviousSibling = sibling;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return viablePreviousSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
static nsINode*
|
||||||
|
FindViableNextSibling(const nsINode& aNode,
|
||||||
|
const Sequence<OwningNodeOrString>& aNodes)
|
||||||
|
{
|
||||||
|
nsTHashtable<nsPtrHashKey<nsINode>> nodeSet(16);
|
||||||
|
InsertNodesIntoHashset(aNodes, nodeSet);
|
||||||
|
|
||||||
|
nsINode* viableNextSibling = nullptr;
|
||||||
|
for (nsINode* sibling = aNode.GetNextSibling(); sibling;
|
||||||
|
sibling = sibling->GetNextSibling()) {
|
||||||
|
if (!nodeSet.Contains(sibling)) {
|
||||||
|
viableNextSibling = sibling;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return viableNextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsINode::Before(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsINode> parent = GetParentNode();
|
||||||
|
if (!parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> viablePreviousSibling =
|
||||||
|
FindViablePreviousSibling(*this, aNodes);
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> node =
|
||||||
|
ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viablePreviousSibling = viablePreviousSibling ?
|
||||||
|
viablePreviousSibling->GetNextSibling() : parent->GetFirstChild();
|
||||||
|
|
||||||
|
parent->InsertBefore(*node, viablePreviousSibling, aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsINode::After(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsINode> parent = GetParentNode();
|
||||||
|
if (!parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> viableNextSibling = FindViableNextSibling(*this, aNodes);
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> node =
|
||||||
|
ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->InsertBefore(*node, viableNextSibling, aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsINode::ReplaceWith(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsINode> parent = GetParentNode();
|
||||||
|
if (!parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> viableNextSibling = FindViableNextSibling(*this, aNodes);
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> node =
|
||||||
|
ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent == GetParentNode()) {
|
||||||
|
parent->ReplaceChild(*node, *this, aRv);
|
||||||
|
} else {
|
||||||
|
parent->InsertBefore(*node, viableNextSibling, aRv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsINode::Remove()
|
nsINode::Remove()
|
||||||
{
|
{
|
||||||
@ -1693,6 +1855,33 @@ nsINode::GetLastElementChild() const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsINode::Prepend(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsINode> node =
|
||||||
|
ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> refNode = mFirstChild;
|
||||||
|
InsertBefore(*node, refNode, aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsINode::Append(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsINode> node =
|
||||||
|
ConvertNodesOrStringsIntoNode(aNodes, OwnerDoc(), aRv);
|
||||||
|
if (aRv.Failed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendChild(*node, aRv);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
|
nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
|
||||||
nsIContent* aKid, nsAttrAndChildArray& aChildArray)
|
nsIContent* aKid, nsAttrAndChildArray& aChildArray)
|
||||||
|
@ -75,6 +75,8 @@ class DOMRectReadOnly;
|
|||||||
class Element;
|
class Element;
|
||||||
class EventHandlerNonNull;
|
class EventHandlerNonNull;
|
||||||
template<typename T> class Optional;
|
template<typename T> class Optional;
|
||||||
|
class OwningNodeOrString;
|
||||||
|
template<typename> class Sequence;
|
||||||
class Text;
|
class Text;
|
||||||
class TextOrElementOrDocument;
|
class TextOrElementOrDocument;
|
||||||
struct DOMPointInit;
|
struct DOMPointInit;
|
||||||
@ -267,9 +269,13 @@ public:
|
|||||||
typedef mozilla::dom::DOMPointInit DOMPointInit;
|
typedef mozilla::dom::DOMPointInit DOMPointInit;
|
||||||
typedef mozilla::dom::DOMQuad DOMQuad;
|
typedef mozilla::dom::DOMQuad DOMQuad;
|
||||||
typedef mozilla::dom::DOMRectReadOnly DOMRectReadOnly;
|
typedef mozilla::dom::DOMRectReadOnly DOMRectReadOnly;
|
||||||
|
typedef mozilla::dom::OwningNodeOrString OwningNodeOrString;
|
||||||
typedef mozilla::dom::TextOrElementOrDocument TextOrElementOrDocument;
|
typedef mozilla::dom::TextOrElementOrDocument TextOrElementOrDocument;
|
||||||
typedef mozilla::ErrorResult ErrorResult;
|
typedef mozilla::ErrorResult ErrorResult;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using Sequence = mozilla::dom::Sequence<T>;
|
||||||
|
|
||||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
|
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
|
||||||
|
|
||||||
// Among the sub-classes that inherit (directly or indirectly) from nsINode,
|
// Among the sub-classes that inherit (directly or indirectly) from nsINode,
|
||||||
@ -1807,6 +1813,11 @@ public:
|
|||||||
// ChildNode methods
|
// ChildNode methods
|
||||||
mozilla::dom::Element* GetPreviousElementSibling() const;
|
mozilla::dom::Element* GetPreviousElementSibling() const;
|
||||||
mozilla::dom::Element* GetNextElementSibling() const;
|
mozilla::dom::Element* GetNextElementSibling() const;
|
||||||
|
|
||||||
|
void Before(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
|
||||||
|
void After(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
|
||||||
|
void ReplaceWith(const Sequence<OwningNodeOrString>& aNodes,
|
||||||
|
ErrorResult& aRv);
|
||||||
/**
|
/**
|
||||||
* Remove this node from its parent, if any.
|
* Remove this node from its parent, if any.
|
||||||
*/
|
*/
|
||||||
@ -1816,6 +1827,9 @@ public:
|
|||||||
mozilla::dom::Element* GetFirstElementChild() const;
|
mozilla::dom::Element* GetFirstElementChild() const;
|
||||||
mozilla::dom::Element* GetLastElementChild() const;
|
mozilla::dom::Element* GetLastElementChild() const;
|
||||||
|
|
||||||
|
void Prepend(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
|
||||||
|
void Append(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
|
||||||
|
|
||||||
void GetBoxQuads(const BoxQuadOptions& aOptions,
|
void GetBoxQuads(const BoxQuadOptions& aOptions,
|
||||||
nsTArray<RefPtr<DOMQuad> >& aResult,
|
nsTArray<RefPtr<DOMQuad> >& aResult,
|
||||||
mozilla::ErrorResult& aRv);
|
mozilla::ErrorResult& aRv);
|
||||||
|
@ -769,7 +769,8 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
|
|||||||
JS::Handle<JSObject*> parentProto,
|
JS::Handle<JSObject*> parentProto,
|
||||||
const js::Class* protoClass,
|
const js::Class* protoClass,
|
||||||
const NativeProperties* properties,
|
const NativeProperties* properties,
|
||||||
const NativeProperties* chromeOnlyProperties)
|
const NativeProperties* chromeOnlyProperties,
|
||||||
|
const char* const* unscopableNames)
|
||||||
{
|
{
|
||||||
JS::Rooted<JSObject*> ourProto(cx,
|
JS::Rooted<JSObject*> ourProto(cx,
|
||||||
JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
|
JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
|
||||||
@ -778,6 +779,28 @@ CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unscopableNames) {
|
||||||
|
JS::Rooted<JSObject*> unscopableObj(cx, JS_NewPlainObject(cx));
|
||||||
|
if (!unscopableObj) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; *unscopableNames; ++unscopableNames) {
|
||||||
|
if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
|
||||||
|
JS::TrueHandleValue, JSPROP_ENUMERATE)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JS::Rooted<jsid> unscopableId(cx,
|
||||||
|
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
|
||||||
|
// Readonly and non-enumerable to match Array.prototype.
|
||||||
|
if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
|
||||||
|
JSPROP_READONLY)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ourProto;
|
return ourProto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,7 +856,8 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
|
|||||||
JS::Heap<JSObject*>* constructorCache,
|
JS::Heap<JSObject*>* constructorCache,
|
||||||
const NativeProperties* properties,
|
const NativeProperties* properties,
|
||||||
const NativeProperties* chromeOnlyProperties,
|
const NativeProperties* chromeOnlyProperties,
|
||||||
const char* name, bool defineOnGlobal)
|
const char* name, bool defineOnGlobal,
|
||||||
|
const char* const* unscopableNames)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(protoClass || constructorClass || constructor,
|
MOZ_ASSERT(protoClass || constructorClass || constructor,
|
||||||
"Need at least one class or a constructor!");
|
"Need at least one class or a constructor!");
|
||||||
@ -864,7 +888,8 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
|
|||||||
if (protoClass) {
|
if (protoClass) {
|
||||||
proto =
|
proto =
|
||||||
CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
|
CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
|
||||||
properties, chromeOnlyProperties);
|
properties, chromeOnlyProperties,
|
||||||
|
unscopableNames);
|
||||||
if (!proto) {
|
if (!proto) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -611,6 +611,8 @@ struct NamedConstructor
|
|||||||
* false in situations where we want the properties to only
|
* false in situations where we want the properties to only
|
||||||
* appear on privileged Xrays but not on the unprivileged
|
* appear on privileged Xrays but not on the unprivileged
|
||||||
* underlying global.
|
* underlying global.
|
||||||
|
* unscopableNames if not null it points to a null-terminated list of const
|
||||||
|
* char* names of the unscopable properties for this interface.
|
||||||
*
|
*
|
||||||
* At least one of protoClass, constructorClass or constructor should be
|
* At least one of protoClass, constructorClass or constructor should be
|
||||||
* non-null. If constructorClass or constructor are non-null, the resulting
|
* non-null. If constructorClass or constructor are non-null, the resulting
|
||||||
@ -627,7 +629,8 @@ CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
|
|||||||
JS::Heap<JSObject*>* constructorCache,
|
JS::Heap<JSObject*>* constructorCache,
|
||||||
const NativeProperties* regularProperties,
|
const NativeProperties* regularProperties,
|
||||||
const NativeProperties* chromeOnlyProperties,
|
const NativeProperties* chromeOnlyProperties,
|
||||||
const char* name, bool defineOnGlobal);
|
const char* name, bool defineOnGlobal,
|
||||||
|
const char* const* unscopableNames);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the properties (regular and chrome-only) on obj.
|
* Define the properties (regular and chrome-only) on obj.
|
||||||
|
@ -2691,13 +2691,14 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
|||||||
|
|
||||||
properties should be a PropertyArrays instance.
|
properties should be a PropertyArrays instance.
|
||||||
"""
|
"""
|
||||||
def __init__(self, descriptor, properties):
|
def __init__(self, descriptor, properties, haveUnscopables):
|
||||||
args = [Argument('JSContext*', 'aCx'),
|
args = [Argument('JSContext*', 'aCx'),
|
||||||
Argument('JS::Handle<JSObject*>', 'aGlobal'),
|
Argument('JS::Handle<JSObject*>', 'aGlobal'),
|
||||||
Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
|
Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
|
||||||
Argument('bool', 'aDefineOnGlobal')]
|
Argument('bool', 'aDefineOnGlobal')]
|
||||||
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
|
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
|
||||||
self.properties = properties
|
self.properties = properties
|
||||||
|
self.haveUnscopables = haveUnscopables
|
||||||
|
|
||||||
def definition_body(self):
|
def definition_body(self):
|
||||||
(protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
|
(protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
|
||||||
@ -2837,7 +2838,8 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
|||||||
interfaceCache,
|
interfaceCache,
|
||||||
${properties},
|
${properties},
|
||||||
${chromeProperties},
|
${chromeProperties},
|
||||||
${name}, aDefineOnGlobal);
|
${name}, aDefineOnGlobal,
|
||||||
|
${unscopableNames});
|
||||||
""",
|
""",
|
||||||
protoClass=protoClass,
|
protoClass=protoClass,
|
||||||
parentProto=parentProto,
|
parentProto=parentProto,
|
||||||
@ -2849,7 +2851,8 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
|||||||
interfaceCache=interfaceCache,
|
interfaceCache=interfaceCache,
|
||||||
properties=properties,
|
properties=properties,
|
||||||
chromeProperties=chromeProperties,
|
chromeProperties=chromeProperties,
|
||||||
name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr")
|
name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
|
||||||
|
unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr")
|
||||||
|
|
||||||
# If we fail after here, we must clear interface and prototype caches
|
# If we fail after here, we must clear interface and prototype caches
|
||||||
# using this code: intermediate failure must not expose the interface in
|
# using this code: intermediate failure must not expose the interface in
|
||||||
@ -11762,6 +11765,7 @@ class CGDescriptor(CGThing):
|
|||||||
hasPromiseReturningMethod) = False, False, False, False, False, False
|
hasPromiseReturningMethod) = False, False, False, False, False, False
|
||||||
jsonifierMethod = None
|
jsonifierMethod = None
|
||||||
crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
|
crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
|
||||||
|
unscopableNames = list()
|
||||||
for n in descriptor.interface.namedConstructors:
|
for n in descriptor.interface.namedConstructors:
|
||||||
cgThings.append(CGClassConstructor(descriptor, n,
|
cgThings.append(CGClassConstructor(descriptor, n,
|
||||||
NamedConstructorName(n)))
|
NamedConstructorName(n)))
|
||||||
@ -11774,6 +11778,9 @@ class CGDescriptor(CGThing):
|
|||||||
props = memberProperties(m, descriptor)
|
props = memberProperties(m, descriptor)
|
||||||
|
|
||||||
if m.isMethod():
|
if m.isMethod():
|
||||||
|
if m.getExtendedAttribute("Unscopable"):
|
||||||
|
assert not m.isStatic()
|
||||||
|
unscopableNames.append(m.identifier.name)
|
||||||
if props.isJsonifier:
|
if props.isJsonifier:
|
||||||
jsonifierMethod = m
|
jsonifierMethod = m
|
||||||
elif not m.isIdentifierLess() or m == descriptor.operations['Stringifier']:
|
elif not m.isIdentifierLess() or m == descriptor.operations['Stringifier']:
|
||||||
@ -11799,6 +11806,9 @@ class CGDescriptor(CGThing):
|
|||||||
raise TypeError("Stringifier attributes not supported yet. "
|
raise TypeError("Stringifier attributes not supported yet. "
|
||||||
"See bug 824857.\n"
|
"See bug 824857.\n"
|
||||||
"%s" % m.location)
|
"%s" % m.location)
|
||||||
|
if m.getExtendedAttribute("Unscopable"):
|
||||||
|
assert not m.isStatic()
|
||||||
|
unscopableNames.append(m.identifier.name)
|
||||||
if m.isStatic():
|
if m.isStatic():
|
||||||
assert descriptor.interface.hasInterfaceObject()
|
assert descriptor.interface.hasInterfaceObject()
|
||||||
cgThings.append(CGStaticGetter(descriptor, m))
|
cgThings.append(CGStaticGetter(descriptor, m))
|
||||||
@ -11986,9 +11996,20 @@ class CGDescriptor(CGThing):
|
|||||||
cgThings.extend(CGClearCachedValueMethod(descriptor, m) for
|
cgThings.extend(CGClearCachedValueMethod(descriptor, m) for
|
||||||
m in clearableCachedAttrs(descriptor))
|
m in clearableCachedAttrs(descriptor))
|
||||||
|
|
||||||
|
haveUnscopables = (len(unscopableNames) != 0 and
|
||||||
|
descriptor.interface.hasInterfacePrototypeObject())
|
||||||
|
if haveUnscopables:
|
||||||
|
cgThings.append(
|
||||||
|
CGList([CGGeneric("static const char* const unscopableNames[] = {"),
|
||||||
|
CGIndenter(CGList([CGGeneric('"%s"' % name) for
|
||||||
|
name in unscopableNames] +
|
||||||
|
[CGGeneric("nullptr")], ",\n")),
|
||||||
|
CGGeneric("};\n")], "\n"))
|
||||||
|
|
||||||
# CGCreateInterfaceObjectsMethod needs to come after our
|
# CGCreateInterfaceObjectsMethod needs to come after our
|
||||||
# CGDOMJSClass, if any.
|
# CGDOMJSClass and unscopables, if any.
|
||||||
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
|
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties,
|
||||||
|
haveUnscopables))
|
||||||
|
|
||||||
# CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
|
# CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
|
||||||
# to come after CGCreateInterfaceObjectsMethod.
|
# to come after CGCreateInterfaceObjectsMethod.
|
||||||
|
@ -4011,6 +4011,14 @@ class IDLAttribute(IDLInterfaceMember):
|
|||||||
raise WebIDLError("[UseCounter] must not be used on a "
|
raise WebIDLError("[UseCounter] must not be used on a "
|
||||||
"stringifier attribute",
|
"stringifier attribute",
|
||||||
[attr.location, self.location])
|
[attr.location, self.location])
|
||||||
|
elif identifier == "Unscopable":
|
||||||
|
if not attr.noArguments():
|
||||||
|
raise WebIDLError("[Unscopable] must take no arguments",
|
||||||
|
[attr.location])
|
||||||
|
if self.isStatic():
|
||||||
|
raise WebIDLError("[Unscopable] is only allowed on non-static "
|
||||||
|
"attributes and operations",
|
||||||
|
[attr.location, self.location])
|
||||||
elif (identifier == "Pref" or
|
elif (identifier == "Pref" or
|
||||||
identifier == "Deprecated" or
|
identifier == "Deprecated" or
|
||||||
identifier == "SetterThrows" or
|
identifier == "SetterThrows" or
|
||||||
@ -4711,6 +4719,14 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
|
|||||||
raise WebIDLError("[UseCounter] must not be used on a special "
|
raise WebIDLError("[UseCounter] must not be used on a special "
|
||||||
"operation",
|
"operation",
|
||||||
[attr.location, self.location])
|
[attr.location, self.location])
|
||||||
|
elif identifier == "Unscopable":
|
||||||
|
if not attr.noArguments():
|
||||||
|
raise WebIDLError("[Unscopable] must take no arguments",
|
||||||
|
[attr.location])
|
||||||
|
if self.isStatic():
|
||||||
|
raise WebIDLError("[Unscopable] is only allowed on non-static "
|
||||||
|
"attributes and operations",
|
||||||
|
[attr.location, self.location])
|
||||||
elif (identifier == "Throws" or
|
elif (identifier == "Throws" or
|
||||||
identifier == "NewObject" or
|
identifier == "NewObject" or
|
||||||
identifier == "ChromeOnly" or
|
identifier == "ChromeOnly" or
|
||||||
|
@ -9,10 +9,13 @@
|
|||||||
|
|
||||||
[NoInterfaceObject]
|
[NoInterfaceObject]
|
||||||
interface ChildNode {
|
interface ChildNode {
|
||||||
// Not implemented yet:
|
[Throws, Unscopable]
|
||||||
// void before((Node or DOMString)... nodes);
|
void before((Node or DOMString)... nodes);
|
||||||
// void after((Node or DOMString)... nodes);
|
[Throws, Unscopable]
|
||||||
// void replace((Node or DOMString)... nodes);
|
void after((Node or DOMString)... nodes);
|
||||||
|
[Throws, Unscopable]
|
||||||
|
void replaceWith((Node or DOMString)... nodes);
|
||||||
|
[Unscopable]
|
||||||
void remove();
|
void remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ interface ParentNode {
|
|||||||
[Pure]
|
[Pure]
|
||||||
readonly attribute unsigned long childElementCount;
|
readonly attribute unsigned long childElementCount;
|
||||||
|
|
||||||
// Not implemented yet
|
[Throws, Unscopable]
|
||||||
// void prepend((Node or DOMString)... nodes);
|
void prepend((Node or DOMString)... nodes);
|
||||||
// void append((Node or DOMString)... nodes);
|
[Throws, Unscopable]
|
||||||
|
void append((Node or DOMString)... nodes);
|
||||||
};
|
};
|
||||||
|
37
js/src/jit-test/tests/debug/Environment-unscopables.js
Normal file
37
js/src/jit-test/tests/debug/Environment-unscopables.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables.
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
let g = newGlobal();
|
||||||
|
g.eval(`
|
||||||
|
let x = 'global';
|
||||||
|
function f() {
|
||||||
|
let obj = {
|
||||||
|
x: 'obj',
|
||||||
|
y: 'obj',
|
||||||
|
[Symbol.unscopables]: {x: 1},
|
||||||
|
};
|
||||||
|
with (obj)
|
||||||
|
debugger;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
let dbg = Debugger(g);
|
||||||
|
let hits = 0;
|
||||||
|
dbg.onDebuggerStatement = function (frame) {
|
||||||
|
let env = frame.environment;
|
||||||
|
|
||||||
|
assertEq(env.find("x") !== env, true);
|
||||||
|
assertEq(env.names().indexOf("x"), -1);
|
||||||
|
assertEq(env.getVariable("x"), undefined);
|
||||||
|
assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError);
|
||||||
|
|
||||||
|
assertEq(env.find("y") === env, true);
|
||||||
|
assertEq(env.getVariable("y"), "obj");
|
||||||
|
env.setVariable("y", 8);
|
||||||
|
|
||||||
|
assertEq(frame.eval("x").return, "global");
|
||||||
|
assertEq(frame.eval("y").return, 8);
|
||||||
|
hits++;
|
||||||
|
};
|
||||||
|
g.f();
|
||||||
|
assertEq(hits, 1);
|
@ -4556,17 +4556,25 @@ JS_PUBLIC_API(JSString*)
|
|||||||
GetSymbolDescription(HandleSymbol symbol);
|
GetSymbolDescription(HandleSymbol symbol);
|
||||||
|
|
||||||
/* Well-known symbols. */
|
/* Well-known symbols. */
|
||||||
|
#define JS_FOR_EACH_WELL_KNOWN_SYMBOL(macro) \
|
||||||
|
macro(iterator) \
|
||||||
|
macro(match) \
|
||||||
|
macro(species) \
|
||||||
|
macro(toPrimitive) \
|
||||||
|
macro(unscopables)
|
||||||
|
|
||||||
enum class SymbolCode : uint32_t {
|
enum class SymbolCode : uint32_t {
|
||||||
iterator, // well-known symbols
|
// There is one SymbolCode for each well-known symbol.
|
||||||
match,
|
#define JS_DEFINE_SYMBOL_ENUM(name) name,
|
||||||
species,
|
JS_FOR_EACH_WELL_KNOWN_SYMBOL(JS_DEFINE_SYMBOL_ENUM) // SymbolCode::iterator, etc.
|
||||||
toPrimitive,
|
#undef JS_DEFINE_SYMBOL_ENUM
|
||||||
|
Limit,
|
||||||
InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor()
|
InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor()
|
||||||
UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol()
|
UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol()
|
||||||
};
|
};
|
||||||
|
|
||||||
/* For use in loops that iterate over the well-known symbols. */
|
/* For use in loops that iterate over the well-known symbols. */
|
||||||
const size_t WellKnownSymbolLimit = 4;
|
const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the SymbolCode telling what sort of symbol `symbol` is.
|
* Return the SymbolCode telling what sort of symbol `symbol` is.
|
||||||
|
@ -3278,6 +3278,32 @@ CreateArrayPrototype(JSContext* cx, JSProtoKey key)
|
|||||||
return arrayProto;
|
return arrayProto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
|
||||||
|
{
|
||||||
|
// Add Array.prototype[@@unscopables]. ECMA-262 draft (2016 Mar 19) 22.1.3.32.
|
||||||
|
RootedObject unscopables(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr, TenuredObject));
|
||||||
|
if (!unscopables)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
RootedValue value(cx, BooleanValue(true));
|
||||||
|
if (!DefineProperty(cx, unscopables, cx->names().copyWithin, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().entries, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().fill, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().find, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().findIndex, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().includes, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().keys, value) ||
|
||||||
|
!DefineProperty(cx, unscopables, cx->names().values, value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().get(JS::SymbolCode::unscopables)));
|
||||||
|
value.setObject(*unscopables);
|
||||||
|
return DefineProperty(cx, proto, id, value, nullptr, nullptr, JSPROP_READONLY);
|
||||||
|
}
|
||||||
|
|
||||||
const Class ArrayObject::class_ = {
|
const Class ArrayObject::class_ = {
|
||||||
"Array",
|
"Array",
|
||||||
JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_DELAY_METADATA_CALLBACK,
|
JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_DELAY_METADATA_CALLBACK,
|
||||||
@ -3298,7 +3324,9 @@ const Class ArrayObject::class_ = {
|
|||||||
CreateArrayPrototype,
|
CreateArrayPrototype,
|
||||||
array_static_methods,
|
array_static_methods,
|
||||||
nullptr,
|
nullptr,
|
||||||
array_methods
|
array_methods,
|
||||||
|
nullptr,
|
||||||
|
array_proto_finish
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,9 +38,6 @@ IdIsIndex(jsid id, uint32_t* indexp)
|
|||||||
return js::StringIsArrayIndex(atom, indexp);
|
return js::StringIsArrayIndex(atom, indexp);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern JSObject*
|
|
||||||
InitArrayClass(JSContext* cx, js::HandleObject obj);
|
|
||||||
|
|
||||||
// The methods below only create dense boxed arrays.
|
// The methods below only create dense boxed arrays.
|
||||||
|
|
||||||
/* Create a dense array with no capacity allocated, length set to 0. */
|
/* Create a dense array with no capacity allocated, length set to 0. */
|
||||||
|
51
js/src/tests/ecma_6/Array/unscopables.js
Normal file
51
js/src/tests/ecma_6/Array/unscopables.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
let Array_unscopables = Array.prototype[Symbol.unscopables];
|
||||||
|
|
||||||
|
let desc = Reflect.getOwnPropertyDescriptor(Array.prototype, Symbol.unscopables);
|
||||||
|
assertDeepEq(desc, {
|
||||||
|
value: Array_unscopables,
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEq(Reflect.getPrototypeOf(Array_unscopables), null);
|
||||||
|
|
||||||
|
let desc2 = Object.getOwnPropertyDescriptor(Array_unscopables, "values");
|
||||||
|
assertDeepEq(desc2, {
|
||||||
|
value: true,
|
||||||
|
writable: true,
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let keys = Reflect.ownKeys(Array_unscopables);
|
||||||
|
print(uneval(keys));
|
||||||
|
assertDeepEq(keys, [
|
||||||
|
"copyWithin",
|
||||||
|
"entries",
|
||||||
|
"fill",
|
||||||
|
"find",
|
||||||
|
"findIndex",
|
||||||
|
"includes",
|
||||||
|
"keys",
|
||||||
|
"values"
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let key of keys)
|
||||||
|
assertEq(Array_unscopables[key], true);
|
||||||
|
|
||||||
|
// Test that it actually works
|
||||||
|
assertThrowsInstanceOf(() => {
|
||||||
|
with ([]) {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}, ReferenceError);
|
||||||
|
|
||||||
|
{
|
||||||
|
let fill = 33;
|
||||||
|
with (Array.prototype) {
|
||||||
|
assertEq(fill, 33);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
22
js/src/tests/ecma_6/LexicalEnvironment/unscopables-basics.js
Normal file
22
js/src/tests/ecma_6/LexicalEnvironment/unscopables-basics.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Basics of @@unscopables support.
|
||||||
|
|
||||||
|
// In with(obj), if obj[@@unscopables][id] is truthy, then the identifier id
|
||||||
|
// is not present as a binding in the with-block's scope.
|
||||||
|
var x = "global";
|
||||||
|
with ({x: "with", [Symbol.unscopables]: {x: true}})
|
||||||
|
assertEq(x, "global");
|
||||||
|
|
||||||
|
// But if obj[@@unscopables][id] is false or not present, there is a binding.
|
||||||
|
with ({y: "with", z: "with", [Symbol.unscopables]: {y: false}}) {
|
||||||
|
assertEq(y, "with");
|
||||||
|
assertEq(z, "with");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBoolean(obj[@@unscopables][id]) determines whether there's a binding.
|
||||||
|
let someValues = [0, -0, NaN, "", undefined, null, "x", {}, []];
|
||||||
|
for (let v of someValues) {
|
||||||
|
with ({x: "with", [Symbol.unscopables]: {x: v}})
|
||||||
|
assertEq(x, v ? "global" : "with");
|
||||||
|
}
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,23 @@
|
|||||||
|
// @@unscopables continues to work after exiting the relevant `with` block,
|
||||||
|
// if the environment is captured by a closure.
|
||||||
|
|
||||||
|
let env = {
|
||||||
|
x: 9000,
|
||||||
|
[Symbol.unscopables]: {x: true}
|
||||||
|
};
|
||||||
|
|
||||||
|
function make_adder(x) {
|
||||||
|
with (env)
|
||||||
|
return function (y) { return x + y; };
|
||||||
|
}
|
||||||
|
assertEq(make_adder(3)(10), 13);
|
||||||
|
|
||||||
|
// Same test, but with a bunch of different parts for bad luck
|
||||||
|
let x = 500;
|
||||||
|
function make_adder_with_eval() {
|
||||||
|
with (env)
|
||||||
|
return eval('y => eval("x + y")');
|
||||||
|
}
|
||||||
|
assertEq(make_adder_with_eval()(10), 510);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,8 @@
|
|||||||
|
// @@unscopables prevents a property from having any effect on assigning to a
|
||||||
|
// const binding (which is an error).
|
||||||
|
|
||||||
|
const x = 1;
|
||||||
|
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||||
|
assertThrowsInstanceOf(() => {x = 2;}, TypeError);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
27
js/src/tests/ecma_6/LexicalEnvironment/unscopables-delete.js
Normal file
27
js/src/tests/ecma_6/LexicalEnvironment/unscopables-delete.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// If obj[@@unscopables][id], then `delete id` works across `with (obj)` scope.
|
||||||
|
|
||||||
|
this.niche = 7;
|
||||||
|
let obj = { niche: 8, [Symbol.unscopables]: { niche: true } };
|
||||||
|
with (obj) {
|
||||||
|
delete niche;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(obj.niche, 8);
|
||||||
|
assertEq("niche" in this, false);
|
||||||
|
|
||||||
|
// Same thing, but delete a variable introduced by sloppy direct eval.
|
||||||
|
this.niche = 9;
|
||||||
|
function f() {
|
||||||
|
eval("var niche = 10;");
|
||||||
|
with (obj) {
|
||||||
|
assertEq(niche, 10);
|
||||||
|
delete niche;
|
||||||
|
}
|
||||||
|
assertEq(niche, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of course none of this affects a qualified delete.
|
||||||
|
assertEq(delete this.niche, true);
|
||||||
|
assertEq("niche" in this, false);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,41 @@
|
|||||||
|
// @@unscopables checks can call getters.
|
||||||
|
|
||||||
|
// The @@unscopables property itself can be a getter.
|
||||||
|
let hit1 = 0;
|
||||||
|
let x = "global x";
|
||||||
|
let env1 = {
|
||||||
|
x: "env1.x",
|
||||||
|
get [Symbol.unscopables]() {
|
||||||
|
hit1++;
|
||||||
|
return {x: true};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
with (env1)
|
||||||
|
assertEq(x, "global x");
|
||||||
|
assertEq(hit1, 1);
|
||||||
|
|
||||||
|
// It can throw; the exception is propagated out.
|
||||||
|
function Fit() {}
|
||||||
|
with ({x: 0, get [Symbol.unscopables]() { throw new Fit; }})
|
||||||
|
assertThrowsInstanceOf(() => x, Fit);
|
||||||
|
|
||||||
|
// Individual properties on the @@unscopables object can have getters.
|
||||||
|
let hit2 = 0;
|
||||||
|
let env2 = {
|
||||||
|
x: "env2.x",
|
||||||
|
[Symbol.unscopables]: {
|
||||||
|
get x() {
|
||||||
|
hit2++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
with (env2)
|
||||||
|
assertEq(x, "global x");
|
||||||
|
assertEq(hit2, 1);
|
||||||
|
|
||||||
|
// And they can throw.
|
||||||
|
with ({x: 0, [Symbol.unscopables]: {get x() { throw new Fit; }}})
|
||||||
|
assertThrowsInstanceOf(() => x, Fit);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
18
js/src/tests/ecma_6/LexicalEnvironment/unscopables-global.js
Normal file
18
js/src/tests/ecma_6/LexicalEnvironment/unscopables-global.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// @@unscopables does not affect the global environment.
|
||||||
|
|
||||||
|
this.x = "global property x";
|
||||||
|
let y = "global lexical y";
|
||||||
|
this[Symbol.unscopables] = {x: true, y: true};
|
||||||
|
assertEq(x, "global property x");
|
||||||
|
assertEq(y, "global lexical y");
|
||||||
|
assertEq(eval("x"), "global property x");
|
||||||
|
assertEq(eval("y"), "global lexical y");
|
||||||
|
|
||||||
|
// But it does affect `with` statements targeting the global object.
|
||||||
|
{
|
||||||
|
let x = "local x";
|
||||||
|
with (this)
|
||||||
|
assertEq(x, "local x");
|
||||||
|
}
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,22 @@
|
|||||||
|
// In these cases, @@unscopables should not be consulted.
|
||||||
|
|
||||||
|
// Because obj has no properties `assertEq` or `x`,
|
||||||
|
// obj[@@unscopables] is not checked here:
|
||||||
|
var obj = {
|
||||||
|
get [Symbol.unscopables]() {
|
||||||
|
throw "tried to read @@unscopables";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var x = 3;
|
||||||
|
with (obj)
|
||||||
|
assertEq(x, 3);
|
||||||
|
|
||||||
|
// If @@unscopables is present but not an object, it is ignored:
|
||||||
|
for (let nonObject of [undefined, null, "nothing", Symbol.for("moon")]) {
|
||||||
|
let y = 4;
|
||||||
|
let obj2 = {[Symbol.unscopables]: nonObject, y: 5};
|
||||||
|
with (obj2)
|
||||||
|
assertEq(y, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,7 @@
|
|||||||
|
// Trying to access a binding that doesn't exist due to @@unscopables
|
||||||
|
// is a ReferenceError.
|
||||||
|
|
||||||
|
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||||
|
assertThrowsInstanceOf(() => x, ReferenceError);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,18 @@
|
|||||||
|
// When env[@@unscopables].x changes, bindings can appear even if env is inextensible.
|
||||||
|
|
||||||
|
let x = "global";
|
||||||
|
let unscopables = {x: true};
|
||||||
|
let env = Object.create(null);
|
||||||
|
env[Symbol.unscopables] = unscopables;
|
||||||
|
env.x = "object";
|
||||||
|
Object.freeze(env);
|
||||||
|
|
||||||
|
for (let i = 0; i < 1004; i++) {
|
||||||
|
if (i === 1000)
|
||||||
|
unscopables.x = false;
|
||||||
|
with (env) {
|
||||||
|
assertEq(x, i < 1000 ? "global" : "object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,44 @@
|
|||||||
|
// When obj[@@unscopables].x changes, bindings appear and disappear accordingly.
|
||||||
|
|
||||||
|
let x = "global";
|
||||||
|
function getX() { return x; }
|
||||||
|
|
||||||
|
let unscopables = {x: true};
|
||||||
|
let obj = {x: "obj", [Symbol.unscopables]: unscopables};
|
||||||
|
|
||||||
|
with (obj) {
|
||||||
|
assertEq(x, "global");
|
||||||
|
x = "global-1";
|
||||||
|
assertEq(x, "global-1");
|
||||||
|
assertEq(obj.x, "obj");
|
||||||
|
|
||||||
|
unscopables.x = false; // suddenly x appears in the with-environment
|
||||||
|
|
||||||
|
assertEq(x, "obj");
|
||||||
|
x = "obj-1";
|
||||||
|
assertEq(getX(), "global-1"); // unchanged
|
||||||
|
assertEq(obj.x, "obj-1");
|
||||||
|
|
||||||
|
unscopables.x = true; // *poof*
|
||||||
|
|
||||||
|
assertEq(x, "global-1");
|
||||||
|
x = "global-2";
|
||||||
|
assertEq(getX(), "global-2");
|
||||||
|
assertEq(obj.x, "obj-1"); // unchanged
|
||||||
|
|
||||||
|
// The determination of which binding is assigned happens when the LHS of
|
||||||
|
// assignment is evaluated, before the RHS. This is observable if we make
|
||||||
|
// the binding appear or disappear during evaluation of the RHS, before
|
||||||
|
// assigning.
|
||||||
|
x = (unscopables.x = false, "global-3");
|
||||||
|
assertEq(getX(), "global-3");
|
||||||
|
assertEq(obj.x, "obj-1");
|
||||||
|
|
||||||
|
x = (unscopables.x = true, "obj-2");
|
||||||
|
assertEq(getX(), "global-3");
|
||||||
|
assertEq(obj.x, "obj-2");
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(x, "global-3");
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
39
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proto.js
Normal file
39
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proto.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// @@unscopables treats properties found on prototype chains the same as other
|
||||||
|
// properties.
|
||||||
|
|
||||||
|
const x = "global x";
|
||||||
|
const y = "global y";
|
||||||
|
|
||||||
|
// obj[@@unscopables].x works when obj.x is inherited via the prototype chain.
|
||||||
|
let proto = {x: "object x", y: "object y"};
|
||||||
|
let env = Object.create(proto);
|
||||||
|
env[Symbol.unscopables] = {x: true, y: false};
|
||||||
|
with (env) {
|
||||||
|
assertEq(x, "global x");
|
||||||
|
assertEq(delete x, false);
|
||||||
|
assertEq(y, "object y");
|
||||||
|
}
|
||||||
|
assertEq(env.x, "object x");
|
||||||
|
|
||||||
|
// @@unscopables works if is inherited via the prototype chain.
|
||||||
|
env = {
|
||||||
|
x: "object",
|
||||||
|
[Symbol.unscopables]: {x: true, y: true}
|
||||||
|
};
|
||||||
|
for (let i = 0; i < 50; i++)
|
||||||
|
env = Object.create(env);
|
||||||
|
env.y = 1;
|
||||||
|
with (env) {
|
||||||
|
assertEq(x, "global x");
|
||||||
|
assertEq(y, "global y");
|
||||||
|
}
|
||||||
|
|
||||||
|
// @@unscopables works if the obj[@@unscopables][id] property is inherited.
|
||||||
|
env = {
|
||||||
|
x: "object",
|
||||||
|
[Symbol.unscopables]: Object.create({x: true})
|
||||||
|
};
|
||||||
|
with (env)
|
||||||
|
assertEq(x, "global x");
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
46
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proxy.js
Normal file
46
js/src/tests/ecma_6/LexicalEnvironment/unscopables-proxy.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Object operations are performed in the right order, as observed by proxies.
|
||||||
|
|
||||||
|
let log = [];
|
||||||
|
function LoggingProxyHandlerWrapper(name, handler={}) {
|
||||||
|
return new Proxy(handler, {
|
||||||
|
get(t, id) {
|
||||||
|
let method = handler[id];
|
||||||
|
return function (...args) {
|
||||||
|
log.push([name + "." + id, ...args.filter(v => typeof v !== "object")]);
|
||||||
|
if (method === undefined)
|
||||||
|
return Reflect[id].apply(null, args);
|
||||||
|
return method.apply(this, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoggingProxy(name, target) {
|
||||||
|
return new Proxy(target, new LoggingProxyHandlerWrapper(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
let proto = {x: 44};
|
||||||
|
let proto_proxy = new LoggingProxy("proto", proto);
|
||||||
|
let unscopables = {x: true};
|
||||||
|
let unscopables_proxy = new LoggingProxy("unscopables", {x: true});
|
||||||
|
let env = Object.create(proto_proxy, {
|
||||||
|
[Symbol.unscopables]: { value: unscopables_proxy }
|
||||||
|
});
|
||||||
|
let env_proxy = new LoggingProxy("env", env);
|
||||||
|
|
||||||
|
let x = 11;
|
||||||
|
function f() {
|
||||||
|
with (env_proxy)
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEq(f(), 11);
|
||||||
|
|
||||||
|
assertDeepEq(log, [
|
||||||
|
["env.has", "x"],
|
||||||
|
["proto.has", "x"],
|
||||||
|
["env.get", Symbol.unscopables],
|
||||||
|
["unscopables.get", "x"]
|
||||||
|
]);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
32
js/src/tests/ecma_6/LexicalEnvironment/unscopables-strict.js
Normal file
32
js/src/tests/ecma_6/LexicalEnvironment/unscopables-strict.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Strict assignment to the name of a property that's masked by @@unscopables
|
||||||
|
// throws a ReferenceError.
|
||||||
|
|
||||||
|
let env = {k: 1};
|
||||||
|
let f;
|
||||||
|
with (env) {
|
||||||
|
f = function () {
|
||||||
|
"use strict";
|
||||||
|
k = 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
||||||
|
assertEq(env.k, 2);
|
||||||
|
|
||||||
|
env[Symbol.unscopables] = {k: true};
|
||||||
|
assertThrowsInstanceOf(f, ReferenceError);
|
||||||
|
|
||||||
|
// @@unscopables is tested when the LHS of assignment is evaluated, so there is
|
||||||
|
// no effect on the assignment if it is changed while evaluating the RHS.
|
||||||
|
let g;
|
||||||
|
with (env) {
|
||||||
|
g = function () {
|
||||||
|
"use strict";
|
||||||
|
k = (env[Symbol.unscopables].k = true, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env[Symbol.unscopables].k = false;
|
||||||
|
g();
|
||||||
|
assertEq(env.k, 3);
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
@ -0,0 +1,9 @@
|
|||||||
|
// Accessing an uninitialized variable due to @@unscopables is still a ReferenceError.
|
||||||
|
|
||||||
|
with ({x: 1, [Symbol.unscopables]: {x: true}})
|
||||||
|
assertThrowsInstanceOf(() => x, ReferenceError);
|
||||||
|
|
||||||
|
let x;
|
||||||
|
|
||||||
|
reportCompare(0, 0);
|
||||||
|
|
@ -49,6 +49,7 @@
|
|||||||
macro(construct, construct, "construct") \
|
macro(construct, construct, "construct") \
|
||||||
macro(constructor, constructor, "constructor") \
|
macro(constructor, constructor, "constructor") \
|
||||||
macro(ConvertAndCopyTo, ConvertAndCopyTo, "ConvertAndCopyTo") \
|
macro(ConvertAndCopyTo, ConvertAndCopyTo, "ConvertAndCopyTo") \
|
||||||
|
macro(copyWithin, copyWithin, "copyWithin") \
|
||||||
macro(count, count, "count") \
|
macro(count, count, "count") \
|
||||||
macro(currency, currency, "currency") \
|
macro(currency, currency, "currency") \
|
||||||
macro(currencyDisplay, currencyDisplay, "currencyDisplay") \
|
macro(currencyDisplay, currencyDisplay, "currencyDisplay") \
|
||||||
@ -73,6 +74,7 @@
|
|||||||
macro(encodeURI, encodeURI, "encodeURI") \
|
macro(encodeURI, encodeURI, "encodeURI") \
|
||||||
macro(encodeURIComponent, encodeURIComponent, "encodeURIComponent") \
|
macro(encodeURIComponent, encodeURIComponent, "encodeURIComponent") \
|
||||||
macro(endTimestamp, endTimestamp, "endTimestamp") \
|
macro(endTimestamp, endTimestamp, "endTimestamp") \
|
||||||
|
macro(entries, entries, "entries") \
|
||||||
macro(enumerable, enumerable, "enumerable") \
|
macro(enumerable, enumerable, "enumerable") \
|
||||||
macro(enumerate, enumerate, "enumerate") \
|
macro(enumerate, enumerate, "enumerate") \
|
||||||
macro(escape, escape, "escape") \
|
macro(escape, escape, "escape") \
|
||||||
@ -81,6 +83,9 @@
|
|||||||
macro(fieldOffsets, fieldOffsets, "fieldOffsets") \
|
macro(fieldOffsets, fieldOffsets, "fieldOffsets") \
|
||||||
macro(fieldTypes, fieldTypes, "fieldTypes") \
|
macro(fieldTypes, fieldTypes, "fieldTypes") \
|
||||||
macro(fileName, fileName, "fileName") \
|
macro(fileName, fileName, "fileName") \
|
||||||
|
macro(fill, fill, "fill") \
|
||||||
|
macro(find, find, "find") \
|
||||||
|
macro(findIndex, findIndex, "findIndex") \
|
||||||
macro(fix, fix, "fix") \
|
macro(fix, fix, "fix") \
|
||||||
macro(flags, flags, "flags") \
|
macro(flags, flags, "flags") \
|
||||||
macro(float32, float32, "float32") \
|
macro(float32, float32, "float32") \
|
||||||
@ -106,6 +111,7 @@
|
|||||||
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
|
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
|
||||||
macro(ignoreCase, ignoreCase, "ignoreCase") \
|
macro(ignoreCase, ignoreCase, "ignoreCase") \
|
||||||
macro(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \
|
macro(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \
|
||||||
|
macro(includes, includes, "includes") \
|
||||||
macro(index, index, "index") \
|
macro(index, index, "index") \
|
||||||
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
|
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
|
||||||
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
|
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
|
||||||
@ -270,6 +276,7 @@
|
|||||||
macro(match, match, "match") \
|
macro(match, match, "match") \
|
||||||
macro(species, species, "species") \
|
macro(species, species, "species") \
|
||||||
macro(toPrimitive, toPrimitive, "toPrimitive") \
|
macro(toPrimitive, toPrimitive, "toPrimitive") \
|
||||||
|
macro(unscopables, unscopables, "unscopables") \
|
||||||
/* Same goes for the descriptions of the well-known symbols. */ \
|
/* Same goes for the descriptions of the well-known symbols. */ \
|
||||||
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
|
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
|
||||||
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
|
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
|
||||||
@ -277,7 +284,6 @@
|
|||||||
macro(Symbol_match, Symbol_match, "Symbol.match") \
|
macro(Symbol_match, Symbol_match, "Symbol.match") \
|
||||||
macro(Symbol_species, Symbol_species, "Symbol.species") \
|
macro(Symbol_species, Symbol_species, "Symbol.species") \
|
||||||
macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
|
macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
|
||||||
macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
|
|
||||||
macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
|
macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
|
||||||
/* Function names for properties named by symbols. */ \
|
/* Function names for properties named by symbols. */ \
|
||||||
macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \
|
macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \
|
||||||
|
@ -8174,6 +8174,14 @@ DebuggerEnv_getVariable(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
/* This can trigger getters. */
|
/* This can trigger getters. */
|
||||||
ErrorCopier ec(ac);
|
ErrorCopier ec(ac);
|
||||||
|
|
||||||
|
bool found;
|
||||||
|
if (!HasProperty(cx, env, id, &found))
|
||||||
|
return false;
|
||||||
|
if (!found) {
|
||||||
|
args.rval().setUndefined();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// For DebugScopeObjects, we get sentinel values for optimized out
|
// For DebugScopeObjects, we get sentinel values for optimized out
|
||||||
// slots and arguments instead of throwing (the default behavior).
|
// slots and arguments instead of throwing (the default behavior).
|
||||||
//
|
//
|
||||||
|
@ -443,10 +443,9 @@ namespace js {
|
|||||||
*/
|
*/
|
||||||
struct WellKnownSymbols
|
struct WellKnownSymbols
|
||||||
{
|
{
|
||||||
js::ImmutableSymbolPtr iterator;
|
#define DECLARE_SYMBOL(name) js::ImmutableSymbolPtr name;
|
||||||
js::ImmutableSymbolPtr match;
|
JS_FOR_EACH_WELL_KNOWN_SYMBOL(DECLARE_SYMBOL)
|
||||||
js::ImmutableSymbolPtr species;
|
#undef DECLARE_SYMBOL
|
||||||
js::ImmutableSymbolPtr toPrimitive;
|
|
||||||
|
|
||||||
const ImmutableSymbolPtr& get(size_t u) const {
|
const ImmutableSymbolPtr& get(size_t u) const {
|
||||||
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
|
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
|
||||||
|
@ -652,6 +652,26 @@ DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclo
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
|
||||||
|
static bool
|
||||||
|
CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
|
||||||
|
{
|
||||||
|
RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
|
||||||
|
.get(JS::SymbolCode::unscopables)));
|
||||||
|
RootedValue v(cx);
|
||||||
|
if (!GetProperty(cx, obj, obj, unscopablesId, &v))
|
||||||
|
return false;
|
||||||
|
if (v.isObject()) {
|
||||||
|
RootedObject unscopablesObj(cx, &v.toObject());
|
||||||
|
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
|
||||||
|
return false;
|
||||||
|
*scopable = !ToBoolean(v);
|
||||||
|
} else {
|
||||||
|
*scopable = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||||
MutableHandleObject objp, MutableHandleShape propp)
|
MutableHandleObject objp, MutableHandleShape propp)
|
||||||
@ -662,7 +682,19 @@ with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
||||||
return LookupProperty(cx, actual, id, objp, propp);
|
if (!LookupProperty(cx, actual, id, objp, propp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (propp) {
|
||||||
|
bool scopable;
|
||||||
|
if (!CheckUnscopables(cx, actual, id, &scopable))
|
||||||
|
return false;
|
||||||
|
if (!scopable) {
|
||||||
|
objp.set(nullptr);
|
||||||
|
propp.set(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -679,7 +711,15 @@ with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
|
|||||||
{
|
{
|
||||||
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
|
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
|
||||||
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
|
||||||
return HasProperty(cx, actual, id, foundp);
|
|
||||||
|
// ES 8.1.1.2.1 step 3-5.
|
||||||
|
if (!HasProperty(cx, actual, id, foundp))
|
||||||
|
return false;
|
||||||
|
if (!*foundp)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Steps 7-10. (Step 6 is a no-op.)
|
||||||
|
return CheckUnscopables(cx, actual, id, foundp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -2173,12 +2213,25 @@ class DebugScopeProxy : public BaseProxyHandler
|
|||||||
// target object, the object would indicate that native enumeration is
|
// target object, the object would indicate that native enumeration is
|
||||||
// the thing to do, but native enumeration over the DynamicWithObject
|
// the thing to do, but native enumeration over the DynamicWithObject
|
||||||
// wrapper yields no properties. So instead here we hack around the
|
// wrapper yields no properties. So instead here we hack around the
|
||||||
// issue, and punch a hole through to the with object target.
|
// issue: punch a hole through to the with object target, then manually
|
||||||
Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>()
|
// examine @@unscopables.
|
||||||
? &scope->as<DynamicWithObject>().object() : scope));
|
bool isWith = scope->is<DynamicWithObject>();
|
||||||
|
Rooted<JSObject*> target(cx, (isWith ? &scope->as<DynamicWithObject>().object() : scope));
|
||||||
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
|
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (isWith) {
|
||||||
|
size_t j = 0;
|
||||||
|
for (size_t i = 0; i < props.length(); i++) {
|
||||||
|
bool inScope;
|
||||||
|
if (!CheckUnscopables(cx, scope, props[i], &inScope))
|
||||||
|
return false;
|
||||||
|
if (inScope)
|
||||||
|
props[j++].set(props[i]);
|
||||||
|
}
|
||||||
|
props.resize(j);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function scopes are optimized to not contain unaliased variables so
|
* Function scopes are optimized to not contain unaliased variables so
|
||||||
* they must be manually appended here.
|
* they must be manually appended here.
|
||||||
|
Loading…
Reference in New Issue
Block a user