| OLD | NEW |
| (Empty) |
| 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 // Flags: --harmony-observation --harmony-proxies --harmony-collections | |
| 29 // Flags: --harmony-symbols --allow-natives-syntax | |
| 30 | |
| 31 var allObservers = []; | |
| 32 function reset() { | |
| 33 allObservers.forEach(function(observer) { observer.reset(); }); | |
| 34 } | |
| 35 | |
| 36 function stringifyNoThrow(arg) { | |
| 37 try { | |
| 38 return JSON.stringify(arg); | |
| 39 } catch (e) { | |
| 40 return '{<circular reference>}'; | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 function createObserver() { | |
| 45 "use strict"; // So that |this| in callback can be undefined. | |
| 46 | |
| 47 var observer = { | |
| 48 records: undefined, | |
| 49 callbackCount: 0, | |
| 50 reset: function() { | |
| 51 this.records = undefined; | |
| 52 this.callbackCount = 0; | |
| 53 }, | |
| 54 assertNotCalled: function() { | |
| 55 assertEquals(undefined, this.records); | |
| 56 assertEquals(0, this.callbackCount); | |
| 57 }, | |
| 58 assertCalled: function() { | |
| 59 assertEquals(1, this.callbackCount); | |
| 60 }, | |
| 61 assertRecordCount: function(count) { | |
| 62 this.assertCalled(); | |
| 63 assertEquals(count, this.records.length); | |
| 64 }, | |
| 65 assertCallbackRecords: function(recs) { | |
| 66 this.assertRecordCount(recs.length); | |
| 67 for (var i = 0; i < recs.length; i++) { | |
| 68 if ('name' in recs[i]) recs[i].name = String(recs[i].name); | |
| 69 print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i])); | |
| 70 assertSame(this.records[i].object, recs[i].object); | |
| 71 assertEquals('string', typeof recs[i].type); | |
| 72 assertPropertiesEqual(this.records[i], recs[i]); | |
| 73 } | |
| 74 } | |
| 75 }; | |
| 76 | |
| 77 observer.callback = function(r) { | |
| 78 assertEquals(undefined, this); | |
| 79 assertEquals('object', typeof r); | |
| 80 assertTrue(r instanceof Array) | |
| 81 observer.records = r; | |
| 82 observer.callbackCount++; | |
| 83 }; | |
| 84 | |
| 85 observer.reset(); | |
| 86 allObservers.push(observer); | |
| 87 return observer; | |
| 88 } | |
| 89 | |
| 90 var observer = createObserver(); | |
| 91 var observer2 = createObserver(); | |
| 92 | |
| 93 assertEquals("function", typeof observer.callback); | |
| 94 assertEquals("function", typeof observer2.callback); | |
| 95 | |
| 96 var obj = {}; | |
| 97 | |
| 98 function frozenFunction() {} | |
| 99 Object.freeze(frozenFunction); | |
| 100 var nonFunction = {}; | |
| 101 var changeRecordWithAccessor = { type: 'foo' }; | |
| 102 var recordCreated = false; | |
| 103 Object.defineProperty(changeRecordWithAccessor, 'name', { | |
| 104 get: function() { | |
| 105 recordCreated = true; | |
| 106 return "bar"; | |
| 107 }, | |
| 108 enumerable: true | |
| 109 }) | |
| 110 | |
| 111 | |
| 112 // Object.observe | |
| 113 assertThrows(function() { Object.observe("non-object", observer.callback); }, | |
| 114 TypeError); | |
| 115 assertThrows(function() { Object.observe(obj, nonFunction); }, TypeError); | |
| 116 assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError); | |
| 117 assertEquals(obj, Object.observe(obj, observer.callback, [1])); | |
| 118 assertEquals(obj, Object.observe(obj, observer.callback, [true])); | |
| 119 assertEquals(obj, Object.observe(obj, observer.callback, ['foo', null])); | |
| 120 assertEquals(obj, Object.observe(obj, observer.callback, [undefined])); | |
| 121 assertEquals(obj, Object.observe(obj, observer.callback, | |
| 122 ['foo', 'bar', 'baz'])); | |
| 123 assertEquals(obj, Object.observe(obj, observer.callback, [])); | |
| 124 assertEquals(obj, Object.observe(obj, observer.callback, undefined)); | |
| 125 assertEquals(obj, Object.observe(obj, observer.callback)); | |
| 126 | |
| 127 // Object.unobserve | |
| 128 assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError); | |
| 129 assertThrows(function() { Object.unobserve(obj, nonFunction); }, TypeError); | |
| 130 assertEquals(obj, Object.unobserve(obj, observer.callback)); | |
| 131 | |
| 132 | |
| 133 // Object.getNotifier | |
| 134 var notifier = Object.getNotifier(obj); | |
| 135 assertSame(notifier, Object.getNotifier(obj)); | |
| 136 assertEquals(null, Object.getNotifier(Object.freeze({}))); | |
| 137 assertFalse(notifier.hasOwnProperty('notify')); | |
| 138 assertEquals([], Object.keys(notifier)); | |
| 139 var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify'); | |
| 140 assertTrue(notifyDesc.configurable); | |
| 141 assertTrue(notifyDesc.writable); | |
| 142 assertFalse(notifyDesc.enumerable); | |
| 143 assertThrows(function() { notifier.notify({}); }, TypeError); | |
| 144 assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError); | |
| 145 | |
| 146 assertThrows(function() { notifier.performChange(1, function(){}); }, TypeError)
; | |
| 147 assertThrows(function() { notifier.performChange(undefined, function(){}); }, Ty
peError); | |
| 148 assertThrows(function() { notifier.performChange('foo', undefined); }, TypeError
); | |
| 149 assertThrows(function() { notifier.performChange('foo', 'bar'); }, TypeError); | |
| 150 var global = this; | |
| 151 notifier.performChange('foo', function() { | |
| 152 assertEquals(global, this); | |
| 153 }); | |
| 154 | |
| 155 var notify = notifier.notify; | |
| 156 assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError); | |
| 157 assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError); | |
| 158 assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError); | |
| 159 assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError); | |
| 160 assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError); | |
| 161 assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError); | |
| 162 assertFalse(recordCreated); | |
| 163 notifier.notify(changeRecordWithAccessor); | |
| 164 assertFalse(recordCreated); // not observed yet | |
| 165 | |
| 166 | |
| 167 // Object.deliverChangeRecords | |
| 168 assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError
); | |
| 169 | |
| 170 Object.observe(obj, observer.callback); | |
| 171 | |
| 172 | |
| 173 // notify uses to [[CreateOwnProperty]] to create changeRecord; | |
| 174 reset(); | |
| 175 var protoExpandoAccessed = false; | |
| 176 Object.defineProperty(Object.prototype, 'protoExpando', | |
| 177 { | |
| 178 configurable: true, | |
| 179 set: function() { protoExpandoAccessed = true; } | |
| 180 } | |
| 181 ); | |
| 182 notifier.notify({ type: 'foo', protoExpando: 'val'}); | |
| 183 assertFalse(protoExpandoAccessed); | |
| 184 delete Object.prototype.protoExpando; | |
| 185 Object.deliverChangeRecords(observer.callback); | |
| 186 | |
| 187 | |
| 188 // Multiple records are delivered. | |
| 189 reset(); | |
| 190 notifier.notify({ | |
| 191 type: 'update', | |
| 192 name: 'foo', | |
| 193 expando: 1 | |
| 194 }); | |
| 195 | |
| 196 notifier.notify({ | |
| 197 object: notifier, // object property is ignored | |
| 198 type: 'delete', | |
| 199 name: 'bar', | |
| 200 expando2: 'str' | |
| 201 }); | |
| 202 Object.deliverChangeRecords(observer.callback); | |
| 203 observer.assertCallbackRecords([ | |
| 204 { object: obj, name: 'foo', type: 'update', expando: 1 }, | |
| 205 { object: obj, name: 'bar', type: 'delete', expando2: 'str' } | |
| 206 ]); | |
| 207 | |
| 208 // Non-string accept values are coerced to strings | |
| 209 reset(); | |
| 210 Object.observe(obj, observer.callback, [true, 1, null, undefined]); | |
| 211 notifier = Object.getNotifier(obj); | |
| 212 notifier.notify({ type: 'true' }); | |
| 213 notifier.notify({ type: 'false' }); | |
| 214 notifier.notify({ type: '1' }); | |
| 215 notifier.notify({ type: '-1' }); | |
| 216 notifier.notify({ type: 'null' }); | |
| 217 notifier.notify({ type: 'nill' }); | |
| 218 notifier.notify({ type: 'undefined' }); | |
| 219 notifier.notify({ type: 'defined' }); | |
| 220 Object.deliverChangeRecords(observer.callback); | |
| 221 observer.assertCallbackRecords([ | |
| 222 { object: obj, type: 'true' }, | |
| 223 { object: obj, type: '1' }, | |
| 224 { object: obj, type: 'null' }, | |
| 225 { object: obj, type: 'undefined' } | |
| 226 ]); | |
| 227 | |
| 228 // No delivery takes place if no records are pending | |
| 229 reset(); | |
| 230 Object.deliverChangeRecords(observer.callback); | |
| 231 observer.assertNotCalled(); | |
| 232 | |
| 233 | |
| 234 // Multiple observation has no effect. | |
| 235 reset(); | |
| 236 Object.observe(obj, observer.callback); | |
| 237 Object.observe(obj, observer.callback); | |
| 238 Object.getNotifier(obj).notify({ | |
| 239 type: 'update', | |
| 240 }); | |
| 241 Object.deliverChangeRecords(observer.callback); | |
| 242 observer.assertCalled(); | |
| 243 | |
| 244 | |
| 245 // Observation can be stopped. | |
| 246 reset(); | |
| 247 Object.unobserve(obj, observer.callback); | |
| 248 Object.getNotifier(obj).notify({ | |
| 249 type: 'update', | |
| 250 }); | |
| 251 Object.deliverChangeRecords(observer.callback); | |
| 252 observer.assertNotCalled(); | |
| 253 | |
| 254 | |
| 255 // Multiple unobservation has no effect | |
| 256 reset(); | |
| 257 Object.unobserve(obj, observer.callback); | |
| 258 Object.unobserve(obj, observer.callback); | |
| 259 Object.getNotifier(obj).notify({ | |
| 260 type: 'update', | |
| 261 }); | |
| 262 Object.deliverChangeRecords(observer.callback); | |
| 263 observer.assertNotCalled(); | |
| 264 | |
| 265 | |
| 266 // Re-observation works and only includes changeRecords after of call. | |
| 267 reset(); | |
| 268 Object.getNotifier(obj).notify({ | |
| 269 type: 'update', | |
| 270 }); | |
| 271 Object.observe(obj, observer.callback); | |
| 272 Object.getNotifier(obj).notify({ | |
| 273 type: 'update', | |
| 274 }); | |
| 275 records = undefined; | |
| 276 Object.deliverChangeRecords(observer.callback); | |
| 277 observer.assertRecordCount(1); | |
| 278 | |
| 279 // Get notifier prior to observing | |
| 280 reset(); | |
| 281 var obj = {}; | |
| 282 Object.getNotifier(obj); | |
| 283 Object.observe(obj, observer.callback); | |
| 284 obj.id = 1; | |
| 285 Object.deliverChangeRecords(observer.callback); | |
| 286 observer.assertCallbackRecords([ | |
| 287 { object: obj, type: 'add', name: 'id' }, | |
| 288 ]); | |
| 289 | |
| 290 // The empty-string property is observable | |
| 291 reset(); | |
| 292 var obj = {}; | |
| 293 Object.observe(obj, observer.callback); | |
| 294 obj[''] = ''; | |
| 295 obj[''] = ' '; | |
| 296 delete obj['']; | |
| 297 Object.deliverChangeRecords(observer.callback); | |
| 298 observer.assertCallbackRecords([ | |
| 299 { object: obj, type: 'add', name: '' }, | |
| 300 { object: obj, type: 'update', name: '', oldValue: '' }, | |
| 301 { object: obj, type: 'delete', name: '', oldValue: ' ' }, | |
| 302 ]); | |
| 303 | |
| 304 // Object.preventExtensions | |
| 305 reset(); | |
| 306 var obj = { foo: 'bar'}; | |
| 307 Object.observe(obj, observer.callback); | |
| 308 obj.baz = 'bat'; | |
| 309 Object.preventExtensions(obj); | |
| 310 | |
| 311 Object.deliverChangeRecords(observer.callback); | |
| 312 observer.assertCallbackRecords([ | |
| 313 { object: obj, type: 'add', name: 'baz' }, | |
| 314 { object: obj, type: 'preventExtensions' }, | |
| 315 ]); | |
| 316 | |
| 317 reset(); | |
| 318 var obj = { foo: 'bar'}; | |
| 319 Object.preventExtensions(obj); | |
| 320 Object.observe(obj, observer.callback); | |
| 321 Object.preventExtensions(obj); | |
| 322 Object.deliverChangeRecords(observer.callback); | |
| 323 observer.assertNotCalled(); | |
| 324 | |
| 325 // Object.freeze | |
| 326 reset(); | |
| 327 var obj = { a: 'a' }; | |
| 328 Object.defineProperty(obj, 'b', { | |
| 329 writable: false, | |
| 330 configurable: true, | |
| 331 value: 'b' | |
| 332 }); | |
| 333 Object.defineProperty(obj, 'c', { | |
| 334 writable: true, | |
| 335 configurable: false, | |
| 336 value: 'c' | |
| 337 }); | |
| 338 Object.defineProperty(obj, 'd', { | |
| 339 writable: false, | |
| 340 configurable: false, | |
| 341 value: 'd' | |
| 342 }); | |
| 343 Object.observe(obj, observer.callback); | |
| 344 Object.freeze(obj); | |
| 345 | |
| 346 Object.deliverChangeRecords(observer.callback); | |
| 347 observer.assertCallbackRecords([ | |
| 348 { object: obj, type: 'reconfigure', name: 'a' }, | |
| 349 { object: obj, type: 'reconfigure', name: 'b' }, | |
| 350 { object: obj, type: 'reconfigure', name: 'c' }, | |
| 351 { object: obj, type: 'preventExtensions' }, | |
| 352 ]); | |
| 353 | |
| 354 reset(); | |
| 355 var obj = { foo: 'bar'}; | |
| 356 Object.freeze(obj); | |
| 357 Object.observe(obj, observer.callback); | |
| 358 Object.freeze(obj); | |
| 359 Object.deliverChangeRecords(observer.callback); | |
| 360 observer.assertNotCalled(); | |
| 361 | |
| 362 // Object.seal | |
| 363 reset(); | |
| 364 var obj = { a: 'a' }; | |
| 365 Object.defineProperty(obj, 'b', { | |
| 366 writable: false, | |
| 367 configurable: true, | |
| 368 value: 'b' | |
| 369 }); | |
| 370 Object.defineProperty(obj, 'c', { | |
| 371 writable: true, | |
| 372 configurable: false, | |
| 373 value: 'c' | |
| 374 }); | |
| 375 Object.defineProperty(obj, 'd', { | |
| 376 writable: false, | |
| 377 configurable: false, | |
| 378 value: 'd' | |
| 379 }); | |
| 380 Object.observe(obj, observer.callback); | |
| 381 Object.seal(obj); | |
| 382 | |
| 383 Object.deliverChangeRecords(observer.callback); | |
| 384 observer.assertCallbackRecords([ | |
| 385 { object: obj, type: 'reconfigure', name: 'a' }, | |
| 386 { object: obj, type: 'reconfigure', name: 'b' }, | |
| 387 { object: obj, type: 'preventExtensions' }, | |
| 388 ]); | |
| 389 | |
| 390 reset(); | |
| 391 var obj = { foo: 'bar'}; | |
| 392 Object.seal(obj); | |
| 393 Object.observe(obj, observer.callback); | |
| 394 Object.seal(obj); | |
| 395 Object.deliverChangeRecords(observer.callback); | |
| 396 observer.assertNotCalled(); | |
| 397 | |
| 398 // Observing a continuous stream of changes, while itermittantly unobserving. | |
| 399 reset(); | |
| 400 var obj = {}; | |
| 401 Object.observe(obj, observer.callback); | |
| 402 Object.getNotifier(obj).notify({ | |
| 403 type: 'update', | |
| 404 val: 1 | |
| 405 }); | |
| 406 | |
| 407 Object.unobserve(obj, observer.callback); | |
| 408 Object.getNotifier(obj).notify({ | |
| 409 type: 'update', | |
| 410 val: 2 | |
| 411 }); | |
| 412 | |
| 413 Object.observe(obj, observer.callback); | |
| 414 Object.getNotifier(obj).notify({ | |
| 415 type: 'update', | |
| 416 val: 3 | |
| 417 }); | |
| 418 | |
| 419 Object.unobserve(obj, observer.callback); | |
| 420 Object.getNotifier(obj).notify({ | |
| 421 type: 'update', | |
| 422 val: 4 | |
| 423 }); | |
| 424 | |
| 425 Object.observe(obj, observer.callback); | |
| 426 Object.getNotifier(obj).notify({ | |
| 427 type: 'update', | |
| 428 val: 5 | |
| 429 }); | |
| 430 | |
| 431 Object.unobserve(obj, observer.callback); | |
| 432 Object.deliverChangeRecords(observer.callback); | |
| 433 observer.assertCallbackRecords([ | |
| 434 { object: obj, type: 'update', val: 1 }, | |
| 435 { object: obj, type: 'update', val: 3 }, | |
| 436 { object: obj, type: 'update', val: 5 } | |
| 437 ]); | |
| 438 | |
| 439 // Accept | |
| 440 reset(); | |
| 441 Object.observe(obj, observer.callback, ['somethingElse']); | |
| 442 Object.getNotifier(obj).notify({ | |
| 443 type: 'add' | |
| 444 }); | |
| 445 Object.getNotifier(obj).notify({ | |
| 446 type: 'update' | |
| 447 }); | |
| 448 Object.getNotifier(obj).notify({ | |
| 449 type: 'delete' | |
| 450 }); | |
| 451 Object.getNotifier(obj).notify({ | |
| 452 type: 'reconfigure' | |
| 453 }); | |
| 454 Object.getNotifier(obj).notify({ | |
| 455 type: 'setPrototype' | |
| 456 }); | |
| 457 Object.deliverChangeRecords(observer.callback); | |
| 458 observer.assertNotCalled(); | |
| 459 | |
| 460 reset(); | |
| 461 Object.observe(obj, observer.callback, ['add', 'delete', 'setPrototype']); | |
| 462 Object.getNotifier(obj).notify({ | |
| 463 type: 'add' | |
| 464 }); | |
| 465 Object.getNotifier(obj).notify({ | |
| 466 type: 'update' | |
| 467 }); | |
| 468 Object.getNotifier(obj).notify({ | |
| 469 type: 'delete' | |
| 470 }); | |
| 471 Object.getNotifier(obj).notify({ | |
| 472 type: 'delete' | |
| 473 }); | |
| 474 Object.getNotifier(obj).notify({ | |
| 475 type: 'reconfigure' | |
| 476 }); | |
| 477 Object.getNotifier(obj).notify({ | |
| 478 type: 'setPrototype' | |
| 479 }); | |
| 480 Object.deliverChangeRecords(observer.callback); | |
| 481 observer.assertCallbackRecords([ | |
| 482 { object: obj, type: 'add' }, | |
| 483 { object: obj, type: 'delete' }, | |
| 484 { object: obj, type: 'delete' }, | |
| 485 { object: obj, type: 'setPrototype' } | |
| 486 ]); | |
| 487 | |
| 488 reset(); | |
| 489 Object.observe(obj, observer.callback, ['update', 'foo']); | |
| 490 Object.getNotifier(obj).notify({ | |
| 491 type: 'add' | |
| 492 }); | |
| 493 Object.getNotifier(obj).notify({ | |
| 494 type: 'update' | |
| 495 }); | |
| 496 Object.getNotifier(obj).notify({ | |
| 497 type: 'delete' | |
| 498 }); | |
| 499 Object.getNotifier(obj).notify({ | |
| 500 type: 'foo' | |
| 501 }); | |
| 502 Object.getNotifier(obj).notify({ | |
| 503 type: 'bar' | |
| 504 }); | |
| 505 Object.getNotifier(obj).notify({ | |
| 506 type: 'foo' | |
| 507 }); | |
| 508 Object.deliverChangeRecords(observer.callback); | |
| 509 observer.assertCallbackRecords([ | |
| 510 { object: obj, type: 'update' }, | |
| 511 { object: obj, type: 'foo' }, | |
| 512 { object: obj, type: 'foo' } | |
| 513 ]); | |
| 514 | |
| 515 reset(); | |
| 516 function Thingy(a, b, c) { | |
| 517 this.a = a; | |
| 518 this.b = b; | |
| 519 } | |
| 520 | |
| 521 Thingy.MULTIPLY = 'multiply'; | |
| 522 Thingy.INCREMENT = 'increment'; | |
| 523 Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply'; | |
| 524 | |
| 525 Thingy.prototype = { | |
| 526 increment: function(amount) { | |
| 527 var notifier = Object.getNotifier(this); | |
| 528 | |
| 529 var self = this; | |
| 530 notifier.performChange(Thingy.INCREMENT, function() { | |
| 531 self.a += amount; | |
| 532 self.b += amount; | |
| 533 | |
| 534 return { | |
| 535 incremented: amount | |
| 536 }; // implicit notify | |
| 537 }); | |
| 538 }, | |
| 539 | |
| 540 multiply: function(amount) { | |
| 541 var notifier = Object.getNotifier(this); | |
| 542 | |
| 543 var self = this; | |
| 544 notifier.performChange(Thingy.MULTIPLY, function() { | |
| 545 self.a *= amount; | |
| 546 self.b *= amount; | |
| 547 | |
| 548 return { | |
| 549 multiplied: amount | |
| 550 }; // implicit notify | |
| 551 }); | |
| 552 }, | |
| 553 | |
| 554 incrementAndMultiply: function(incAmount, multAmount) { | |
| 555 var notifier = Object.getNotifier(this); | |
| 556 | |
| 557 var self = this; | |
| 558 notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() { | |
| 559 self.increment(incAmount); | |
| 560 self.multiply(multAmount); | |
| 561 | |
| 562 return { | |
| 563 incremented: incAmount, | |
| 564 multiplied: multAmount | |
| 565 }; // implicit notify | |
| 566 }); | |
| 567 } | |
| 568 } | |
| 569 | |
| 570 Thingy.observe = function(thingy, callback) { | |
| 571 Object.observe(thingy, callback, [Thingy.INCREMENT, | |
| 572 Thingy.MULTIPLY, | |
| 573 Thingy.INCREMENT_AND_MULTIPLY, | |
| 574 'update']); | |
| 575 } | |
| 576 | |
| 577 Thingy.unobserve = function(thingy, callback) { | |
| 578 Object.unobserve(thingy); | |
| 579 } | |
| 580 | |
| 581 var thingy = new Thingy(2, 4); | |
| 582 | |
| 583 Object.observe(thingy, observer.callback); | |
| 584 Thingy.observe(thingy, observer2.callback); | |
| 585 thingy.increment(3); // { a: 5, b: 7 } | |
| 586 thingy.b++; // { a: 5, b: 8 } | |
| 587 thingy.multiply(2); // { a: 10, b: 16 } | |
| 588 thingy.a++; // { a: 11, b: 16 } | |
| 589 thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 } | |
| 590 | |
| 591 Object.deliverChangeRecords(observer.callback); | |
| 592 Object.deliverChangeRecords(observer2.callback); | |
| 593 observer.assertCallbackRecords([ | |
| 594 { object: thingy, type: 'update', name: 'a', oldValue: 2 }, | |
| 595 { object: thingy, type: 'update', name: 'b', oldValue: 4 }, | |
| 596 { object: thingy, type: 'update', name: 'b', oldValue: 7 }, | |
| 597 { object: thingy, type: 'update', name: 'a', oldValue: 5 }, | |
| 598 { object: thingy, type: 'update', name: 'b', oldValue: 8 }, | |
| 599 { object: thingy, type: 'update', name: 'a', oldValue: 10 }, | |
| 600 { object: thingy, type: 'update', name: 'a', oldValue: 11 }, | |
| 601 { object: thingy, type: 'update', name: 'b', oldValue: 16 }, | |
| 602 { object: thingy, type: 'update', name: 'a', oldValue: 13 }, | |
| 603 { object: thingy, type: 'update', name: 'b', oldValue: 18 }, | |
| 604 ]); | |
| 605 observer2.assertCallbackRecords([ | |
| 606 { object: thingy, type: Thingy.INCREMENT, incremented: 3 }, | |
| 607 { object: thingy, type: 'update', name: 'b', oldValue: 7 }, | |
| 608 { object: thingy, type: Thingy.MULTIPLY, multiplied: 2 }, | |
| 609 { object: thingy, type: 'update', name: 'a', oldValue: 10 }, | |
| 610 { | |
| 611 object: thingy, | |
| 612 type: Thingy.INCREMENT_AND_MULTIPLY, | |
| 613 incremented: 2, | |
| 614 multiplied: 2 | |
| 615 } | |
| 616 ]); | |
| 617 | |
| 618 // ArrayPush cached stub | |
| 619 reset(); | |
| 620 | |
| 621 function pushMultiple(arr) { | |
| 622 arr.push('a'); | |
| 623 arr.push('b'); | |
| 624 arr.push('c'); | |
| 625 } | |
| 626 | |
| 627 for (var i = 0; i < 5; i++) { | |
| 628 var arr = []; | |
| 629 pushMultiple(arr); | |
| 630 } | |
| 631 | |
| 632 for (var i = 0; i < 5; i++) { | |
| 633 reset(); | |
| 634 var arr = []; | |
| 635 Object.observe(arr, observer.callback); | |
| 636 pushMultiple(arr); | |
| 637 Object.unobserve(arr, observer.callback); | |
| 638 Object.deliverChangeRecords(observer.callback); | |
| 639 observer.assertCallbackRecords([ | |
| 640 { object: arr, type: 'add', name: '0' }, | |
| 641 { object: arr, type: 'update', name: 'length', oldValue: 0 }, | |
| 642 { object: arr, type: 'add', name: '1' }, | |
| 643 { object: arr, type: 'update', name: 'length', oldValue: 1 }, | |
| 644 { object: arr, type: 'add', name: '2' }, | |
| 645 { object: arr, type: 'update', name: 'length', oldValue: 2 }, | |
| 646 ]); | |
| 647 } | |
| 648 | |
| 649 | |
| 650 // ArrayPop cached stub | |
| 651 reset(); | |
| 652 | |
| 653 function popMultiple(arr) { | |
| 654 arr.pop(); | |
| 655 arr.pop(); | |
| 656 arr.pop(); | |
| 657 } | |
| 658 | |
| 659 for (var i = 0; i < 5; i++) { | |
| 660 var arr = ['a', 'b', 'c']; | |
| 661 popMultiple(arr); | |
| 662 } | |
| 663 | |
| 664 for (var i = 0; i < 5; i++) { | |
| 665 reset(); | |
| 666 var arr = ['a', 'b', 'c']; | |
| 667 Object.observe(arr, observer.callback); | |
| 668 popMultiple(arr); | |
| 669 Object.unobserve(arr, observer.callback); | |
| 670 Object.deliverChangeRecords(observer.callback); | |
| 671 observer.assertCallbackRecords([ | |
| 672 { object: arr, type: 'delete', name: '2', oldValue: 'c' }, | |
| 673 { object: arr, type: 'update', name: 'length', oldValue: 3 }, | |
| 674 { object: arr, type: 'delete', name: '1', oldValue: 'b' }, | |
| 675 { object: arr, type: 'update', name: 'length', oldValue: 2 }, | |
| 676 { object: arr, type: 'delete', name: '0', oldValue: 'a' }, | |
| 677 { object: arr, type: 'update', name: 'length', oldValue: 1 }, | |
| 678 ]); | |
| 679 } | |
| 680 | |
| 681 | |
| 682 reset(); | |
| 683 function RecursiveThingy() {} | |
| 684 | |
| 685 RecursiveThingy.MULTIPLY_FIRST_N = 'multiplyFirstN'; | |
| 686 | |
| 687 RecursiveThingy.prototype = { | |
| 688 __proto__: Array.prototype, | |
| 689 | |
| 690 multiplyFirstN: function(amount, n) { | |
| 691 if (!n) | |
| 692 return; | |
| 693 var notifier = Object.getNotifier(this); | |
| 694 var self = this; | |
| 695 notifier.performChange(RecursiveThingy.MULTIPLY_FIRST_N, function() { | |
| 696 self[n-1] = self[n-1]*amount; | |
| 697 self.multiplyFirstN(amount, n-1); | |
| 698 }); | |
| 699 | |
| 700 notifier.notify({ | |
| 701 type: RecursiveThingy.MULTIPLY_FIRST_N, | |
| 702 multiplied: amount, | |
| 703 n: n | |
| 704 }); | |
| 705 }, | |
| 706 } | |
| 707 | |
| 708 RecursiveThingy.observe = function(thingy, callback) { | |
| 709 Object.observe(thingy, callback, [RecursiveThingy.MULTIPLY_FIRST_N]); | |
| 710 } | |
| 711 | |
| 712 RecursiveThingy.unobserve = function(thingy, callback) { | |
| 713 Object.unobserve(thingy); | |
| 714 } | |
| 715 | |
| 716 var thingy = new RecursiveThingy; | |
| 717 thingy.push(1, 2, 3, 4); | |
| 718 | |
| 719 Object.observe(thingy, observer.callback); | |
| 720 RecursiveThingy.observe(thingy, observer2.callback); | |
| 721 thingy.multiplyFirstN(2, 3); // [2, 4, 6, 4] | |
| 722 | |
| 723 Object.deliverChangeRecords(observer.callback); | |
| 724 Object.deliverChangeRecords(observer2.callback); | |
| 725 observer.assertCallbackRecords([ | |
| 726 { object: thingy, type: 'update', name: '2', oldValue: 3 }, | |
| 727 { object: thingy, type: 'update', name: '1', oldValue: 2 }, | |
| 728 { object: thingy, type: 'update', name: '0', oldValue: 1 } | |
| 729 ]); | |
| 730 observer2.assertCallbackRecords([ | |
| 731 { object: thingy, type: RecursiveThingy.MULTIPLY_FIRST_N, multiplied: 2, n: 3
} | |
| 732 ]); | |
| 733 | |
| 734 reset(); | |
| 735 function DeckSuit() { | |
| 736 this.push('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'Q', 'K'); | |
| 737 } | |
| 738 | |
| 739 DeckSuit.SHUFFLE = 'shuffle'; | |
| 740 | |
| 741 DeckSuit.prototype = { | |
| 742 __proto__: Array.prototype, | |
| 743 | |
| 744 shuffle: function() { | |
| 745 var notifier = Object.getNotifier(this); | |
| 746 var self = this; | |
| 747 notifier.performChange(DeckSuit.SHUFFLE, function() { | |
| 748 self.reverse(); | |
| 749 self.sort(function() { return Math.random()* 2 - 1; }); | |
| 750 var cut = self.splice(0, 6); | |
| 751 Array.prototype.push.apply(self, cut); | |
| 752 self.reverse(); | |
| 753 self.sort(function() { return Math.random()* 2 - 1; }); | |
| 754 var cut = self.splice(0, 6); | |
| 755 Array.prototype.push.apply(self, cut); | |
| 756 self.reverse(); | |
| 757 self.sort(function() { return Math.random()* 2 - 1; }); | |
| 758 }); | |
| 759 | |
| 760 notifier.notify({ | |
| 761 type: DeckSuit.SHUFFLE | |
| 762 }); | |
| 763 }, | |
| 764 } | |
| 765 | |
| 766 DeckSuit.observe = function(thingy, callback) { | |
| 767 Object.observe(thingy, callback, [DeckSuit.SHUFFLE]); | |
| 768 } | |
| 769 | |
| 770 DeckSuit.unobserve = function(thingy, callback) { | |
| 771 Object.unobserve(thingy); | |
| 772 } | |
| 773 | |
| 774 var deck = new DeckSuit; | |
| 775 | |
| 776 DeckSuit.observe(deck, observer2.callback); | |
| 777 deck.shuffle(); | |
| 778 | |
| 779 Object.deliverChangeRecords(observer2.callback); | |
| 780 observer2.assertCallbackRecords([ | |
| 781 { object: deck, type: DeckSuit.SHUFFLE } | |
| 782 ]); | |
| 783 | |
| 784 // Observing multiple objects; records appear in order. | |
| 785 reset(); | |
| 786 var obj2 = {}; | |
| 787 var obj3 = {} | |
| 788 Object.observe(obj, observer.callback); | |
| 789 Object.observe(obj3, observer.callback); | |
| 790 Object.observe(obj2, observer.callback); | |
| 791 Object.getNotifier(obj).notify({ | |
| 792 type: 'add', | |
| 793 }); | |
| 794 Object.getNotifier(obj2).notify({ | |
| 795 type: 'update', | |
| 796 }); | |
| 797 Object.getNotifier(obj3).notify({ | |
| 798 type: 'delete', | |
| 799 }); | |
| 800 Object.observe(obj3, observer.callback); | |
| 801 Object.deliverChangeRecords(observer.callback); | |
| 802 observer.assertCallbackRecords([ | |
| 803 { object: obj, type: 'add' }, | |
| 804 { object: obj2, type: 'update' }, | |
| 805 { object: obj3, type: 'delete' } | |
| 806 ]); | |
| 807 | |
| 808 | |
| 809 // Recursive observation. | |
| 810 var obj = {a: 1}; | |
| 811 var callbackCount = 0; | |
| 812 function recursiveObserver(r) { | |
| 813 assertEquals(1, r.length); | |
| 814 ++callbackCount; | |
| 815 if (r[0].oldValue < 100) ++obj[r[0].name]; | |
| 816 } | |
| 817 Object.observe(obj, recursiveObserver); | |
| 818 ++obj.a; | |
| 819 Object.deliverChangeRecords(recursiveObserver); | |
| 820 assertEquals(100, callbackCount); | |
| 821 | |
| 822 var obj1 = {a: 1}; | |
| 823 var obj2 = {a: 1}; | |
| 824 var recordCount = 0; | |
| 825 function recursiveObserver2(r) { | |
| 826 recordCount += r.length; | |
| 827 if (r[0].oldValue < 100) { | |
| 828 ++obj1.a; | |
| 829 ++obj2.a; | |
| 830 } | |
| 831 } | |
| 832 Object.observe(obj1, recursiveObserver2); | |
| 833 Object.observe(obj2, recursiveObserver2); | |
| 834 ++obj1.a; | |
| 835 Object.deliverChangeRecords(recursiveObserver2); | |
| 836 assertEquals(199, recordCount); | |
| 837 | |
| 838 | |
| 839 // Observing named properties. | |
| 840 reset(); | |
| 841 var obj = {a: 1} | |
| 842 Object.observe(obj, observer.callback); | |
| 843 obj.a = 2; | |
| 844 obj["a"] = 3; | |
| 845 delete obj.a; | |
| 846 obj.a = 4; | |
| 847 obj.a = 4; // ignored | |
| 848 obj.a = 5; | |
| 849 Object.defineProperty(obj, "a", {value: 6}); | |
| 850 Object.defineProperty(obj, "a", {writable: false}); | |
| 851 obj.a = 7; // ignored | |
| 852 Object.defineProperty(obj, "a", {value: 8}); | |
| 853 Object.defineProperty(obj, "a", {value: 7, writable: true}); | |
| 854 Object.defineProperty(obj, "a", {get: function() {}}); | |
| 855 Object.defineProperty(obj, "a", {get: frozenFunction}); | |
| 856 Object.defineProperty(obj, "a", {get: frozenFunction}); // ignored | |
| 857 Object.defineProperty(obj, "a", {get: frozenFunction, set: frozenFunction}); | |
| 858 Object.defineProperty(obj, "a", {set: frozenFunction}); // ignored | |
| 859 Object.defineProperty(obj, "a", {get: undefined, set: frozenFunction}); | |
| 860 delete obj.a; | |
| 861 delete obj.a; | |
| 862 Object.defineProperty(obj, "a", {get: function() {}, configurable: true}); | |
| 863 Object.defineProperty(obj, "a", {value: 9, writable: true}); | |
| 864 obj.a = 10; | |
| 865 ++obj.a; | |
| 866 obj.a++; | |
| 867 obj.a *= 3; | |
| 868 delete obj.a; | |
| 869 Object.defineProperty(obj, "a", {value: 11, configurable: true}); | |
| 870 Object.deliverChangeRecords(observer.callback); | |
| 871 observer.assertCallbackRecords([ | |
| 872 { object: obj, name: "a", type: "update", oldValue: 1 }, | |
| 873 { object: obj, name: "a", type: "update", oldValue: 2 }, | |
| 874 { object: obj, name: "a", type: "delete", oldValue: 3 }, | |
| 875 { object: obj, name: "a", type: "add" }, | |
| 876 { object: obj, name: "a", type: "update", oldValue: 4 }, | |
| 877 { object: obj, name: "a", type: "update", oldValue: 5 }, | |
| 878 { object: obj, name: "a", type: "reconfigure" }, | |
| 879 { object: obj, name: "a", type: "update", oldValue: 6 }, | |
| 880 { object: obj, name: "a", type: "reconfigure", oldValue: 8 }, | |
| 881 { object: obj, name: "a", type: "reconfigure", oldValue: 7 }, | |
| 882 { object: obj, name: "a", type: "reconfigure" }, | |
| 883 { object: obj, name: "a", type: "reconfigure" }, | |
| 884 { object: obj, name: "a", type: "reconfigure" }, | |
| 885 { object: obj, name: "a", type: "delete" }, | |
| 886 { object: obj, name: "a", type: "add" }, | |
| 887 { object: obj, name: "a", type: "reconfigure" }, | |
| 888 { object: obj, name: "a", type: "update", oldValue: 9 }, | |
| 889 { object: obj, name: "a", type: "update", oldValue: 10 }, | |
| 890 { object: obj, name: "a", type: "update", oldValue: 11 }, | |
| 891 { object: obj, name: "a", type: "update", oldValue: 12 }, | |
| 892 { object: obj, name: "a", type: "delete", oldValue: 36 }, | |
| 893 { object: obj, name: "a", type: "add" }, | |
| 894 ]); | |
| 895 | |
| 896 | |
| 897 // Observing indexed properties. | |
| 898 reset(); | |
| 899 var obj = {'1': 1} | |
| 900 Object.observe(obj, observer.callback); | |
| 901 obj[1] = 2; | |
| 902 obj[1] = 3; | |
| 903 delete obj[1]; | |
| 904 obj[1] = 4; | |
| 905 obj[1] = 4; // ignored | |
| 906 obj[1] = 5; | |
| 907 Object.defineProperty(obj, "1", {value: 6}); | |
| 908 Object.defineProperty(obj, "1", {writable: false}); | |
| 909 obj[1] = 7; // ignored | |
| 910 Object.defineProperty(obj, "1", {value: 8}); | |
| 911 Object.defineProperty(obj, "1", {value: 7, writable: true}); | |
| 912 Object.defineProperty(obj, "1", {get: function() {}}); | |
| 913 Object.defineProperty(obj, "1", {get: frozenFunction}); | |
| 914 Object.defineProperty(obj, "1", {get: frozenFunction}); // ignored | |
| 915 Object.defineProperty(obj, "1", {get: frozenFunction, set: frozenFunction}); | |
| 916 Object.defineProperty(obj, "1", {set: frozenFunction}); // ignored | |
| 917 Object.defineProperty(obj, "1", {get: undefined, set: frozenFunction}); | |
| 918 delete obj[1]; | |
| 919 delete obj[1]; | |
| 920 Object.defineProperty(obj, "1", {get: function() {}, configurable: true}); | |
| 921 Object.defineProperty(obj, "1", {value: 9, writable: true}); | |
| 922 obj[1] = 10; | |
| 923 ++obj[1]; | |
| 924 obj[1]++; | |
| 925 obj[1] *= 3; | |
| 926 delete obj[1]; | |
| 927 Object.defineProperty(obj, "1", {value: 11, configurable: true}); | |
| 928 Object.deliverChangeRecords(observer.callback); | |
| 929 observer.assertCallbackRecords([ | |
| 930 { object: obj, name: "1", type: "update", oldValue: 1 }, | |
| 931 { object: obj, name: "1", type: "update", oldValue: 2 }, | |
| 932 { object: obj, name: "1", type: "delete", oldValue: 3 }, | |
| 933 { object: obj, name: "1", type: "add" }, | |
| 934 { object: obj, name: "1", type: "update", oldValue: 4 }, | |
| 935 { object: obj, name: "1", type: "update", oldValue: 5 }, | |
| 936 { object: obj, name: "1", type: "reconfigure" }, | |
| 937 { object: obj, name: "1", type: "update", oldValue: 6 }, | |
| 938 { object: obj, name: "1", type: "reconfigure", oldValue: 8 }, | |
| 939 { object: obj, name: "1", type: "reconfigure", oldValue: 7 }, | |
| 940 { object: obj, name: "1", type: "reconfigure" }, | |
| 941 { object: obj, name: "1", type: "reconfigure" }, | |
| 942 { object: obj, name: "1", type: "reconfigure" }, | |
| 943 { object: obj, name: "1", type: "delete" }, | |
| 944 { object: obj, name: "1", type: "add" }, | |
| 945 { object: obj, name: "1", type: "reconfigure" }, | |
| 946 { object: obj, name: "1", type: "update", oldValue: 9 }, | |
| 947 { object: obj, name: "1", type: "update", oldValue: 10 }, | |
| 948 { object: obj, name: "1", type: "update", oldValue: 11 }, | |
| 949 { object: obj, name: "1", type: "update", oldValue: 12 }, | |
| 950 { object: obj, name: "1", type: "delete", oldValue: 36 }, | |
| 951 { object: obj, name: "1", type: "add" }, | |
| 952 ]); | |
| 953 | |
| 954 | |
| 955 // Observing symbol properties (not). | |
| 956 print("*****") | |
| 957 reset(); | |
| 958 var obj = {} | |
| 959 var symbol = Symbol("secret"); | |
| 960 Object.observe(obj, observer.callback); | |
| 961 obj[symbol] = 3; | |
| 962 delete obj[symbol]; | |
| 963 Object.defineProperty(obj, symbol, {get: function() {}, configurable: true}); | |
| 964 Object.defineProperty(obj, symbol, {value: 6}); | |
| 965 Object.defineProperty(obj, symbol, {writable: false}); | |
| 966 delete obj[symbol]; | |
| 967 Object.defineProperty(obj, symbol, {value: 7}); | |
| 968 ++obj[symbol]; | |
| 969 obj[symbol]++; | |
| 970 obj[symbol] *= 3; | |
| 971 delete obj[symbol]; | |
| 972 obj.__defineSetter__(symbol, function() {}); | |
| 973 obj.__defineGetter__(symbol, function() {}); | |
| 974 Object.deliverChangeRecords(observer.callback); | |
| 975 observer.assertNotCalled(); | |
| 976 | |
| 977 | |
| 978 // Test all kinds of objects generically. | |
| 979 function TestObserveConfigurable(obj, prop) { | |
| 980 reset(); | |
| 981 Object.observe(obj, observer.callback); | |
| 982 Object.unobserve(obj, observer.callback); | |
| 983 obj[prop] = 1; | |
| 984 Object.observe(obj, observer.callback); | |
| 985 obj[prop] = 2; | |
| 986 obj[prop] = 3; | |
| 987 delete obj[prop]; | |
| 988 obj[prop] = 4; | |
| 989 obj[prop] = 4; // ignored | |
| 990 obj[prop] = 5; | |
| 991 Object.defineProperty(obj, prop, {value: 6}); | |
| 992 Object.defineProperty(obj, prop, {writable: false}); | |
| 993 obj[prop] = 7; // ignored | |
| 994 Object.defineProperty(obj, prop, {value: 8}); | |
| 995 Object.defineProperty(obj, prop, {value: 7, writable: true}); | |
| 996 Object.defineProperty(obj, prop, {get: function() {}}); | |
| 997 Object.defineProperty(obj, prop, {get: frozenFunction}); | |
| 998 Object.defineProperty(obj, prop, {get: frozenFunction}); // ignored | |
| 999 Object.defineProperty(obj, prop, {get: frozenFunction, set: frozenFunction}); | |
| 1000 Object.defineProperty(obj, prop, {set: frozenFunction}); // ignored | |
| 1001 Object.defineProperty(obj, prop, {get: undefined, set: frozenFunction}); | |
| 1002 obj.__defineSetter__(prop, frozenFunction); // ignored | |
| 1003 obj.__defineSetter__(prop, function() {}); | |
| 1004 obj.__defineGetter__(prop, function() {}); | |
| 1005 delete obj[prop]; | |
| 1006 delete obj[prop]; // ignored | |
| 1007 obj.__defineGetter__(prop, function() {}); | |
| 1008 delete obj[prop]; | |
| 1009 Object.defineProperty(obj, prop, {get: function() {}, configurable: true}); | |
| 1010 Object.defineProperty(obj, prop, {value: 9, writable: true}); | |
| 1011 obj[prop] = 10; | |
| 1012 ++obj[prop]; | |
| 1013 obj[prop]++; | |
| 1014 obj[prop] *= 3; | |
| 1015 delete obj[prop]; | |
| 1016 Object.defineProperty(obj, prop, {value: 11, configurable: true}); | |
| 1017 Object.deliverChangeRecords(observer.callback); | |
| 1018 observer.assertCallbackRecords([ | |
| 1019 { object: obj, name: prop, type: "update", oldValue: 1 }, | |
| 1020 { object: obj, name: prop, type: "update", oldValue: 2 }, | |
| 1021 { object: obj, name: prop, type: "delete", oldValue: 3 }, | |
| 1022 { object: obj, name: prop, type: "add" }, | |
| 1023 { object: obj, name: prop, type: "update", oldValue: 4 }, | |
| 1024 { object: obj, name: prop, type: "update", oldValue: 5 }, | |
| 1025 { object: obj, name: prop, type: "reconfigure" }, | |
| 1026 { object: obj, name: prop, type: "update", oldValue: 6 }, | |
| 1027 { object: obj, name: prop, type: "reconfigure", oldValue: 8 }, | |
| 1028 { object: obj, name: prop, type: "reconfigure", oldValue: 7 }, | |
| 1029 { object: obj, name: prop, type: "reconfigure" }, | |
| 1030 { object: obj, name: prop, type: "reconfigure" }, | |
| 1031 { object: obj, name: prop, type: "reconfigure" }, | |
| 1032 { object: obj, name: prop, type: "reconfigure" }, | |
| 1033 { object: obj, name: prop, type: "reconfigure" }, | |
| 1034 { object: obj, name: prop, type: "delete" }, | |
| 1035 { object: obj, name: prop, type: "add" }, | |
| 1036 { object: obj, name: prop, type: "delete" }, | |
| 1037 { object: obj, name: prop, type: "add" }, | |
| 1038 { object: obj, name: prop, type: "reconfigure" }, | |
| 1039 { object: obj, name: prop, type: "update", oldValue: 9 }, | |
| 1040 { object: obj, name: prop, type: "update", oldValue: 10 }, | |
| 1041 { object: obj, name: prop, type: "update", oldValue: 11 }, | |
| 1042 { object: obj, name: prop, type: "update", oldValue: 12 }, | |
| 1043 { object: obj, name: prop, type: "delete", oldValue: 36 }, | |
| 1044 { object: obj, name: prop, type: "add" }, | |
| 1045 ]); | |
| 1046 Object.unobserve(obj, observer.callback); | |
| 1047 delete obj[prop]; | |
| 1048 } | |
| 1049 | |
| 1050 function TestObserveNonConfigurable(obj, prop, desc) { | |
| 1051 reset(); | |
| 1052 Object.observe(obj, observer.callback); | |
| 1053 Object.unobserve(obj, observer.callback); | |
| 1054 obj[prop] = 1; | |
| 1055 Object.observe(obj, observer.callback); | |
| 1056 obj[prop] = 4; | |
| 1057 obj[prop] = 4; // ignored | |
| 1058 obj[prop] = 5; | |
| 1059 Object.defineProperty(obj, prop, {value: 6}); | |
| 1060 Object.defineProperty(obj, prop, {value: 6}); // ignored | |
| 1061 Object.defineProperty(obj, prop, {value: 7}); | |
| 1062 Object.defineProperty(obj, prop, {enumerable: desc.enumerable}); // ignored | |
| 1063 Object.defineProperty(obj, prop, {writable: false}); | |
| 1064 obj[prop] = 7; // ignored | |
| 1065 Object.deliverChangeRecords(observer.callback); | |
| 1066 observer.assertCallbackRecords([ | |
| 1067 { object: obj, name: prop, type: "update", oldValue: 1 }, | |
| 1068 { object: obj, name: prop, type: "update", oldValue: 4 }, | |
| 1069 { object: obj, name: prop, type: "update", oldValue: 5 }, | |
| 1070 { object: obj, name: prop, type: "update", oldValue: 6 }, | |
| 1071 { object: obj, name: prop, type: "reconfigure" }, | |
| 1072 ]); | |
| 1073 Object.unobserve(obj, observer.callback); | |
| 1074 } | |
| 1075 | |
| 1076 function createProxy(create, x) { | |
| 1077 var handler = { | |
| 1078 getPropertyDescriptor: function(k) { | |
| 1079 for (var o = this.target; o; o = Object.getPrototypeOf(o)) { | |
| 1080 var desc = Object.getOwnPropertyDescriptor(o, k); | |
| 1081 if (desc) return desc; | |
| 1082 } | |
| 1083 return undefined; | |
| 1084 }, | |
| 1085 getOwnPropertyDescriptor: function(k) { | |
| 1086 return Object.getOwnPropertyDescriptor(this.target, k); | |
| 1087 }, | |
| 1088 defineProperty: function(k, desc) { | |
| 1089 var x = Object.defineProperty(this.target, k, desc); | |
| 1090 Object.deliverChangeRecords(this.callback); | |
| 1091 return x; | |
| 1092 }, | |
| 1093 delete: function(k) { | |
| 1094 var x = delete this.target[k]; | |
| 1095 Object.deliverChangeRecords(this.callback); | |
| 1096 return x; | |
| 1097 }, | |
| 1098 getPropertyNames: function() { | |
| 1099 return Object.getOwnPropertyNames(this.target); | |
| 1100 }, | |
| 1101 target: {isProxy: true}, | |
| 1102 callback: function(changeRecords) { | |
| 1103 print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got)); | |
| 1104 for (var i in changeRecords) { | |
| 1105 var got = changeRecords[i]; | |
| 1106 var change = {object: handler.proxy, name: got.name, type: got.type}; | |
| 1107 if ("oldValue" in got) change.oldValue = got.oldValue; | |
| 1108 Object.getNotifier(handler.proxy).notify(change); | |
| 1109 } | |
| 1110 }, | |
| 1111 }; | |
| 1112 Object.observe(handler.target, handler.callback); | |
| 1113 return handler.proxy = create(handler, x); | |
| 1114 } | |
| 1115 | |
| 1116 var objects = [ | |
| 1117 {}, | |
| 1118 [], | |
| 1119 this, // global object | |
| 1120 function(){}, | |
| 1121 (function(){ return arguments })(), | |
| 1122 (function(){ "use strict"; return arguments })(), | |
| 1123 Object(1), Object(true), Object("bla"), | |
| 1124 new Date(), | |
| 1125 Object, Function, Date, RegExp, | |
| 1126 new Set, new Map, new WeakMap, | |
| 1127 new ArrayBuffer(10), new Int32Array(5), | |
| 1128 createProxy(Proxy.create, null), | |
| 1129 createProxy(Proxy.createFunction, function(){}), | |
| 1130 ]; | |
| 1131 var properties = ["a", "1", 1, "length", "setPrototype", "name", "caller"]; | |
| 1132 | |
| 1133 // Cases that yield non-standard results. | |
| 1134 function blacklisted(obj, prop) { | |
| 1135 return (obj instanceof Int32Array && prop == 1) || | |
| 1136 (obj instanceof Int32Array && prop === "length") || | |
| 1137 (obj instanceof ArrayBuffer && prop == 1) | |
| 1138 } | |
| 1139 | |
| 1140 for (var i in objects) for (var j in properties) { | |
| 1141 var obj = objects[i]; | |
| 1142 var prop = properties[j]; | |
| 1143 if (blacklisted(obj, prop)) continue; | |
| 1144 var desc = Object.getOwnPropertyDescriptor(obj, prop); | |
| 1145 print("***", typeof obj, stringifyNoThrow(obj), prop); | |
| 1146 if (!desc || desc.configurable) | |
| 1147 TestObserveConfigurable(obj, prop); | |
| 1148 else if (desc.writable) | |
| 1149 TestObserveNonConfigurable(obj, prop, desc); | |
| 1150 } | |
| 1151 | |
| 1152 | |
| 1153 // Observing array length (including truncation) | |
| 1154 reset(); | |
| 1155 var arr = ['a', 'b', 'c', 'd']; | |
| 1156 var arr2 = ['alpha', 'beta']; | |
| 1157 var arr3 = ['hello']; | |
| 1158 arr3[2] = 'goodbye'; | |
| 1159 arr3.length = 6; | |
| 1160 Object.defineProperty(arr, '0', {configurable: false}); | |
| 1161 Object.defineProperty(arr, '2', {get: function(){}}); | |
| 1162 Object.defineProperty(arr2, '0', {get: function(){}, configurable: false}); | |
| 1163 Object.observe(arr, observer.callback); | |
| 1164 Array.observe(arr, observer2.callback); | |
| 1165 Object.observe(arr2, observer.callback); | |
| 1166 Array.observe(arr2, observer2.callback); | |
| 1167 Object.observe(arr3, observer.callback); | |
| 1168 Array.observe(arr3, observer2.callback); | |
| 1169 arr.length = 2; | |
| 1170 arr.length = 0; | |
| 1171 arr.length = 10; | |
| 1172 Object.defineProperty(arr, 'length', {writable: false}); | |
| 1173 arr2.length = 0; | |
| 1174 arr2.length = 1; // no change expected | |
| 1175 Object.defineProperty(arr2, 'length', {value: 1, writable: false}); | |
| 1176 arr3.length = 0; | |
| 1177 ++arr3.length; | |
| 1178 arr3.length++; | |
| 1179 arr3.length /= 2; | |
| 1180 Object.defineProperty(arr3, 'length', {value: 5}); | |
| 1181 arr3[4] = 5; | |
| 1182 Object.defineProperty(arr3, 'length', {value: 1, writable: false}); | |
| 1183 Object.deliverChangeRecords(observer.callback); | |
| 1184 observer.assertCallbackRecords([ | |
| 1185 { object: arr, name: '3', type: 'delete', oldValue: 'd' }, | |
| 1186 { object: arr, name: '2', type: 'delete' }, | |
| 1187 { object: arr, name: 'length', type: 'update', oldValue: 4 }, | |
| 1188 { object: arr, name: '1', type: 'delete', oldValue: 'b' }, | |
| 1189 { object: arr, name: 'length', type: 'update', oldValue: 2 }, | |
| 1190 { object: arr, name: 'length', type: 'update', oldValue: 1 }, | |
| 1191 { object: arr, name: 'length', type: 'reconfigure' }, | |
| 1192 { object: arr2, name: '1', type: 'delete', oldValue: 'beta' }, | |
| 1193 { object: arr2, name: 'length', type: 'update', oldValue: 2 }, | |
| 1194 { object: arr2, name: 'length', type: 'reconfigure' }, | |
| 1195 { object: arr3, name: '2', type: 'delete', oldValue: 'goodbye' }, | |
| 1196 { object: arr3, name: '0', type: 'delete', oldValue: 'hello' }, | |
| 1197 { object: arr3, name: 'length', type: 'update', oldValue: 6 }, | |
| 1198 { object: arr3, name: 'length', type: 'update', oldValue: 0 }, | |
| 1199 { object: arr3, name: 'length', type: 'update', oldValue: 1 }, | |
| 1200 { object: arr3, name: 'length', type: 'update', oldValue: 2 }, | |
| 1201 { object: arr3, name: 'length', type: 'update', oldValue: 1 }, | |
| 1202 { object: arr3, name: '4', type: 'add' }, | |
| 1203 { object: arr3, name: '4', type: 'delete', oldValue: 5 }, | |
| 1204 // TODO(rafaelw): It breaks spec compliance to get two records here. | |
| 1205 // When the TODO in v8natives.js::DefineArrayProperty is addressed | |
| 1206 // which prevents DefineProperty from over-writing the magic length | |
| 1207 // property, these will collapse into a single record. | |
| 1208 { object: arr3, name: 'length', type: 'update', oldValue: 5 }, | |
| 1209 { object: arr3, name: 'length', type: 'reconfigure' } | |
| 1210 ]); | |
| 1211 Object.deliverChangeRecords(observer2.callback); | |
| 1212 observer2.assertCallbackRecords([ | |
| 1213 { object: arr, type: 'splice', index: 2, removed: [, 'd'], addedCount: 0 }, | |
| 1214 { object: arr, type: 'splice', index: 1, removed: ['b'], addedCount: 0 }, | |
| 1215 { object: arr, type: 'splice', index: 1, removed: [], addedCount: 9 }, | |
| 1216 { object: arr2, type: 'splice', index: 1, removed: ['beta'], addedCount: 0 }, | |
| 1217 { object: arr3, type: 'splice', index: 0, removed: ['hello',, 'goodbye',,,,],
addedCount: 0 }, | |
| 1218 { object: arr3, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
| 1219 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 1 }, | |
| 1220 { object: arr3, type: 'splice', index: 1, removed: [,], addedCount: 0 }, | |
| 1221 { object: arr3, type: 'splice', index: 1, removed: [], addedCount: 4 }, | |
| 1222 { object: arr3, name: '4', type: 'add' }, | |
| 1223 { object: arr3, type: 'splice', index: 1, removed: [,,,5], addedCount: 0 } | |
| 1224 ]); | |
| 1225 | |
| 1226 | |
| 1227 // Updating length on large (slow) array | |
| 1228 reset(); | |
| 1229 var slow_arr = new Array(1000000000); | |
| 1230 slow_arr[500000000] = 'hello'; | |
| 1231 Object.observe(slow_arr, observer.callback); | |
| 1232 var spliceRecords; | |
| 1233 function slowSpliceCallback(records) { | |
| 1234 spliceRecords = records; | |
| 1235 } | |
| 1236 Array.observe(slow_arr, slowSpliceCallback); | |
| 1237 slow_arr.length = 100; | |
| 1238 Object.deliverChangeRecords(observer.callback); | |
| 1239 observer.assertCallbackRecords([ | |
| 1240 { object: slow_arr, name: '500000000', type: 'delete', oldValue: 'hello' }, | |
| 1241 { object: slow_arr, name: 'length', type: 'update', oldValue: 1000000000 }, | |
| 1242 ]); | |
| 1243 Object.deliverChangeRecords(slowSpliceCallback); | |
| 1244 assertEquals(spliceRecords.length, 1); | |
| 1245 // Have to custom assert this splice record because the removed array is huge. | |
| 1246 var splice = spliceRecords[0]; | |
| 1247 assertSame(splice.object, slow_arr); | |
| 1248 assertEquals(splice.type, 'splice'); | |
| 1249 assertEquals(splice.index, 100); | |
| 1250 assertEquals(splice.addedCount, 0); | |
| 1251 var array_keys = %GetArrayKeys(splice.removed, splice.removed.length); | |
| 1252 assertEquals(array_keys.length, 1); | |
| 1253 assertEquals(array_keys[0], 499999900); | |
| 1254 assertEquals(splice.removed[499999900], 'hello'); | |
| 1255 assertEquals(splice.removed.length, 999999900); | |
| 1256 | |
| 1257 | |
| 1258 // Assignments in loops (checking different IC states). | |
| 1259 reset(); | |
| 1260 var obj = {}; | |
| 1261 Object.observe(obj, observer.callback); | |
| 1262 for (var i = 0; i < 5; i++) { | |
| 1263 obj["a" + i] = i; | |
| 1264 } | |
| 1265 Object.deliverChangeRecords(observer.callback); | |
| 1266 observer.assertCallbackRecords([ | |
| 1267 { object: obj, name: "a0", type: "add" }, | |
| 1268 { object: obj, name: "a1", type: "add" }, | |
| 1269 { object: obj, name: "a2", type: "add" }, | |
| 1270 { object: obj, name: "a3", type: "add" }, | |
| 1271 { object: obj, name: "a4", type: "add" }, | |
| 1272 ]); | |
| 1273 | |
| 1274 reset(); | |
| 1275 var obj = {}; | |
| 1276 Object.observe(obj, observer.callback); | |
| 1277 for (var i = 0; i < 5; i++) { | |
| 1278 obj[i] = i; | |
| 1279 } | |
| 1280 Object.deliverChangeRecords(observer.callback); | |
| 1281 observer.assertCallbackRecords([ | |
| 1282 { object: obj, name: "0", type: "add" }, | |
| 1283 { object: obj, name: "1", type: "add" }, | |
| 1284 { object: obj, name: "2", type: "add" }, | |
| 1285 { object: obj, name: "3", type: "add" }, | |
| 1286 { object: obj, name: "4", type: "add" }, | |
| 1287 ]); | |
| 1288 | |
| 1289 | |
| 1290 // Adding elements past the end of an array should notify on length for | |
| 1291 // Object.observe and emit "splices" for Array.observe. | |
| 1292 reset(); | |
| 1293 var arr = [1, 2, 3]; | |
| 1294 Object.observe(arr, observer.callback); | |
| 1295 Array.observe(arr, observer2.callback); | |
| 1296 arr[3] = 10; | |
| 1297 arr[100] = 20; | |
| 1298 Object.defineProperty(arr, '200', {value: 7}); | |
| 1299 Object.defineProperty(arr, '400', {get: function(){}}); | |
| 1300 arr[50] = 30; // no length change expected | |
| 1301 Object.deliverChangeRecords(observer.callback); | |
| 1302 observer.assertCallbackRecords([ | |
| 1303 { object: arr, name: '3', type: 'add' }, | |
| 1304 { object: arr, name: 'length', type: 'update', oldValue: 3 }, | |
| 1305 { object: arr, name: '100', type: 'add' }, | |
| 1306 { object: arr, name: 'length', type: 'update', oldValue: 4 }, | |
| 1307 { object: arr, name: '200', type: 'add' }, | |
| 1308 { object: arr, name: 'length', type: 'update', oldValue: 101 }, | |
| 1309 { object: arr, name: '400', type: 'add' }, | |
| 1310 { object: arr, name: 'length', type: 'update', oldValue: 201 }, | |
| 1311 { object: arr, name: '50', type: 'add' }, | |
| 1312 ]); | |
| 1313 Object.deliverChangeRecords(observer2.callback); | |
| 1314 observer2.assertCallbackRecords([ | |
| 1315 { object: arr, type: 'splice', index: 3, removed: [], addedCount: 1 }, | |
| 1316 { object: arr, type: 'splice', index: 4, removed: [], addedCount: 97 }, | |
| 1317 { object: arr, type: 'splice', index: 101, removed: [], addedCount: 100 }, | |
| 1318 { object: arr, type: 'splice', index: 201, removed: [], addedCount: 200 }, | |
| 1319 { object: arr, type: 'add', name: '50' }, | |
| 1320 ]); | |
| 1321 | |
| 1322 | |
| 1323 // Tests for array methods, first on arrays and then on plain objects | |
| 1324 // | |
| 1325 // === ARRAYS === | |
| 1326 // | |
| 1327 // Push | |
| 1328 reset(); | |
| 1329 var array = [1, 2]; | |
| 1330 Object.observe(array, observer.callback); | |
| 1331 Array.observe(array, observer2.callback); | |
| 1332 array.push(3, 4); | |
| 1333 array.push(5); | |
| 1334 Object.deliverChangeRecords(observer.callback); | |
| 1335 observer.assertCallbackRecords([ | |
| 1336 { object: array, name: '2', type: 'add' }, | |
| 1337 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1338 { object: array, name: '3', type: 'add' }, | |
| 1339 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
| 1340 { object: array, name: '4', type: 'add' }, | |
| 1341 { object: array, name: 'length', type: 'update', oldValue: 4 }, | |
| 1342 ]); | |
| 1343 Object.deliverChangeRecords(observer2.callback); | |
| 1344 observer2.assertCallbackRecords([ | |
| 1345 { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 }, | |
| 1346 { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 } | |
| 1347 ]); | |
| 1348 | |
| 1349 // Pop | |
| 1350 reset(); | |
| 1351 var array = [1, 2]; | |
| 1352 Object.observe(array, observer.callback); | |
| 1353 array.pop(); | |
| 1354 array.pop(); | |
| 1355 Object.deliverChangeRecords(observer.callback); | |
| 1356 observer.assertCallbackRecords([ | |
| 1357 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
| 1358 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1359 { object: array, name: '0', type: 'delete', oldValue: 1 }, | |
| 1360 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
| 1361 ]); | |
| 1362 | |
| 1363 // Shift | |
| 1364 reset(); | |
| 1365 var array = [1, 2]; | |
| 1366 Object.observe(array, observer.callback); | |
| 1367 array.shift(); | |
| 1368 array.shift(); | |
| 1369 Object.deliverChangeRecords(observer.callback); | |
| 1370 observer.assertCallbackRecords([ | |
| 1371 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
| 1372 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
| 1373 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1374 { object: array, name: '0', type: 'delete', oldValue: 2 }, | |
| 1375 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
| 1376 ]); | |
| 1377 | |
| 1378 // Unshift | |
| 1379 reset(); | |
| 1380 var array = [1, 2]; | |
| 1381 Object.observe(array, observer.callback); | |
| 1382 array.unshift(3, 4); | |
| 1383 Object.deliverChangeRecords(observer.callback); | |
| 1384 observer.assertCallbackRecords([ | |
| 1385 { object: array, name: '3', type: 'add' }, | |
| 1386 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1387 { object: array, name: '2', type: 'add' }, | |
| 1388 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
| 1389 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1390 ]); | |
| 1391 | |
| 1392 // Splice | |
| 1393 reset(); | |
| 1394 var array = [1, 2, 3]; | |
| 1395 Object.observe(array, observer.callback); | |
| 1396 array.splice(1, 1, 4, 5); | |
| 1397 Object.deliverChangeRecords(observer.callback); | |
| 1398 observer.assertCallbackRecords([ | |
| 1399 { object: array, name: '3', type: 'add' }, | |
| 1400 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
| 1401 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1402 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
| 1403 ]); | |
| 1404 | |
| 1405 // Sort | |
| 1406 reset(); | |
| 1407 var array = [3, 2, 1]; | |
| 1408 Object.observe(array, observer.callback); | |
| 1409 array.sort(); | |
| 1410 assertEquals(1, array[0]); | |
| 1411 assertEquals(2, array[1]); | |
| 1412 assertEquals(3, array[2]); | |
| 1413 Object.deliverChangeRecords(observer.callback); | |
| 1414 observer.assertCallbackRecords([ | |
| 1415 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1416 { object: array, name: '0', type: 'update', oldValue: 3 }, | |
| 1417 { object: array, name: '2', type: 'update', oldValue: 1 }, | |
| 1418 { object: array, name: '1', type: 'update', oldValue: 3 }, | |
| 1419 { object: array, name: '0', type: 'update', oldValue: 2 }, | |
| 1420 ]); | |
| 1421 | |
| 1422 // Splice emitted after Array mutation methods | |
| 1423 function MockArray(initial, observer) { | |
| 1424 for (var i = 0; i < initial.length; i++) | |
| 1425 this[i] = initial[i]; | |
| 1426 | |
| 1427 this.length_ = initial.length; | |
| 1428 this.observer = observer; | |
| 1429 } | |
| 1430 MockArray.prototype = { | |
| 1431 set length(length) { | |
| 1432 Object.getNotifier(this).notify({ type: 'lengthChange' }); | |
| 1433 this.length_ = length; | |
| 1434 Object.observe(this, this.observer.callback, ['splice']); | |
| 1435 }, | |
| 1436 get length() { | |
| 1437 return this.length_; | |
| 1438 } | |
| 1439 } | |
| 1440 | |
| 1441 reset(); | |
| 1442 var array = new MockArray([], observer); | |
| 1443 Object.observe(array, observer.callback, ['lengthChange']); | |
| 1444 Array.prototype.push.call(array, 1); | |
| 1445 Object.deliverChangeRecords(observer.callback); | |
| 1446 observer.assertCallbackRecords([ | |
| 1447 { object: array, type: 'lengthChange' }, | |
| 1448 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
| 1449 ]); | |
| 1450 | |
| 1451 reset(); | |
| 1452 var array = new MockArray([1], observer); | |
| 1453 Object.observe(array, observer.callback, ['lengthChange']); | |
| 1454 Array.prototype.pop.call(array); | |
| 1455 Object.deliverChangeRecords(observer.callback); | |
| 1456 observer.assertCallbackRecords([ | |
| 1457 { object: array, type: 'lengthChange' }, | |
| 1458 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
| 1459 ]); | |
| 1460 | |
| 1461 reset(); | |
| 1462 var array = new MockArray([1], observer); | |
| 1463 Object.observe(array, observer.callback, ['lengthChange']); | |
| 1464 Array.prototype.shift.call(array); | |
| 1465 Object.deliverChangeRecords(observer.callback); | |
| 1466 observer.assertCallbackRecords([ | |
| 1467 { object: array, type: 'lengthChange' }, | |
| 1468 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
| 1469 ]); | |
| 1470 | |
| 1471 reset(); | |
| 1472 var array = new MockArray([], observer); | |
| 1473 Object.observe(array, observer.callback, ['lengthChange']); | |
| 1474 Array.prototype.unshift.call(array, 1); | |
| 1475 Object.deliverChangeRecords(observer.callback); | |
| 1476 observer.assertCallbackRecords([ | |
| 1477 { object: array, type: 'lengthChange' }, | |
| 1478 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 }, | |
| 1479 ]); | |
| 1480 | |
| 1481 reset(); | |
| 1482 var array = new MockArray([0, 1, 2], observer); | |
| 1483 Object.observe(array, observer.callback, ['lengthChange']); | |
| 1484 Array.prototype.splice.call(array, 1, 1); | |
| 1485 Object.deliverChangeRecords(observer.callback); | |
| 1486 observer.assertCallbackRecords([ | |
| 1487 { object: array, type: 'lengthChange' }, | |
| 1488 { object: array, type: 'splice', index: 1, removed: [1], addedCount: 0 }, | |
| 1489 ]); | |
| 1490 | |
| 1491 // | |
| 1492 // === PLAIN OBJECTS === | |
| 1493 // | |
| 1494 // Push | |
| 1495 reset() | |
| 1496 var array = {0: 1, 1: 2, length: 2} | |
| 1497 Object.observe(array, observer.callback); | |
| 1498 Array.prototype.push.call(array, 3, 4); | |
| 1499 Object.deliverChangeRecords(observer.callback); | |
| 1500 observer.assertCallbackRecords([ | |
| 1501 { object: array, name: '2', type: 'add' }, | |
| 1502 { object: array, name: '3', type: 'add' }, | |
| 1503 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1504 ]); | |
| 1505 | |
| 1506 // Pop | |
| 1507 reset(); | |
| 1508 var array = [1, 2]; | |
| 1509 Object.observe(array, observer.callback); | |
| 1510 Array.observe(array, observer2.callback); | |
| 1511 array.pop(); | |
| 1512 array.pop(); | |
| 1513 array.pop(); | |
| 1514 Object.deliverChangeRecords(observer.callback); | |
| 1515 observer.assertCallbackRecords([ | |
| 1516 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
| 1517 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1518 { object: array, name: '0', type: 'delete', oldValue: 1 }, | |
| 1519 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
| 1520 ]); | |
| 1521 Object.deliverChangeRecords(observer2.callback); | |
| 1522 observer2.assertCallbackRecords([ | |
| 1523 { object: array, type: 'splice', index: 1, removed: [2], addedCount: 0 }, | |
| 1524 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 } | |
| 1525 ]); | |
| 1526 | |
| 1527 // Shift | |
| 1528 reset(); | |
| 1529 var array = [1, 2]; | |
| 1530 Object.observe(array, observer.callback); | |
| 1531 Array.observe(array, observer2.callback); | |
| 1532 array.shift(); | |
| 1533 array.shift(); | |
| 1534 array.shift(); | |
| 1535 Object.deliverChangeRecords(observer.callback); | |
| 1536 observer.assertCallbackRecords([ | |
| 1537 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
| 1538 { object: array, name: '1', type: 'delete', oldValue: 2 }, | |
| 1539 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1540 { object: array, name: '0', type: 'delete', oldValue: 2 }, | |
| 1541 { object: array, name: 'length', type: 'update', oldValue: 1 }, | |
| 1542 ]); | |
| 1543 Object.deliverChangeRecords(observer2.callback); | |
| 1544 observer2.assertCallbackRecords([ | |
| 1545 { object: array, type: 'splice', index: 0, removed: [1], addedCount: 0 }, | |
| 1546 { object: array, type: 'splice', index: 0, removed: [2], addedCount: 0 } | |
| 1547 ]); | |
| 1548 | |
| 1549 // Unshift | |
| 1550 reset(); | |
| 1551 var array = [1, 2]; | |
| 1552 Object.observe(array, observer.callback); | |
| 1553 Array.observe(array, observer2.callback); | |
| 1554 array.unshift(3, 4); | |
| 1555 array.unshift(5); | |
| 1556 Object.deliverChangeRecords(observer.callback); | |
| 1557 observer.assertCallbackRecords([ | |
| 1558 { object: array, name: '3', type: 'add' }, | |
| 1559 { object: array, name: 'length', type: 'update', oldValue: 2 }, | |
| 1560 { object: array, name: '2', type: 'add' }, | |
| 1561 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
| 1562 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1563 { object: array, name: '4', type: 'add' }, | |
| 1564 { object: array, name: 'length', type: 'update', oldValue: 4 }, | |
| 1565 { object: array, name: '3', type: 'update', oldValue: 2 }, | |
| 1566 { object: array, name: '2', type: 'update', oldValue: 1 }, | |
| 1567 { object: array, name: '1', type: 'update', oldValue: 4 }, | |
| 1568 { object: array, name: '0', type: 'update', oldValue: 3 }, | |
| 1569 ]); | |
| 1570 Object.deliverChangeRecords(observer2.callback); | |
| 1571 observer2.assertCallbackRecords([ | |
| 1572 { object: array, type: 'splice', index: 0, removed: [], addedCount: 2 }, | |
| 1573 { object: array, type: 'splice', index: 0, removed: [], addedCount: 1 } | |
| 1574 ]); | |
| 1575 | |
| 1576 // Splice | |
| 1577 reset(); | |
| 1578 var array = [1, 2, 3]; | |
| 1579 Object.observe(array, observer.callback); | |
| 1580 Array.observe(array, observer2.callback); | |
| 1581 array.splice(1, 0, 4, 5); // 1 4 5 2 3 | |
| 1582 array.splice(0, 2); // 5 2 3 | |
| 1583 array.splice(1, 2, 6, 7); // 5 6 7 | |
| 1584 array.splice(2, 0); | |
| 1585 Object.deliverChangeRecords(observer.callback); | |
| 1586 observer.assertCallbackRecords([ | |
| 1587 { object: array, name: '4', type: 'add' }, | |
| 1588 { object: array, name: 'length', type: 'update', oldValue: 3 }, | |
| 1589 { object: array, name: '3', type: 'add' }, | |
| 1590 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1591 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
| 1592 | |
| 1593 { object: array, name: '0', type: 'update', oldValue: 1 }, | |
| 1594 { object: array, name: '1', type: 'update', oldValue: 4 }, | |
| 1595 { object: array, name: '2', type: 'update', oldValue: 5 }, | |
| 1596 { object: array, name: '4', type: 'delete', oldValue: 3 }, | |
| 1597 { object: array, name: '3', type: 'delete', oldValue: 2 }, | |
| 1598 { object: array, name: 'length', type: 'update', oldValue: 5 }, | |
| 1599 | |
| 1600 { object: array, name: '1', type: 'update', oldValue: 2 }, | |
| 1601 { object: array, name: '2', type: 'update', oldValue: 3 }, | |
| 1602 ]); | |
| 1603 Object.deliverChangeRecords(observer2.callback); | |
| 1604 observer2.assertCallbackRecords([ | |
| 1605 { object: array, type: 'splice', index: 1, removed: [], addedCount: 2 }, | |
| 1606 { object: array, type: 'splice', index: 0, removed: [1, 4], addedCount: 0 }, | |
| 1607 { object: array, type: 'splice', index: 1, removed: [2, 3], addedCount: 2 }, | |
| 1608 ]); | |
| 1609 | |
| 1610 // Exercise StoreIC_ArrayLength | |
| 1611 reset(); | |
| 1612 var dummy = {}; | |
| 1613 Object.observe(dummy, observer.callback); | |
| 1614 Object.unobserve(dummy, observer.callback); | |
| 1615 var array = [0]; | |
| 1616 Object.observe(array, observer.callback); | |
| 1617 array.splice(0, 1); | |
| 1618 Object.deliverChangeRecords(observer.callback); | |
| 1619 observer.assertCallbackRecords([ | |
| 1620 { object: array, name: '0', type: 'delete', oldValue: 0 }, | |
| 1621 { object: array, name: 'length', type: 'update', oldValue: 1}, | |
| 1622 ]); | |
| 1623 | |
| 1624 | |
| 1625 // __proto__ | |
| 1626 reset(); | |
| 1627 var obj = {}; | |
| 1628 Object.observe(obj, observer.callback); | |
| 1629 var p = {foo: 'yes'}; | |
| 1630 var q = {bar: 'no'}; | |
| 1631 obj.__proto__ = p; | |
| 1632 obj.__proto__ = p; // ignored | |
| 1633 obj.__proto__ = null; | |
| 1634 obj.__proto__ = q; // the __proto__ accessor is gone | |
| 1635 // TODO(adamk): Add tests for objects with hidden prototypes | |
| 1636 // once we support observing the global object. | |
| 1637 Object.deliverChangeRecords(observer.callback); | |
| 1638 observer.assertCallbackRecords([ | |
| 1639 { object: obj, name: '__proto__', type: 'setPrototype', | |
| 1640 oldValue: Object.prototype }, | |
| 1641 { object: obj, name: '__proto__', type: 'setPrototype', oldValue: p }, | |
| 1642 { object: obj, name: '__proto__', type: 'add' }, | |
| 1643 ]); | |
| 1644 | |
| 1645 | |
| 1646 // Function.prototype | |
| 1647 reset(); | |
| 1648 var fun = function(){}; | |
| 1649 Object.observe(fun, observer.callback); | |
| 1650 var myproto = {foo: 'bar'}; | |
| 1651 fun.prototype = myproto; | |
| 1652 fun.prototype = 7; | |
| 1653 fun.prototype = 7; // ignored | |
| 1654 Object.defineProperty(fun, 'prototype', {value: 8}); | |
| 1655 Object.deliverChangeRecords(observer.callback); | |
| 1656 observer.assertRecordCount(3); | |
| 1657 // Manually examine the first record in order to test | |
| 1658 // lazy creation of oldValue | |
| 1659 assertSame(fun, observer.records[0].object); | |
| 1660 assertEquals('prototype', observer.records[0].name); | |
| 1661 assertEquals('update', observer.records[0].type); | |
| 1662 // The only existing reference to the oldValue object is in this | |
| 1663 // record, so to test that lazy creation happened correctly | |
| 1664 // we compare its constructor to our function (one of the invariants | |
| 1665 // ensured when creating an object via AllocateFunctionPrototype). | |
| 1666 assertSame(fun, observer.records[0].oldValue.constructor); | |
| 1667 observer.records.splice(0, 1); | |
| 1668 observer.assertCallbackRecords([ | |
| 1669 { object: fun, name: 'prototype', type: 'update', oldValue: myproto }, | |
| 1670 { object: fun, name: 'prototype', type: 'update', oldValue: 7 }, | |
| 1671 ]); | |
| 1672 | |
| 1673 // Function.prototype should not be observable except on the object itself | |
| 1674 reset(); | |
| 1675 var fun = function(){}; | |
| 1676 var obj = { __proto__: fun }; | |
| 1677 Object.observe(obj, observer.callback); | |
| 1678 obj.prototype = 7; | |
| 1679 Object.deliverChangeRecords(observer.callback); | |
| 1680 observer.assertNotCalled(); | |
| 1681 | |
| 1682 | |
| 1683 // Check that changes in observation status are detected in all IC states and | |
| 1684 // in optimized code, especially in cases usually using fast elements. | |
| 1685 var mutation = [ | |
| 1686 "a[i] = v", | |
| 1687 "a[i] ? ++a[i] : a[i] = v", | |
| 1688 "a[i] ? a[i]++ : a[i] = v", | |
| 1689 "a[i] ? a[i] += 1 : a[i] = v", | |
| 1690 "a[i] ? a[i] -= -1 : a[i] = v", | |
| 1691 ]; | |
| 1692 | |
| 1693 var props = [1, "1", "a"]; | |
| 1694 | |
| 1695 function TestFastElements(prop, mutation, prepopulate, polymorphic, optimize) { | |
| 1696 var setElement = eval( | |
| 1697 "(function setElement(a, i, v) { " + mutation + "; " + | |
| 1698 "/* " + [].join.call(arguments, " ") + " */" + | |
| 1699 "})" | |
| 1700 ); | |
| 1701 print("TestFastElements:", setElement); | |
| 1702 | |
| 1703 var arr = prepopulate ? [1, 2, 3, 4, 5] : [0]; | |
| 1704 if (prepopulate) arr[prop] = 2; // for non-element case | |
| 1705 setElement(arr, prop, 3); | |
| 1706 setElement(arr, prop, 4); | |
| 1707 if (polymorphic) setElement(["M", "i", "l", "n", "e", "r"], 0, "m"); | |
| 1708 if (optimize) %OptimizeFunctionOnNextCall(setElement); | |
| 1709 setElement(arr, prop, 5); | |
| 1710 | |
| 1711 reset(); | |
| 1712 Object.observe(arr, observer.callback); | |
| 1713 setElement(arr, prop, 989898); | |
| 1714 Object.deliverChangeRecords(observer.callback); | |
| 1715 observer.assertCallbackRecords([ | |
| 1716 { object: arr, name: "" + prop, type: 'update', oldValue: 5 } | |
| 1717 ]); | |
| 1718 } | |
| 1719 | |
| 1720 for (var b1 = 0; b1 < 2; ++b1) | |
| 1721 for (var b2 = 0; b2 < 2; ++b2) | |
| 1722 for (var b3 = 0; b3 < 2; ++b3) | |
| 1723 for (var i in props) | |
| 1724 for (var j in mutation) | |
| 1725 TestFastElements(props[i], mutation[j], b1 != 0, b2 != 0, b3 != 0); | |
| 1726 | |
| 1727 | |
| 1728 var mutation = [ | |
| 1729 "a.length = v", | |
| 1730 "a.length += newSize - oldSize", | |
| 1731 "a.length -= oldSize - newSize", | |
| 1732 ]; | |
| 1733 | |
| 1734 var mutationByIncr = [ | |
| 1735 "++a.length", | |
| 1736 "a.length++", | |
| 1737 ]; | |
| 1738 | |
| 1739 function TestFastElementsLength( | |
| 1740 mutation, polymorphic, optimize, oldSize, newSize) { | |
| 1741 var setLength = eval( | |
| 1742 "(function setLength(a, v) { " + mutation + "; " + | |
| 1743 "/* " + [].join.call(arguments, " ") + " */" | |
| 1744 + "})" | |
| 1745 ); | |
| 1746 print("TestFastElementsLength:", setLength); | |
| 1747 | |
| 1748 function array(n) { | |
| 1749 var arr = new Array(n); | |
| 1750 for (var i = 0; i < n; ++i) arr[i] = i; | |
| 1751 return arr; | |
| 1752 } | |
| 1753 | |
| 1754 setLength(array(oldSize), newSize); | |
| 1755 setLength(array(oldSize), newSize); | |
| 1756 if (polymorphic) setLength(array(oldSize).map(isNaN), newSize); | |
| 1757 if (optimize) %OptimizeFunctionOnNextCall(setLength); | |
| 1758 setLength(array(oldSize), newSize); | |
| 1759 | |
| 1760 reset(); | |
| 1761 var arr = array(oldSize); | |
| 1762 Object.observe(arr, observer.callback); | |
| 1763 setLength(arr, newSize); | |
| 1764 Object.deliverChangeRecords(observer.callback); | |
| 1765 if (oldSize === newSize) { | |
| 1766 observer.assertNotCalled(); | |
| 1767 } else { | |
| 1768 var count = oldSize > newSize ? oldSize - newSize : 0; | |
| 1769 observer.assertRecordCount(count + 1); | |
| 1770 var lengthRecord = observer.records[count]; | |
| 1771 assertSame(arr, lengthRecord.object); | |
| 1772 assertEquals('length', lengthRecord.name); | |
| 1773 assertEquals('update', lengthRecord.type); | |
| 1774 assertSame(oldSize, lengthRecord.oldValue); | |
| 1775 } | |
| 1776 } | |
| 1777 | |
| 1778 for (var b1 = 0; b1 < 2; ++b1) | |
| 1779 for (var b2 = 0; b2 < 2; ++b2) | |
| 1780 for (var n1 = 0; n1 < 3; ++n1) | |
| 1781 for (var n2 = 0; n2 < 3; ++n2) | |
| 1782 for (var i in mutation) | |
| 1783 TestFastElementsLength(mutation[i], b1 != 0, b2 != 0, 20*n1, 20*n2); | |
| 1784 | |
| 1785 for (var b1 = 0; b1 < 2; ++b1) | |
| 1786 for (var b2 = 0; b2 < 2; ++b2) | |
| 1787 for (var n = 0; n < 3; ++n) | |
| 1788 for (var i in mutationByIncr) | |
| 1789 TestFastElementsLength(mutationByIncr[i], b1 != 0, b2 != 0, 7*n, 7*n+1); | |
| OLD | NEW |