mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 1 | // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 | // Redistribution and use in source and binary forms, with or without |
| 3 | // modification, are permitted provided that the following conditions are |
| 4 | // met: |
| 5 | // |
| 6 | // * Redistributions of source code must retain the above copyright |
| 7 | // notice, this list of conditions and the following disclaimer. |
| 8 | // * Redistributions in binary form must reproduce the above |
| 9 | // copyright notice, this list of conditions and the following |
| 10 | // disclaimer in the documentation and/or other materials provided |
| 11 | // with the distribution. |
| 12 | // * Neither the name of Google Inc. nor the names of its |
| 13 | // contributors may be used to endorse or promote products derived |
| 14 | // from this software without specific prior written permission. |
| 15 | // |
| 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | |
| 28 | #include "v8.h" |
| 29 | |
| 30 | #include "cctest.h" |
| 31 | |
| 32 | using namespace v8; |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 33 | namespace i = v8::internal; |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 34 | |
| 35 | namespace { |
| 36 | // Need to create a new isolate when FLAG_harmony_observation is on. |
| 37 | class HarmonyIsolate { |
| 38 | public: |
| 39 | HarmonyIsolate() { |
| 40 | i::FLAG_harmony_observation = true; |
| 41 | isolate_ = Isolate::New(); |
| 42 | isolate_->Enter(); |
| 43 | } |
| 44 | |
| 45 | ~HarmonyIsolate() { |
| 46 | isolate_->Exit(); |
| 47 | isolate_->Dispose(); |
| 48 | } |
| 49 | |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 50 | Isolate* GetIsolate() const { return isolate_; } |
| 51 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 52 | private: |
| 53 | Isolate* isolate_; |
| 54 | }; |
| 55 | } |
| 56 | |
| 57 | TEST(PerIsolateState) { |
| 58 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 59 | HandleScope scope(isolate.GetIsolate()); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 60 | LocalContext context1; |
| 61 | CompileRun( |
| 62 | "var count = 0;" |
| 63 | "var calls = 0;" |
| 64 | "var observer = function(records) { count = records.length; calls++ };" |
| 65 | "var obj = {};" |
| 66 | "Object.observe(obj, observer);"); |
| 67 | Handle<Value> observer = CompileRun("observer"); |
| 68 | Handle<Value> obj = CompileRun("obj"); |
| 69 | Handle<Value> notify_fun1 = CompileRun( |
| 70 | "(function() { obj.foo = 'bar'; })"); |
| 71 | Handle<Value> notify_fun2; |
| 72 | { |
| 73 | LocalContext context2; |
| 74 | context2->Global()->Set(String::New("obj"), obj); |
| 75 | notify_fun2 = CompileRun( |
| 76 | "(function() { obj.foo = 'baz'; })"); |
| 77 | } |
| 78 | Handle<Value> notify_fun3; |
| 79 | { |
| 80 | LocalContext context3; |
| 81 | context3->Global()->Set(String::New("obj"), obj); |
| 82 | notify_fun3 = CompileRun( |
| 83 | "(function() { obj.foo = 'bat'; })"); |
| 84 | } |
| 85 | { |
| 86 | LocalContext context4; |
| 87 | context4->Global()->Set(String::New("observer"), observer); |
| 88 | context4->Global()->Set(String::New("fun1"), notify_fun1); |
| 89 | context4->Global()->Set(String::New("fun2"), notify_fun2); |
| 90 | context4->Global()->Set(String::New("fun3"), notify_fun3); |
| 91 | CompileRun("fun1(); fun2(); fun3(); Object.deliverChangeRecords(observer)"); |
| 92 | } |
| 93 | CHECK_EQ(1, CompileRun("calls")->Int32Value()); |
| 94 | CHECK_EQ(3, CompileRun("count")->Int32Value()); |
| 95 | } |
| 96 | |
| 97 | TEST(EndOfMicrotaskDelivery) { |
| 98 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 99 | HandleScope scope(isolate.GetIsolate()); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 100 | LocalContext context; |
| 101 | CompileRun( |
| 102 | "var obj = {};" |
| 103 | "var count = 0;" |
| 104 | "var observer = function(records) { count = records.length };" |
| 105 | "Object.observe(obj, observer);" |
| 106 | "obj.foo = 'bar';"); |
| 107 | CHECK_EQ(1, CompileRun("count")->Int32Value()); |
| 108 | } |
| 109 | |
| 110 | TEST(DeliveryOrdering) { |
| 111 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 112 | HandleScope scope(isolate.GetIsolate()); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 113 | LocalContext context; |
| 114 | CompileRun( |
| 115 | "var obj1 = {};" |
| 116 | "var obj2 = {};" |
| 117 | "var ordering = [];" |
| 118 | "function observer2() { ordering.push(2); };" |
| 119 | "function observer1() { ordering.push(1); };" |
| 120 | "function observer3() { ordering.push(3); };" |
| 121 | "Object.observe(obj1, observer1);" |
| 122 | "Object.observe(obj1, observer2);" |
| 123 | "Object.observe(obj1, observer3);" |
| 124 | "obj1.foo = 'bar';"); |
| 125 | CHECK_EQ(3, CompileRun("ordering.length")->Int32Value()); |
| 126 | CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); |
| 127 | CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); |
| 128 | CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); |
| 129 | CompileRun( |
| 130 | "ordering = [];" |
| 131 | "Object.observe(obj2, observer3);" |
| 132 | "Object.observe(obj2, observer2);" |
| 133 | "Object.observe(obj2, observer1);" |
| 134 | "obj2.foo = 'baz'"); |
| 135 | CHECK_EQ(3, CompileRun("ordering.length")->Int32Value()); |
| 136 | CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); |
| 137 | CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); |
| 138 | CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); |
| 139 | } |
| 140 | |
| 141 | TEST(DeliveryOrderingReentrant) { |
| 142 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 143 | HandleScope scope(isolate.GetIsolate()); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 144 | LocalContext context; |
| 145 | CompileRun( |
| 146 | "var obj = {};" |
| 147 | "var reentered = false;" |
| 148 | "var ordering = [];" |
| 149 | "function observer1() { ordering.push(1); };" |
| 150 | "function observer2() {" |
| 151 | " if (!reentered) {" |
| 152 | " obj.foo = 'baz';" |
| 153 | " reentered = true;" |
| 154 | " }" |
| 155 | " ordering.push(2);" |
| 156 | "};" |
| 157 | "function observer3() { ordering.push(3); };" |
| 158 | "Object.observe(obj, observer1);" |
| 159 | "Object.observe(obj, observer2);" |
| 160 | "Object.observe(obj, observer3);" |
| 161 | "obj.foo = 'bar';"); |
| 162 | CHECK_EQ(5, CompileRun("ordering.length")->Int32Value()); |
| 163 | CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); |
| 164 | CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); |
| 165 | CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value()); |
| 166 | // Note that we re-deliver to observers 1 and 2, while observer3 |
| 167 | // already received the second record during the first round. |
| 168 | CHECK_EQ(1, CompileRun("ordering[3]")->Int32Value()); |
| 169 | CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); |
| 170 | } |
| 171 | |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 172 | TEST(DeliveryOrderingDeliverChangeRecords) { |
| 173 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 174 | HandleScope scope(isolate.GetIsolate()); |
yangguo@chromium.org | fb37721 | 2012-11-16 14:43:43 +0000 | [diff] [blame] | 175 | LocalContext context; |
| 176 | CompileRun( |
| 177 | "var obj = {};" |
| 178 | "var ordering = [];" |
| 179 | "function observer1() { ordering.push(1); if (!obj.b) obj.b = true };" |
| 180 | "function observer2() { ordering.push(2); };" |
| 181 | "Object.observe(obj, observer1);" |
| 182 | "Object.observe(obj, observer2);" |
| 183 | "obj.a = 1;" |
| 184 | "Object.deliverChangeRecords(observer2);"); |
| 185 | CHECK_EQ(4, CompileRun("ordering.length")->Int32Value()); |
| 186 | // First, observer2 is called due to deliverChangeRecords |
| 187 | CHECK_EQ(2, CompileRun("ordering[0]")->Int32Value()); |
| 188 | // Then, observer1 is called when the stack unwinds |
| 189 | CHECK_EQ(1, CompileRun("ordering[1]")->Int32Value()); |
| 190 | // observer1's mutation causes both 1 and 2 to be reactivated, |
| 191 | // with 1 having priority. |
| 192 | CHECK_EQ(1, CompileRun("ordering[2]")->Int32Value()); |
| 193 | CHECK_EQ(2, CompileRun("ordering[3]")->Int32Value()); |
| 194 | } |
| 195 | |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 196 | TEST(ObjectHashTableGrowth) { |
| 197 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 198 | HandleScope scope(isolate.GetIsolate()); |
mvstanton@chromium.org | e4ac3ef | 2012-11-12 14:53:34 +0000 | [diff] [blame] | 199 | // Initializing this context sets up initial hash tables. |
| 200 | LocalContext context; |
| 201 | Handle<Value> obj = CompileRun("obj = {};"); |
| 202 | Handle<Value> observer = CompileRun( |
| 203 | "var ran = false;" |
| 204 | "(function() { ran = true })"); |
| 205 | { |
| 206 | // As does initializing this context. |
| 207 | LocalContext context2; |
| 208 | context2->Global()->Set(String::New("obj"), obj); |
| 209 | context2->Global()->Set(String::New("observer"), observer); |
| 210 | CompileRun( |
| 211 | "var objArr = [];" |
| 212 | // 100 objects should be enough to make the hash table grow |
| 213 | // (and thus relocate). |
| 214 | "for (var i = 0; i < 100; ++i) {" |
| 215 | " objArr.push({});" |
| 216 | " Object.observe(objArr[objArr.length-1], function(){});" |
| 217 | "}" |
| 218 | "Object.observe(obj, observer);"); |
| 219 | } |
| 220 | // obj is now marked "is_observed", but our map has moved. |
| 221 | CompileRun("obj.foo = 'bar'"); |
| 222 | CHECK(CompileRun("ran")->BooleanValue()); |
| 223 | } |
mstarzinger@chromium.org | 32280cf | 2012-12-06 17:32:37 +0000 | [diff] [blame] | 224 | |
| 225 | TEST(GlobalObjectObservation) { |
| 226 | HarmonyIsolate isolate; |
mstarzinger@chromium.org | 32280cf | 2012-12-06 17:32:37 +0000 | [diff] [blame] | 227 | LocalContext context; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 228 | HandleScope scope(isolate.GetIsolate()); |
mstarzinger@chromium.org | 32280cf | 2012-12-06 17:32:37 +0000 | [diff] [blame] | 229 | Handle<Object> global_proxy = context->Global(); |
| 230 | Handle<Object> inner_global = global_proxy->GetPrototype().As<Object>(); |
| 231 | CompileRun( |
| 232 | "var records = [];" |
| 233 | "var global = this;" |
| 234 | "Object.observe(global, function(r) { [].push.apply(records, r) });" |
| 235 | "global.foo = 'hello';"); |
| 236 | CHECK_EQ(1, CompileRun("records.length")->Int32Value()); |
| 237 | CHECK(global_proxy->StrictEquals(CompileRun("records[0].object"))); |
| 238 | |
| 239 | // Detached, mutating the proxy has no effect. |
| 240 | context->DetachGlobal(); |
| 241 | CompileRun("global.bar = 'goodbye';"); |
| 242 | CHECK_EQ(1, CompileRun("records.length")->Int32Value()); |
| 243 | |
| 244 | // Mutating the global object directly still has an effect... |
| 245 | CompileRun("this.bar = 'goodbye';"); |
| 246 | CHECK_EQ(2, CompileRun("records.length")->Int32Value()); |
| 247 | CHECK(inner_global->StrictEquals(CompileRun("records[1].object"))); |
| 248 | |
| 249 | // Reattached, back to global proxy. |
| 250 | context->ReattachGlobal(global_proxy); |
| 251 | CompileRun("global.baz = 'again';"); |
| 252 | CHECK_EQ(3, CompileRun("records.length")->Int32Value()); |
| 253 | CHECK(global_proxy->StrictEquals(CompileRun("records[2].object"))); |
| 254 | |
| 255 | // Attached to a different context, should not leak mutations |
| 256 | // to the old context. |
| 257 | context->DetachGlobal(); |
| 258 | { |
| 259 | LocalContext context2; |
| 260 | context2->DetachGlobal(); |
| 261 | context2->ReattachGlobal(global_proxy); |
| 262 | CompileRun( |
| 263 | "var records2 = [];" |
| 264 | "Object.observe(this, function(r) { [].push.apply(records2, r) });" |
| 265 | "this.bat = 'context2';"); |
| 266 | CHECK_EQ(1, CompileRun("records2.length")->Int32Value()); |
| 267 | CHECK(global_proxy->StrictEquals(CompileRun("records2[0].object"))); |
| 268 | } |
| 269 | CHECK_EQ(3, CompileRun("records.length")->Int32Value()); |
| 270 | |
| 271 | // Attaching by passing to Context::New |
| 272 | { |
| 273 | // Delegates to Context::New |
| 274 | LocalContext context3(NULL, Handle<ObjectTemplate>(), global_proxy); |
| 275 | CompileRun( |
| 276 | "var records3 = [];" |
| 277 | "Object.observe(this, function(r) { [].push.apply(records3, r) });" |
| 278 | "this.qux = 'context3';"); |
| 279 | CHECK_EQ(1, CompileRun("records3.length")->Int32Value()); |
| 280 | CHECK(global_proxy->StrictEquals(CompileRun("records3[0].object"))); |
| 281 | } |
| 282 | CHECK_EQ(3, CompileRun("records.length")->Int32Value()); |
| 283 | } |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 284 | |
ulan@chromium.org | 4121f23 | 2012-12-27 15:57:11 +0000 | [diff] [blame] | 285 | |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 286 | struct RecordExpectation { |
| 287 | Handle<Value> object; |
| 288 | const char* type; |
| 289 | const char* name; |
| 290 | Handle<Value> old_value; |
| 291 | }; |
| 292 | |
| 293 | // TODO(adamk): Use this helper elsewhere in this file. |
| 294 | static void ExpectRecords(Handle<Value> records, |
| 295 | const RecordExpectation expectations[], |
| 296 | int num) { |
| 297 | CHECK(records->IsArray()); |
| 298 | Handle<Array> recordArray = records.As<Array>(); |
| 299 | CHECK_EQ(num, static_cast<int>(recordArray->Length())); |
| 300 | for (int i = 0; i < num; ++i) { |
| 301 | Handle<Value> record = recordArray->Get(i); |
| 302 | CHECK(record->IsObject()); |
| 303 | Handle<Object> recordObj = record.As<Object>(); |
| 304 | CHECK(expectations[i].object->StrictEquals( |
| 305 | recordObj->Get(String::New("object")))); |
| 306 | CHECK(String::New(expectations[i].type)->Equals( |
| 307 | recordObj->Get(String::New("type")))); |
| 308 | CHECK(String::New(expectations[i].name)->Equals( |
| 309 | recordObj->Get(String::New("name")))); |
| 310 | if (!expectations[i].old_value.IsEmpty()) { |
| 311 | CHECK(expectations[i].old_value->Equals( |
| 312 | recordObj->Get(String::New("oldValue")))); |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
ulan@chromium.org | 4121f23 | 2012-12-27 15:57:11 +0000 | [diff] [blame] | 317 | #define EXPECT_RECORDS(records, expectations) \ |
| 318 | ExpectRecords(records, expectations, ARRAY_SIZE(expectations)) |
| 319 | |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 320 | TEST(APITestBasicMutation) { |
| 321 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 322 | HandleScope scope(isolate.GetIsolate()); |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 323 | LocalContext context; |
| 324 | Handle<Object> obj = Handle<Object>::Cast(CompileRun( |
| 325 | "var records = [];" |
| 326 | "var obj = {};" |
| 327 | "function observer(r) { [].push.apply(records, r); };" |
| 328 | "Object.observe(obj, observer);" |
| 329 | "obj")); |
hpayer@chromium.org | 7c3372b | 2013-02-13 17:26:04 +0000 | [diff] [blame] | 330 | obj->Set(String::New("foo"), Number::New(7)); |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 331 | obj->Set(1, Number::New(2)); |
| 332 | // ForceSet should work just as well as Set |
| 333 | obj->ForceSet(String::New("foo"), Number::New(3)); |
| 334 | obj->ForceSet(Number::New(1), Number::New(4)); |
| 335 | // Setting an indexed element via the property setting method |
| 336 | obj->Set(Number::New(1), Number::New(5)); |
| 337 | // Setting with a non-String, non-uint32 key |
| 338 | obj->Set(Number::New(1.1), Number::New(6), DontDelete); |
| 339 | obj->Delete(String::New("foo")); |
| 340 | obj->Delete(1); |
| 341 | obj->ForceDelete(Number::New(1.1)); |
| 342 | |
| 343 | // Force delivery |
| 344 | // TODO(adamk): Should the above set methods trigger delivery themselves? |
| 345 | CompileRun("void 0"); |
| 346 | CHECK_EQ(9, CompileRun("records.length")->Int32Value()); |
| 347 | const RecordExpectation expected_records[] = { |
| 348 | { obj, "new", "foo", Handle<Value>() }, |
| 349 | { obj, "new", "1", Handle<Value>() }, |
hpayer@chromium.org | 7c3372b | 2013-02-13 17:26:04 +0000 | [diff] [blame] | 350 | // Note: use 7 not 1 below, as the latter triggers a nifty VS10 compiler bug |
| 351 | // where instead of 1.0, a garbage value would be passed into Number::New. |
| 352 | { obj, "updated", "foo", Number::New(7) }, |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 353 | { obj, "updated", "1", Number::New(2) }, |
| 354 | { obj, "updated", "1", Number::New(4) }, |
| 355 | { obj, "new", "1.1", Handle<Value>() }, |
| 356 | { obj, "deleted", "foo", Number::New(3) }, |
| 357 | { obj, "deleted", "1", Number::New(5) }, |
| 358 | { obj, "deleted", "1.1", Number::New(6) } |
| 359 | }; |
ulan@chromium.org | 4121f23 | 2012-12-27 15:57:11 +0000 | [diff] [blame] | 360 | EXPECT_RECORDS(CompileRun("records"), expected_records); |
| 361 | } |
| 362 | |
| 363 | TEST(HiddenPrototypeObservation) { |
| 364 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 365 | HandleScope scope(isolate.GetIsolate()); |
ulan@chromium.org | 4121f23 | 2012-12-27 15:57:11 +0000 | [diff] [blame] | 366 | LocalContext context; |
| 367 | Handle<FunctionTemplate> tmpl = FunctionTemplate::New(); |
| 368 | tmpl->SetHiddenPrototype(true); |
| 369 | tmpl->InstanceTemplate()->Set(String::New("foo"), Number::New(75)); |
| 370 | Handle<Object> proto = tmpl->GetFunction()->NewInstance(); |
| 371 | Handle<Object> obj = Object::New(); |
| 372 | obj->SetPrototype(proto); |
| 373 | context->Global()->Set(String::New("obj"), obj); |
| 374 | context->Global()->Set(String::New("proto"), proto); |
| 375 | CompileRun( |
| 376 | "var records;" |
| 377 | "function observer(r) { records = r; };" |
| 378 | "Object.observe(obj, observer);" |
| 379 | "obj.foo = 41;" // triggers a notification |
| 380 | "proto.foo = 42;"); // does not trigger a notification |
| 381 | const RecordExpectation expected_records[] = { |
| 382 | { obj, "updated", "foo", Number::New(75) } |
| 383 | }; |
| 384 | EXPECT_RECORDS(CompileRun("records"), expected_records); |
| 385 | obj->SetPrototype(Null()); |
| 386 | CompileRun("obj.foo = 43"); |
| 387 | const RecordExpectation expected_records2[] = { |
| 388 | { obj, "new", "foo", Handle<Value>() } |
| 389 | }; |
| 390 | EXPECT_RECORDS(CompileRun("records"), expected_records2); |
| 391 | obj->SetPrototype(proto); |
| 392 | CompileRun( |
| 393 | "Object.observe(proto, observer);" |
| 394 | "proto.bar = 1;" |
| 395 | "Object.unobserve(obj, observer);" |
| 396 | "obj.foo = 44;"); |
| 397 | const RecordExpectation expected_records3[] = { |
| 398 | { proto, "new", "bar", Handle<Value>() } |
| 399 | // TODO(adamk): The below record should be emitted since proto is observed |
| 400 | // and has been modified. Not clear if this happens in practice. |
| 401 | // { proto, "updated", "foo", Number::New(43) } |
| 402 | }; |
| 403 | EXPECT_RECORDS(CompileRun("records"), expected_records3); |
yangguo@chromium.org | a6bbcc8 | 2012-12-21 12:35:02 +0000 | [diff] [blame] | 404 | } |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 405 | |
| 406 | |
| 407 | static int NumberOfElements(i::Handle<i::JSWeakMap> map) { |
| 408 | return i::ObjectHashTable::cast(map->table())->NumberOfElements(); |
| 409 | } |
| 410 | |
| 411 | |
| 412 | TEST(ObservationWeakMap) { |
| 413 | HarmonyIsolate isolate; |
svenpanne@chromium.org | 2bda543 | 2013-03-15 12:39:50 +0000 | [diff] [blame] | 414 | HandleScope scope(isolate.GetIsolate()); |
mmassi@chromium.org | 2f0efde | 2013-02-06 14:12:58 +0000 | [diff] [blame] | 415 | LocalContext context; |
| 416 | CompileRun( |
| 417 | "var obj = {};" |
| 418 | "Object.observe(obj, function(){});" |
| 419 | "Object.getNotifier(obj);" |
| 420 | "obj = null;"); |
| 421 | i::Handle<i::JSObject> observation_state = FACTORY->observation_state(); |
| 422 | i::Handle<i::JSWeakMap> observerInfoMap = |
| 423 | i::Handle<i::JSWeakMap>::cast( |
| 424 | i::GetProperty(observation_state, "observerInfoMap")); |
| 425 | i::Handle<i::JSWeakMap> objectInfoMap = |
| 426 | i::Handle<i::JSWeakMap>::cast( |
| 427 | i::GetProperty(observation_state, "objectInfoMap")); |
| 428 | i::Handle<i::JSWeakMap> notifierTargetMap = |
| 429 | i::Handle<i::JSWeakMap>::cast( |
| 430 | i::GetProperty(observation_state, "notifierTargetMap")); |
| 431 | CHECK_EQ(1, NumberOfElements(observerInfoMap)); |
| 432 | CHECK_EQ(1, NumberOfElements(objectInfoMap)); |
| 433 | CHECK_EQ(1, NumberOfElements(notifierTargetMap)); |
| 434 | HEAP->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask); |
| 435 | CHECK_EQ(0, NumberOfElements(observerInfoMap)); |
| 436 | CHECK_EQ(0, NumberOfElements(objectInfoMap)); |
| 437 | CHECK_EQ(0, NumberOfElements(notifierTargetMap)); |
| 438 | } |