Bug 1920009 - Fix fragment titles being read by talkback after dismissal from menu...
[gecko.git] / js / src / jsapi-tests / testResolveRecursion.cpp
blob6b73c371f4330c500156d1595d1c44e658c32fbc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "js/Object.h" // JS::GetReservedSlot, JS::SetReservedSlot
9 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById
10 #include "jsapi-tests/tests.h"
13 * Test that resolve hook recursion for the same object and property is
14 * prevented.
16 BEGIN_TEST(testResolveRecursion) {
17 static const JSClassOps my_resolve_classOps = {
18 nullptr, // addProperty
19 nullptr, // delProperty
20 nullptr, // enumerate
21 nullptr, // newEnumerate
22 my_resolve, // resolve
23 nullptr, // mayResolve
24 nullptr, // finalize
25 nullptr, // call
26 nullptr, // construct
27 nullptr, // trace
30 static const JSClass my_resolve_class = {
31 "MyResolve",
32 JSCLASS_HAS_RESERVED_SLOTS(SlotCount),
33 &my_resolve_classOps,
36 obj1.init(cx, JS_NewObject(cx, &my_resolve_class));
37 CHECK(obj1);
38 obj2.init(cx, JS_NewObject(cx, &my_resolve_class));
39 CHECK(obj2);
40 JS::SetReservedSlot(obj1, TestSlot, JS::PrivateValue(this));
41 JS::SetReservedSlot(obj2, TestSlot, JS::PrivateValue(this));
43 JS::RootedValue obj1Val(cx, JS::ObjectValue(*obj1));
44 JS::RootedValue obj2Val(cx, JS::ObjectValue(*obj2));
45 CHECK(JS_DefineProperty(cx, global, "obj1", obj1Val, 0));
46 CHECK(JS_DefineProperty(cx, global, "obj2", obj2Val, 0));
48 resolveEntryCount = 0;
49 resolveExitCount = 0;
51 /* Start the essence of the test via invoking the first resolve hook. */
52 JS::RootedValue v(cx);
53 EVAL("obj1.x", &v);
54 CHECK(v.isFalse());
55 CHECK_EQUAL(resolveEntryCount, 4);
56 CHECK_EQUAL(resolveExitCount, 4);
58 obj1 = nullptr;
59 obj2 = nullptr;
60 return true;
63 enum Slots { TestSlot, SlotCount };
65 JS::PersistentRootedObject obj1;
66 JS::PersistentRootedObject obj2;
67 int resolveEntryCount;
68 int resolveExitCount;
70 struct AutoIncrCounters {
71 explicit AutoIncrCounters(cls_testResolveRecursion* t) : t(t) {
72 t->resolveEntryCount++;
75 ~AutoIncrCounters() { t->resolveExitCount++; }
77 cls_testResolveRecursion* t;
80 bool doResolve(JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
81 CHECK_EQUAL(resolveExitCount, 0);
82 AutoIncrCounters incr(this);
83 CHECK(obj == obj1 || obj == obj2);
85 CHECK(id.isString());
87 JSLinearString* str = JS_EnsureLinearString(cx, id.toString());
88 CHECK(str);
89 JS::RootedValue v(cx);
90 if (JS_LinearStringEqualsLiteral(str, "x")) {
91 if (obj == obj1) {
92 /* First resolve hook invocation. */
93 CHECK_EQUAL(resolveEntryCount, 1);
94 EVAL("obj2.y = true", &v);
95 CHECK(v.isTrue());
96 CHECK(JS_DefinePropertyById(cx, obj, id, JS::FalseHandleValue,
97 JSPROP_RESOLVING));
98 *resolvedp = true;
99 return true;
101 if (obj == obj2) {
102 CHECK_EQUAL(resolveEntryCount, 4);
103 *resolvedp = false;
104 return true;
106 } else if (JS_LinearStringEqualsLiteral(str, "y")) {
107 if (obj == obj2) {
108 CHECK_EQUAL(resolveEntryCount, 2);
109 CHECK(JS_DefinePropertyById(cx, obj, id, JS::NullHandleValue,
110 JSPROP_RESOLVING));
111 EVAL("obj1.x", &v);
112 CHECK(v.isUndefined());
113 EVAL("obj1.y", &v);
114 CHECK(v.isInt32(0));
115 *resolvedp = true;
116 return true;
118 if (obj == obj1) {
119 CHECK_EQUAL(resolveEntryCount, 3);
120 EVAL("obj1.x", &v);
121 CHECK(v.isUndefined());
122 EVAL("obj1.y", &v);
123 CHECK(v.isUndefined());
124 EVAL("obj2.y", &v);
125 CHECK(v.isNull());
126 EVAL("obj2.x", &v);
127 CHECK(v.isUndefined());
128 EVAL("obj1.y = 0", &v);
129 CHECK(v.isInt32(0));
130 *resolvedp = true;
131 return true;
134 CHECK(false);
135 return false;
138 static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
139 bool* resolvedp) {
140 void* p = JS::GetReservedSlot(obj, TestSlot).toPrivate();
141 return static_cast<cls_testResolveRecursion*>(p)->doResolve(obj, id,
142 resolvedp);
144 END_TEST(testResolveRecursion)
147 * Test that JS_InitStandardClasses does not cause resolve hooks to be called.
149 * (XPConnect apparently does have global classes, such as the one created by
150 * nsMessageManagerScriptExecutor::InitChildGlobalInternal(), that have resolve
151 * hooks which can call back into JS, and on which JS_InitStandardClasses is
152 * called. Calling back into JS in the middle of resolving `undefined` is bad.)
154 BEGIN_TEST(testResolveRecursion_InitStandardClasses) {
155 CHECK(JS::InitRealmStandardClasses(cx));
156 return true;
159 const JSClass* getGlobalClass() override {
160 static const JSClassOps myGlobalClassOps = {
161 nullptr, // addProperty
162 nullptr, // delProperty
163 nullptr, // enumerate
164 nullptr, // newEnumerate
165 my_resolve, // resolve
166 nullptr, // mayResolve
167 nullptr, // finalize
168 nullptr, // call
169 nullptr, // construct
170 JS_GlobalObjectTraceHook, // trace
173 static const JSClass myGlobalClass = {
174 "testResolveRecursion_InitStandardClasses_myGlobalClass",
175 JSCLASS_GLOBAL_FLAGS,
176 &myGlobalClassOps,
179 return &myGlobalClass;
182 static bool my_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
183 bool* resolvedp) {
184 MOZ_ASSERT_UNREACHABLE(
185 "resolve hook should not be called from InitStandardClasses");
186 JS_ReportErrorASCII(cx, "FAIL");
187 return false;
189 END_TEST(testResolveRecursion_InitStandardClasses)