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