Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / v8 / test / cctest / test-object-observe.cc
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 "src/v8.h"
29
30 #include "test/cctest/cctest.h"
31
32 using namespace v8;
33 namespace i = v8::internal;
34
35
36 TEST(PerIsolateState) {
37   HandleScope scope(CcTest::isolate());
38   LocalContext context1(CcTest::isolate());
39
40   Local<Value> foo = v8_str("foo");
41   context1->SetSecurityToken(foo);
42
43   CompileRun(
44       "var count = 0;"
45       "var calls = 0;"
46       "var observer = function(records) { count = records.length; calls++ };"
47       "var obj = {};"
48       "Object.observe(obj, observer);");
49   Handle<Value> observer = CompileRun("observer");
50   Handle<Value> obj = CompileRun("obj");
51   Handle<Value> notify_fun1 = CompileRun(
52       "(function() { obj.foo = 'bar'; })");
53   Handle<Value> notify_fun2;
54   {
55     LocalContext context2(CcTest::isolate());
56     context2->SetSecurityToken(foo);
57     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
58                             obj);
59     notify_fun2 = CompileRun(
60         "(function() { obj.foo = 'baz'; })");
61   }
62   Handle<Value> notify_fun3;
63   {
64     LocalContext context3(CcTest::isolate());
65     context3->SetSecurityToken(foo);
66     context3->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
67                             obj);
68     notify_fun3 = CompileRun(
69         "(function() { obj.foo = 'bat'; })");
70   }
71   {
72     LocalContext context4(CcTest::isolate());
73     context4->SetSecurityToken(foo);
74     context4->Global()->Set(
75         String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
76     context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun1"),
77                             notify_fun1);
78     context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun2"),
79                             notify_fun2);
80     context4->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "fun3"),
81                             notify_fun3);
82     CompileRun("fun1(); fun2(); fun3(); Object.deliverChangeRecords(observer)");
83   }
84   CHECK_EQ(1, CompileRun("calls")->Int32Value());
85   CHECK_EQ(3, CompileRun("count")->Int32Value());
86 }
87
88
89 TEST(EndOfMicrotaskDelivery) {
90   HandleScope scope(CcTest::isolate());
91   LocalContext context(CcTest::isolate());
92   CompileRun(
93       "var obj = {};"
94       "var count = 0;"
95       "var observer = function(records) { count = records.length };"
96       "Object.observe(obj, observer);"
97       "obj.foo = 'bar';");
98   CHECK_EQ(1, CompileRun("count")->Int32Value());
99 }
100
101
102 TEST(DeliveryOrdering) {
103   HandleScope scope(CcTest::isolate());
104   LocalContext context(CcTest::isolate());
105   CompileRun(
106       "var obj1 = {};"
107       "var obj2 = {};"
108       "var ordering = [];"
109       "function observer2() { ordering.push(2); };"
110       "function observer1() { ordering.push(1); };"
111       "function observer3() { ordering.push(3); };"
112       "Object.observe(obj1, observer1);"
113       "Object.observe(obj1, observer2);"
114       "Object.observe(obj1, observer3);"
115       "obj1.foo = 'bar';");
116   CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
117   CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
118   CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
119   CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
120   CompileRun(
121       "ordering = [];"
122       "Object.observe(obj2, observer3);"
123       "Object.observe(obj2, observer2);"
124       "Object.observe(obj2, observer1);"
125       "obj2.foo = 'baz'");
126   CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
127   CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
128   CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
129   CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
130 }
131
132
133 TEST(DeliveryOrderingReentrant) {
134   HandleScope scope(CcTest::isolate());
135   LocalContext context(CcTest::isolate());
136   CompileRun(
137       "var obj = {};"
138       "var reentered = false;"
139       "var ordering = [];"
140       "function observer1() { ordering.push(1); };"
141       "function observer2() {"
142       "  if (!reentered) {"
143       "    obj.foo = 'baz';"
144       "    reentered = true;"
145       "  }"
146       "  ordering.push(2);"
147       "};"
148       "function observer3() { ordering.push(3); };"
149       "Object.observe(obj, observer1);"
150       "Object.observe(obj, observer2);"
151       "Object.observe(obj, observer3);"
152       "obj.foo = 'bar';");
153   CHECK_EQ(5, CompileRun("ordering.length")->Int32Value());
154   CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
155   CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
156   CHECK_EQ(3, CompileRun("ordering[2]")->Int32Value());
157   // Note that we re-deliver to observers 1 and 2, while observer3
158   // already received the second record during the first round.
159   CHECK_EQ(1, CompileRun("ordering[3]")->Int32Value());
160   CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
161 }
162
163
164 TEST(DeliveryOrderingDeliverChangeRecords) {
165   HandleScope scope(CcTest::isolate());
166   LocalContext context(CcTest::isolate());
167   CompileRun(
168       "var obj = {};"
169       "var ordering = [];"
170       "function observer1() { ordering.push(1); if (!obj.b) obj.b = true };"
171       "function observer2() { ordering.push(2); };"
172       "Object.observe(obj, observer1);"
173       "Object.observe(obj, observer2);"
174       "obj.a = 1;"
175       "Object.deliverChangeRecords(observer2);");
176   CHECK_EQ(4, CompileRun("ordering.length")->Int32Value());
177   // First, observer2 is called due to deliverChangeRecords
178   CHECK_EQ(2, CompileRun("ordering[0]")->Int32Value());
179   // Then, observer1 is called when the stack unwinds
180   CHECK_EQ(1, CompileRun("ordering[1]")->Int32Value());
181   // observer1's mutation causes both 1 and 2 to be reactivated,
182   // with 1 having priority.
183   CHECK_EQ(1, CompileRun("ordering[2]")->Int32Value());
184   CHECK_EQ(2, CompileRun("ordering[3]")->Int32Value());
185 }
186
187
188 TEST(ObjectHashTableGrowth) {
189   HandleScope scope(CcTest::isolate());
190   // Initializing this context sets up initial hash tables.
191   LocalContext context(CcTest::isolate());
192   Handle<Value> obj = CompileRun("obj = {};");
193   Handle<Value> observer = CompileRun(
194       "var ran = false;"
195       "(function() { ran = true })");
196   {
197     // As does initializing this context.
198     LocalContext context2(CcTest::isolate());
199     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
200                             obj);
201     context2->Global()->Set(
202         String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
203     CompileRun(
204         "var objArr = [];"
205         // 100 objects should be enough to make the hash table grow
206         // (and thus relocate).
207         "for (var i = 0; i < 100; ++i) {"
208         "  objArr.push({});"
209         "  Object.observe(objArr[objArr.length-1], function(){});"
210         "}"
211         "Object.observe(obj, observer);");
212   }
213   // obj is now marked "is_observed", but our map has moved.
214   CompileRun("obj.foo = 'bar'");
215   CHECK(CompileRun("ran")->BooleanValue());
216 }
217
218
219 struct RecordExpectation {
220   Handle<Value> object;
221   const char* type;
222   const char* name;
223   Handle<Value> old_value;
224 };
225
226
227 // TODO(adamk): Use this helper elsewhere in this file.
228 static void ExpectRecords(v8::Isolate* isolate,
229                           Handle<Value> records,
230                           const RecordExpectation expectations[],
231                           int num) {
232   CHECK(records->IsArray());
233   Handle<Array> recordArray = records.As<Array>();
234   CHECK_EQ(num, static_cast<int>(recordArray->Length()));
235   for (int i = 0; i < num; ++i) {
236     Handle<Value> record = recordArray->Get(i);
237     CHECK(record->IsObject());
238     Handle<Object> recordObj = record.As<Object>();
239     CHECK(expectations[i].object->StrictEquals(
240         recordObj->Get(String::NewFromUtf8(isolate, "object"))));
241     CHECK(String::NewFromUtf8(isolate, expectations[i].type)->Equals(
242         recordObj->Get(String::NewFromUtf8(isolate, "type"))));
243     if (strcmp("splice", expectations[i].type) != 0) {
244       CHECK(String::NewFromUtf8(isolate, expectations[i].name)->Equals(
245           recordObj->Get(String::NewFromUtf8(isolate, "name"))));
246       if (!expectations[i].old_value.IsEmpty()) {
247         CHECK(expectations[i].old_value->Equals(
248             recordObj->Get(String::NewFromUtf8(isolate, "oldValue"))));
249       }
250     }
251   }
252 }
253
254 #define EXPECT_RECORDS(records, expectations)                \
255   ExpectRecords(CcTest::isolate(), records, expectations, \
256                 arraysize(expectations))
257
258 TEST(APITestBasicMutation) {
259   v8::Isolate* v8_isolate = CcTest::isolate();
260   HandleScope scope(v8_isolate);
261   LocalContext context(v8_isolate);
262   Handle<Object> obj = Handle<Object>::Cast(CompileRun(
263       "var records = [];"
264       "var obj = {};"
265       "function observer(r) { [].push.apply(records, r); };"
266       "Object.observe(obj, observer);"
267       "obj"));
268   obj->Set(String::NewFromUtf8(v8_isolate, "foo"),
269            Number::New(v8_isolate, 7));
270   obj->Set(1, Number::New(v8_isolate, 2));
271   // ForceSet should work just as well as Set
272   obj->ForceSet(String::NewFromUtf8(v8_isolate, "foo"),
273                 Number::New(v8_isolate, 3));
274   obj->ForceSet(Number::New(v8_isolate, 1), Number::New(v8_isolate, 4));
275   // Setting an indexed element via the property setting method
276   obj->Set(Number::New(v8_isolate, 1), Number::New(v8_isolate, 5));
277   // Setting with a non-String, non-uint32 key
278   obj->ForceSet(Number::New(v8_isolate, 1.1), Number::New(v8_isolate, 6),
279                 DontDelete);
280   obj->Delete(String::NewFromUtf8(v8_isolate, "foo"));
281   obj->Delete(1);
282   obj->ForceDelete(Number::New(v8_isolate, 1.1));
283
284   // Force delivery
285   // TODO(adamk): Should the above set methods trigger delivery themselves?
286   CompileRun("void 0");
287   CHECK_EQ(9, CompileRun("records.length")->Int32Value());
288   const RecordExpectation expected_records[] = {
289     { obj, "add", "foo", Handle<Value>() },
290     { obj, "add", "1", Handle<Value>() },
291     // Note: use 7 not 1 below, as the latter triggers a nifty VS10 compiler bug
292     // where instead of 1.0, a garbage value would be passed into Number::New.
293     { obj, "update", "foo", Number::New(v8_isolate, 7) },
294     { obj, "update", "1", Number::New(v8_isolate, 2) },
295     { obj, "update", "1", Number::New(v8_isolate, 4) },
296     { obj, "add", "1.1", Handle<Value>() },
297     { obj, "delete", "foo", Number::New(v8_isolate, 3) },
298     { obj, "delete", "1", Number::New(v8_isolate, 5) },
299     { obj, "delete", "1.1", Number::New(v8_isolate, 6) }
300   };
301   EXPECT_RECORDS(CompileRun("records"), expected_records);
302 }
303
304
305 TEST(HiddenPrototypeObservation) {
306   v8::Isolate* v8_isolate = CcTest::isolate();
307   HandleScope scope(v8_isolate);
308   LocalContext context(v8_isolate);
309   Handle<FunctionTemplate> tmpl = FunctionTemplate::New(v8_isolate);
310   tmpl->SetHiddenPrototype(true);
311   tmpl->InstanceTemplate()->Set(
312       String::NewFromUtf8(v8_isolate, "foo"), Number::New(v8_isolate, 75));
313   Handle<Object> proto = tmpl->GetFunction()->NewInstance();
314   Handle<Object> obj = Object::New(v8_isolate);
315   obj->SetPrototype(proto);
316   context->Global()->Set(String::NewFromUtf8(v8_isolate, "obj"), obj);
317   context->Global()->Set(String::NewFromUtf8(v8_isolate, "proto"),
318                          proto);
319   CompileRun(
320       "var records;"
321       "function observer(r) { records = r; };"
322       "Object.observe(obj, observer);"
323       "obj.foo = 41;"  // triggers a notification
324       "proto.foo = 42;");  // does not trigger a notification
325   const RecordExpectation expected_records[] = {
326     { obj, "update", "foo", Number::New(v8_isolate, 75) }
327   };
328   EXPECT_RECORDS(CompileRun("records"), expected_records);
329   obj->SetPrototype(Null(v8_isolate));
330   CompileRun("obj.foo = 43");
331   const RecordExpectation expected_records2[] = {
332     { obj, "add", "foo", Handle<Value>() }
333   };
334   EXPECT_RECORDS(CompileRun("records"), expected_records2);
335   obj->SetPrototype(proto);
336   CompileRun(
337       "Object.observe(proto, observer);"
338       "proto.bar = 1;"
339       "Object.unobserve(obj, observer);"
340       "obj.foo = 44;");
341   const RecordExpectation expected_records3[] = {
342     { proto, "add", "bar", Handle<Value>() }
343     // TODO(adamk): The below record should be emitted since proto is observed
344     // and has been modified. Not clear if this happens in practice.
345     // { proto, "update", "foo", Number::New(43) }
346   };
347   EXPECT_RECORDS(CompileRun("records"), expected_records3);
348 }
349
350
351 static int NumberOfElements(i::Handle<i::JSWeakMap> map) {
352   return i::ObjectHashTable::cast(map->table())->NumberOfElements();
353 }
354
355
356 TEST(ObservationWeakMap) {
357   HandleScope scope(CcTest::isolate());
358   LocalContext context(CcTest::isolate());
359   CompileRun(
360       "var obj = {};"
361       "Object.observe(obj, function(){});"
362       "Object.getNotifier(obj);"
363       "obj = null;");
364   i::Isolate* i_isolate = CcTest::i_isolate();
365   i::Handle<i::JSObject> observation_state =
366       i_isolate->factory()->observation_state();
367   i::Handle<i::JSWeakMap> callbackInfoMap =
368       i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
369           i_isolate, observation_state, "callbackInfoMap").ToHandleChecked());
370   i::Handle<i::JSWeakMap> objectInfoMap =
371       i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
372           i_isolate, observation_state, "objectInfoMap").ToHandleChecked());
373   i::Handle<i::JSWeakMap> notifierObjectInfoMap =
374       i::Handle<i::JSWeakMap>::cast(i::Object::GetProperty(
375           i_isolate, observation_state, "notifierObjectInfoMap")
376               .ToHandleChecked());
377   CHECK_EQ(1, NumberOfElements(callbackInfoMap));
378   CHECK_EQ(1, NumberOfElements(objectInfoMap));
379   CHECK_EQ(1, NumberOfElements(notifierObjectInfoMap));
380   i_isolate->heap()->CollectAllGarbage(i::Heap::kAbortIncrementalMarkingMask);
381   CHECK_EQ(0, NumberOfElements(callbackInfoMap));
382   CHECK_EQ(0, NumberOfElements(objectInfoMap));
383   CHECK_EQ(0, NumberOfElements(notifierObjectInfoMap));
384 }
385
386
387 static int TestObserveSecurity(Handle<Context> observer_context,
388                                Handle<Context> object_context,
389                                Handle<Context> mutation_context) {
390   Context::Scope observer_scope(observer_context);
391   CompileRun("var records = null;"
392              "var observer = function(r) { records = r };");
393   Handle<Value> observer = CompileRun("observer");
394   {
395     Context::Scope object_scope(object_context);
396     object_context->Global()->Set(
397         String::NewFromUtf8(CcTest::isolate(), "observer"), observer);
398     CompileRun("var obj = {};"
399                "obj.length = 0;"
400                "Object.observe(obj, observer,"
401                    "['add', 'update', 'delete','reconfigure','splice']"
402                ");");
403     Handle<Value> obj = CompileRun("obj");
404     {
405       Context::Scope mutation_scope(mutation_context);
406       mutation_context->Global()->Set(
407           String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
408       CompileRun("obj.foo = 'bar';"
409                  "obj.foo = 'baz';"
410                  "delete obj.foo;"
411                  "Object.defineProperty(obj, 'bar', {value: 'bot'});"
412                  "Array.prototype.push.call(obj, 1, 2, 3);"
413                  "Array.prototype.splice.call(obj, 1, 2, 2, 4);"
414                  "Array.prototype.pop.call(obj);"
415                  "Array.prototype.shift.call(obj);");
416     }
417   }
418   return CompileRun("records ? records.length : 0")->Int32Value();
419 }
420
421
422 TEST(ObserverSecurityAAA) {
423   v8::Isolate* isolate = CcTest::isolate();
424   v8::HandleScope scope(isolate);
425   v8::Local<Context> contextA = Context::New(isolate);
426   CHECK_EQ(8, TestObserveSecurity(contextA, contextA, contextA));
427 }
428
429
430 TEST(ObserverSecurityA1A2A3) {
431   v8::Isolate* isolate = CcTest::isolate();
432   v8::HandleScope scope(isolate);
433
434   v8::Local<Context> contextA1 = Context::New(isolate);
435   v8::Local<Context> contextA2 = Context::New(isolate);
436   v8::Local<Context> contextA3 = Context::New(isolate);
437
438   Local<Value> foo = v8_str("foo");
439   contextA1->SetSecurityToken(foo);
440   contextA2->SetSecurityToken(foo);
441   contextA3->SetSecurityToken(foo);
442
443   CHECK_EQ(8, TestObserveSecurity(contextA1, contextA2, contextA3));
444 }
445
446
447 TEST(ObserverSecurityAAB) {
448   v8::Isolate* isolate = CcTest::isolate();
449   v8::HandleScope scope(isolate);
450   v8::Local<Context> contextA = Context::New(isolate);
451   v8::Local<Context> contextB = Context::New(isolate);
452   CHECK_EQ(0, TestObserveSecurity(contextA, contextA, contextB));
453 }
454
455
456 TEST(ObserverSecurityA1A2B) {
457   v8::Isolate* isolate = CcTest::isolate();
458   v8::HandleScope scope(isolate);
459
460   v8::Local<Context> contextA1 = Context::New(isolate);
461   v8::Local<Context> contextA2 = Context::New(isolate);
462   v8::Local<Context> contextB = Context::New(isolate);
463
464   Local<Value> foo = v8_str("foo");
465   contextA1->SetSecurityToken(foo);
466   contextA2->SetSecurityToken(foo);
467
468   CHECK_EQ(0, TestObserveSecurity(contextA1, contextA2, contextB));
469 }
470
471
472 TEST(ObserverSecurityABA) {
473   v8::Isolate* isolate = CcTest::isolate();
474   v8::HandleScope scope(isolate);
475   v8::Local<Context> contextA = Context::New(isolate);
476   v8::Local<Context> contextB = Context::New(isolate);
477   CHECK_EQ(0, TestObserveSecurity(contextA, contextB, contextA));
478 }
479
480
481 TEST(ObserverSecurityA1BA2) {
482   v8::Isolate* isolate = CcTest::isolate();
483   v8::HandleScope scope(isolate);
484   v8::Local<Context> contextA1 = Context::New(isolate);
485   v8::Local<Context> contextA2 = Context::New(isolate);
486   v8::Local<Context> contextB = Context::New(isolate);
487
488   Local<Value> foo = v8_str("foo");
489   contextA1->SetSecurityToken(foo);
490   contextA2->SetSecurityToken(foo);
491
492   CHECK_EQ(0, TestObserveSecurity(contextA1, contextB, contextA2));
493 }
494
495
496 TEST(ObserverSecurityBAA) {
497   v8::Isolate* isolate = CcTest::isolate();
498   v8::HandleScope scope(isolate);
499   v8::Local<Context> contextA = Context::New(isolate);
500   v8::Local<Context> contextB = Context::New(isolate);
501   CHECK_EQ(0, TestObserveSecurity(contextB, contextA, contextA));
502 }
503
504
505 TEST(ObserverSecurityBA1A2) {
506   v8::Isolate* isolate = CcTest::isolate();
507   v8::HandleScope scope(isolate);
508   v8::Local<Context> contextA1 = Context::New(isolate);
509   v8::Local<Context> contextA2 = Context::New(isolate);
510   v8::Local<Context> contextB = Context::New(isolate);
511
512   Local<Value> foo = v8_str("foo");
513   contextA1->SetSecurityToken(foo);
514   contextA2->SetSecurityToken(foo);
515
516   CHECK_EQ(0, TestObserveSecurity(contextB, contextA1, contextA2));
517 }
518
519
520 TEST(ObserverSecurityNotify) {
521   v8::Isolate* isolate = CcTest::isolate();
522   v8::HandleScope scope(isolate);
523   v8::Local<Context> contextA = Context::New(isolate);
524   v8::Local<Context> contextB = Context::New(isolate);
525
526   Context::Scope scopeA(contextA);
527   CompileRun("var obj = {};"
528              "var recordsA = null;"
529              "var observerA = function(r) { recordsA = r };"
530              "Object.observe(obj, observerA);");
531   Handle<Value> obj = CompileRun("obj");
532
533   {
534     Context::Scope scopeB(contextB);
535     contextB->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"), obj);
536     CompileRun("var recordsB = null;"
537                "var observerB = function(r) { recordsB = r };"
538                "Object.observe(obj, observerB);");
539   }
540
541   CompileRun("var notifier = Object.getNotifier(obj);"
542              "notifier.notify({ type: 'update' });");
543   CHECK_EQ(1, CompileRun("recordsA ? recordsA.length : 0")->Int32Value());
544
545   {
546     Context::Scope scopeB(contextB);
547     CHECK_EQ(0, CompileRun("recordsB ? recordsB.length : 0")->Int32Value());
548   }
549 }
550
551
552 TEST(HiddenPropertiesLeakage) {
553   HandleScope scope(CcTest::isolate());
554   LocalContext context(CcTest::isolate());
555   CompileRun("var obj = {};"
556              "var records = null;"
557              "var observer = function(r) { records = r };"
558              "Object.observe(obj, observer);");
559   Handle<Value> obj =
560       context->Global()->Get(String::NewFromUtf8(CcTest::isolate(), "obj"));
561   Handle<Object>::Cast(obj)
562       ->SetHiddenValue(String::NewFromUtf8(CcTest::isolate(), "foo"),
563                        Null(CcTest::isolate()));
564   CompileRun("");  // trigger delivery
565   CHECK(CompileRun("records")->IsNull());
566 }
567
568
569 TEST(GetNotifierFromOtherContext) {
570   HandleScope scope(CcTest::isolate());
571   LocalContext context(CcTest::isolate());
572   CompileRun("var obj = {};");
573   Handle<Value> instance = CompileRun("obj");
574   {
575     LocalContext context2(CcTest::isolate());
576     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
577                             instance);
578     CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
579   }
580 }
581
582
583 TEST(GetNotifierFromOtherOrigin) {
584   HandleScope scope(CcTest::isolate());
585   Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
586   Handle<Value> bar = String::NewFromUtf8(CcTest::isolate(), "bar");
587   LocalContext context(CcTest::isolate());
588   context->SetSecurityToken(foo);
589   CompileRun("var obj = {};");
590   Handle<Value> instance = CompileRun("obj");
591   {
592     LocalContext context2(CcTest::isolate());
593     context2->SetSecurityToken(bar);
594     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
595                             instance);
596     CHECK(CompileRun("Object.getNotifier(obj)")->IsNull());
597   }
598 }
599
600
601 TEST(GetNotifierFromSameOrigin) {
602   HandleScope scope(CcTest::isolate());
603   Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
604   LocalContext context(CcTest::isolate());
605   context->SetSecurityToken(foo);
606   CompileRun("var obj = {};");
607   Handle<Value> instance = CompileRun("obj");
608   {
609     LocalContext context2(CcTest::isolate());
610     context2->SetSecurityToken(foo);
611     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
612                             instance);
613     CHECK(CompileRun("Object.getNotifier(obj)")->IsObject());
614   }
615 }
616
617
618 static int GetGlobalObjectsCount() {
619   int count = 0;
620   i::HeapIterator it(CcTest::heap());
621   for (i::HeapObject* object = it.next(); object != NULL; object = it.next())
622     if (object->IsJSGlobalObject()) count++;
623   return count;
624 }
625
626
627 static void CheckSurvivingGlobalObjectsCount(int expected) {
628   // We need to collect all garbage twice to be sure that everything
629   // has been collected.  This is because inline caches are cleared in
630   // the first garbage collection but some of the maps have already
631   // been marked at that point.  Therefore some of the maps are not
632   // collected until the second garbage collection.
633   CcTest::heap()->CollectAllGarbage(i::Heap::kNoGCFlags);
634   CcTest::heap()->CollectAllGarbage(i::Heap::kMakeHeapIterableMask);
635   int count = GetGlobalObjectsCount();
636 #ifdef DEBUG
637   if (count != expected) CcTest::heap()->TracePathToGlobal();
638 #endif
639   CHECK_EQ(expected, count);
640 }
641
642
643 TEST(DontLeakContextOnObserve) {
644   HandleScope scope(CcTest::isolate());
645   Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
646   LocalContext context(CcTest::isolate());
647   context->SetSecurityToken(foo);
648   CompileRun("var obj = {};");
649   Handle<Value> object = CompileRun("obj");
650   {
651     HandleScope scope(CcTest::isolate());
652     LocalContext context2(CcTest::isolate());
653     context2->SetSecurityToken(foo);
654     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
655                             object);
656     CompileRun("function observer() {};"
657                "Object.observe(obj, observer, ['foo', 'bar', 'baz']);"
658                "Object.unobserve(obj, observer);");
659   }
660
661   CcTest::isolate()->ContextDisposedNotification();
662   CheckSurvivingGlobalObjectsCount(1);
663 }
664
665
666 TEST(DontLeakContextOnGetNotifier) {
667   HandleScope scope(CcTest::isolate());
668   Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
669   LocalContext context(CcTest::isolate());
670   context->SetSecurityToken(foo);
671   CompileRun("var obj = {};");
672   Handle<Value> object = CompileRun("obj");
673   {
674     HandleScope scope(CcTest::isolate());
675     LocalContext context2(CcTest::isolate());
676     context2->SetSecurityToken(foo);
677     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
678                             object);
679     CompileRun("Object.getNotifier(obj);");
680   }
681
682   CcTest::isolate()->ContextDisposedNotification();
683   CheckSurvivingGlobalObjectsCount(1);
684 }
685
686
687 TEST(DontLeakContextOnNotifierPerformChange) {
688   HandleScope scope(CcTest::isolate());
689   Handle<Value> foo = String::NewFromUtf8(CcTest::isolate(), "foo");
690   LocalContext context(CcTest::isolate());
691   context->SetSecurityToken(foo);
692   CompileRun("var obj = {};");
693   Handle<Value> object = CompileRun("obj");
694   Handle<Value> notifier = CompileRun("Object.getNotifier(obj)");
695   {
696     HandleScope scope(CcTest::isolate());
697     LocalContext context2(CcTest::isolate());
698     context2->SetSecurityToken(foo);
699     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "obj"),
700                             object);
701     context2->Global()->Set(String::NewFromUtf8(CcTest::isolate(), "notifier"),
702                             notifier);
703     CompileRun("var obj2 = {};"
704                "var notifier2 = Object.getNotifier(obj2);"
705                "notifier2.performChange.call("
706                    "notifier, 'foo', function(){})");
707   }
708
709   CcTest::isolate()->ContextDisposedNotification();
710   CheckSurvivingGlobalObjectsCount(1);
711 }