Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Side by Side Diff: src/array.js

Issue 1067523003: Wrap array implementation in a function. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | src/builtins.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2012 the V8 project authors. All rights reserved. 1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var $arrayConcat;
6 var $arrayJoin;
7 var $arrayPush;
8 var $arrayPop;
9 var $arrayShift;
10 var $arraySlice;
11 var $arraySplice;
12 var $arrayUnshift;
13
14 (function() {
15
5 "use strict"; 16 "use strict";
6 17
7 // This file relies on the fact that the following declarations have been made 18 %CheckIsBootstrapping();
8 // in runtime.js: 19
9 // var $Array = global.Array; 20 var GlobalArray = global.Array;
10 21
11 // ------------------------------------------------------------------- 22 // -------------------------------------------------------------------
12 23
13 // Global list of arrays visited during toString, toLocaleString and 24 // Global list of arrays visited during toString, toLocaleString and
14 // join invocations. 25 // join invocations.
15 var visited_arrays = new InternalArray(); 26 var visited_arrays = new InternalArray();
16 27
17 28
18 // Gets a sorted array of array keys. Useful for operations on sparse 29 // Gets a sorted array of array keys. Useful for operations on sparse
19 // arrays. Dupes have not been removed. 30 // arrays. Dupes have not been removed.
(...skipping 391 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 delete this[n]; 422 delete this[n];
412 this.length = n; 423 this.length = n;
413 } finally { 424 } finally {
414 $observeEndPerformSplice(this); 425 $observeEndPerformSplice(this);
415 $observeEnqueueSpliceRecord(this, n, [value], 0); 426 $observeEnqueueSpliceRecord(this, n, [value], 0);
416 } 427 }
417 428
418 return value; 429 return value;
419 } 430 }
420 431
432
421 // Removes the last element from the array and returns it. See 433 // Removes the last element from the array and returns it. See
422 // ECMA-262, section 15.4.4.6. 434 // ECMA-262, section 15.4.4.6.
423 function ArrayPop() { 435 function ArrayPop() {
424 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop"); 436 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop");
425 437
426 var array = TO_OBJECT_INLINE(this); 438 var array = TO_OBJECT_INLINE(this);
427 var n = TO_UINT32(array.length); 439 var n = TO_UINT32(array.length);
428 if (n == 0) { 440 if (n == 0) {
429 array.length = n; 441 array.length = n;
430 return; 442 return;
(...skipping 22 matching lines...) Expand all
453 var new_length = n + m; 465 var new_length = n + m;
454 this.length = new_length; 466 this.length = new_length;
455 } finally { 467 } finally {
456 $observeEndPerformSplice(this); 468 $observeEndPerformSplice(this);
457 $observeEnqueueSpliceRecord(this, n, [], m); 469 $observeEnqueueSpliceRecord(this, n, [], m);
458 } 470 }
459 471
460 return new_length; 472 return new_length;
461 } 473 }
462 474
475
463 // Appends the arguments to the end of the array and returns the new 476 // Appends the arguments to the end of the array and returns the new
464 // length of the array. See ECMA-262, section 15.4.4.7. 477 // length of the array. See ECMA-262, section 15.4.4.7.
465 function ArrayPush() { 478 function ArrayPush() {
466 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push"); 479 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
467 480
468 if (%IsObserved(this)) 481 if (%IsObserved(this))
469 return ObservedArrayPush.apply(this, arguments); 482 return ObservedArrayPush.apply(this, arguments);
470 483
471 var array = TO_OBJECT_INLINE(this); 484 var array = TO_OBJECT_INLINE(this);
472 var n = TO_UINT32(array.length); 485 var n = TO_UINT32(array.length);
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after
588 SimpleMove(this, 0, 1, len, 0); 601 SimpleMove(this, 0, 1, len, 0);
589 this.length = len - 1; 602 this.length = len - 1;
590 } finally { 603 } finally {
591 $observeEndPerformSplice(this); 604 $observeEndPerformSplice(this);
592 $observeEnqueueSpliceRecord(this, 0, [first], 0); 605 $observeEnqueueSpliceRecord(this, 0, [first], 0);
593 } 606 }
594 607
595 return first; 608 return first;
596 } 609 }
597 610
611
598 function ArrayShift() { 612 function ArrayShift() {
599 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift"); 613 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift");
600 614
601 var array = TO_OBJECT_INLINE(this); 615 var array = TO_OBJECT_INLINE(this);
602 var len = TO_UINT32(array.length); 616 var len = TO_UINT32(array.length);
603 617
604 if (len === 0) { 618 if (len === 0) {
605 array.length = 0; 619 array.length = 0;
606 return; 620 return;
607 } 621 }
(...skipping 12 matching lines...) Expand all
620 SparseMove(array, 0, 1, len, 0); 634 SparseMove(array, 0, 1, len, 0);
621 } else { 635 } else {
622 SimpleMove(array, 0, 1, len, 0); 636 SimpleMove(array, 0, 1, len, 0);
623 } 637 }
624 638
625 array.length = len - 1; 639 array.length = len - 1;
626 640
627 return first; 641 return first;
628 } 642 }
629 643
644
630 function ObservedArrayUnshift() { 645 function ObservedArrayUnshift() {
631 var len = TO_UINT32(this.length); 646 var len = TO_UINT32(this.length);
632 var num_arguments = %_ArgumentsLength(); 647 var num_arguments = %_ArgumentsLength();
633 648
634 try { 649 try {
635 $observeBeginPerformSplice(this); 650 $observeBeginPerformSplice(this);
636 SimpleMove(this, 0, 0, len, num_arguments); 651 SimpleMove(this, 0, 0, len, num_arguments);
637 for (var i = 0; i < num_arguments; i++) { 652 for (var i = 0; i < num_arguments; i++) {
638 this[i] = %_Arguments(i); 653 this[i] = %_Arguments(i);
639 } 654 }
640 var new_length = len + num_arguments; 655 var new_length = len + num_arguments;
641 this.length = new_length; 656 this.length = new_length;
642 } finally { 657 } finally {
643 $observeEndPerformSplice(this); 658 $observeEndPerformSplice(this);
644 $observeEnqueueSpliceRecord(this, 0, [], num_arguments); 659 $observeEnqueueSpliceRecord(this, 0, [], num_arguments);
645 } 660 }
646 661
647 return new_length; 662 return new_length;
648 } 663 }
649 664
665
650 function ArrayUnshift(arg1) { // length == 1 666 function ArrayUnshift(arg1) { // length == 1
651 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.unshift"); 667 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.unshift");
652 668
653 if (%IsObserved(this)) 669 if (%IsObserved(this))
654 return ObservedArrayUnshift.apply(this, arguments); 670 return ObservedArrayUnshift.apply(this, arguments);
655 671
656 var array = TO_OBJECT_INLINE(this); 672 var array = TO_OBJECT_INLINE(this);
657 var len = TO_UINT32(array.length); 673 var len = TO_UINT32(array.length);
658 var num_arguments = %_ArgumentsLength(); 674 var num_arguments = %_ArgumentsLength();
659 675
(...skipping 483 matching lines...) Expand 10 before | Expand all | Expand 10 after
1143 var length = ToUint32(array.length); 1159 var length = ToUint32(array.length);
1144 1160
1145 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); 1161 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
1146 var needs_wrapper = false; 1162 var needs_wrapper = false;
1147 if (IS_NULL_OR_UNDEFINED(receiver)) { 1163 if (IS_NULL_OR_UNDEFINED(receiver)) {
1148 receiver = %GetDefaultReceiver(f) || receiver; 1164 receiver = %GetDefaultReceiver(f) || receiver;
1149 } else { 1165 } else {
1150 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); 1166 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
1151 } 1167 }
1152 1168
1153 var result = new $Array(); 1169 var result = new GlobalArray();
1154 var accumulator = new InternalArray(); 1170 var accumulator = new InternalArray();
1155 var accumulator_length = 0; 1171 var accumulator_length = 0;
1156 var is_array = IS_ARRAY(array); 1172 var is_array = IS_ARRAY(array);
1157 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); 1173 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1158 for (var i = 0; i < length; i++) { 1174 for (var i = 0; i < length; i++) {
1159 if (HAS_INDEX(array, i, is_array)) { 1175 if (HAS_INDEX(array, i, is_array)) {
1160 var element = array[i]; 1176 var element = array[i];
1161 // Prepare break slots for debugger step in. 1177 // Prepare break slots for debugger step in.
1162 if (stepping) %DebugPrepareStepInIfStepping(f); 1178 if (stepping) %DebugPrepareStepInIfStepping(f);
1163 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; 1179 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver;
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
1257 var element = array[i]; 1273 var element = array[i];
1258 // Prepare break slots for debugger step in. 1274 // Prepare break slots for debugger step in.
1259 if (stepping) %DebugPrepareStepInIfStepping(f); 1275 if (stepping) %DebugPrepareStepInIfStepping(f);
1260 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; 1276 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver;
1261 if (!%_CallFunction(new_receiver, element, i, array, f)) return false; 1277 if (!%_CallFunction(new_receiver, element, i, array, f)) return false;
1262 } 1278 }
1263 } 1279 }
1264 return true; 1280 return true;
1265 } 1281 }
1266 1282
1283
1267 function ArrayMap(f, receiver) { 1284 function ArrayMap(f, receiver) {
1268 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map"); 1285 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
1269 1286
1270 // Pull out the length so that modifications to the length in the 1287 // Pull out the length so that modifications to the length in the
1271 // loop will not affect the looping and side effects are visible. 1288 // loop will not affect the looping and side effects are visible.
1272 var array = ToObject(this); 1289 var array = ToObject(this);
1273 var length = TO_UINT32(array.length); 1290 var length = TO_UINT32(array.length);
1274 1291
1275 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); 1292 if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f);
1276 var needs_wrapper = false; 1293 var needs_wrapper = false;
1277 if (IS_NULL_OR_UNDEFINED(receiver)) { 1294 if (IS_NULL_OR_UNDEFINED(receiver)) {
1278 receiver = %GetDefaultReceiver(f) || receiver; 1295 receiver = %GetDefaultReceiver(f) || receiver;
1279 } else { 1296 } else {
1280 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver); 1297 needs_wrapper = SHOULD_CREATE_WRAPPER(f, receiver);
1281 } 1298 }
1282 1299
1283 var result = new $Array(); 1300 var result = new GlobalArray();
1284 var accumulator = new InternalArray(length); 1301 var accumulator = new InternalArray(length);
1285 var is_array = IS_ARRAY(array); 1302 var is_array = IS_ARRAY(array);
1286 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f); 1303 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1287 for (var i = 0; i < length; i++) { 1304 for (var i = 0; i < length; i++) {
1288 if (HAS_INDEX(array, i, is_array)) { 1305 if (HAS_INDEX(array, i, is_array)) {
1289 var element = array[i]; 1306 var element = array[i];
1290 // Prepare break slots for debugger step in. 1307 // Prepare break slots for debugger step in.
1291 if (stepping) %DebugPrepareStepInIfStepping(f); 1308 if (stepping) %DebugPrepareStepInIfStepping(f);
1292 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver; 1309 var new_receiver = needs_wrapper ? ToObject(receiver) : receiver;
1293 accumulator[i] = %_CallFunction(new_receiver, element, i, array, f); 1310 accumulator[i] = %_CallFunction(new_receiver, element, i, array, f);
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
1438 if (HAS_INDEX(array, i, is_array)) { 1455 if (HAS_INDEX(array, i, is_array)) {
1439 var element = array[i]; 1456 var element = array[i];
1440 // Prepare break slots for debugger step in. 1457 // Prepare break slots for debugger step in.
1441 if (stepping) %DebugPrepareStepInIfStepping(callback); 1458 if (stepping) %DebugPrepareStepInIfStepping(callback);
1442 current = %_CallFunction(receiver, current, element, i, array, callback); 1459 current = %_CallFunction(receiver, current, element, i, array, callback);
1443 } 1460 }
1444 } 1461 }
1445 return current; 1462 return current;
1446 } 1463 }
1447 1464
1465
1448 function ArrayReduceRight(callback, current) { 1466 function ArrayReduceRight(callback, current) {
1449 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight"); 1467 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
1450 1468
1451 // Pull out the length so that side effects are visible before the 1469 // Pull out the length so that side effects are visible before the
1452 // callback function is checked. 1470 // callback function is checked.
1453 var array = ToObject(this); 1471 var array = ToObject(this);
1454 var length = ToUint32(array.length); 1472 var length = ToUint32(array.length);
1455 1473
1456 if (!IS_SPEC_FUNCTION(callback)) { 1474 if (!IS_SPEC_FUNCTION(callback)) {
1457 throw MakeTypeError(kCalledNonCallable, callback); 1475 throw MakeTypeError(kCalledNonCallable, callback);
(...skipping 25 matching lines...) Expand all
1483 } 1501 }
1484 1502
1485 // ES5, 15.4.3.2 1503 // ES5, 15.4.3.2
1486 function ArrayIsArray(obj) { 1504 function ArrayIsArray(obj) {
1487 return IS_ARRAY(obj); 1505 return IS_ARRAY(obj);
1488 } 1506 }
1489 1507
1490 1508
1491 // ------------------------------------------------------------------- 1509 // -------------------------------------------------------------------
1492 1510
1493 function SetUpArray() { 1511 // Set up non-enumerable constructor property on the Array.prototype
1494 %CheckIsBootstrapping(); 1512 // object.
1513 %AddNamedProperty(GlobalArray.prototype, "constructor", GlobalArray,
1514 DONT_ENUM);
1495 1515
1496 // Set up non-enumerable constructor property on the Array.prototype 1516 // Set up unscopable properties on the Array.prototype object.
1497 // object. 1517 var unscopables = {
1498 %AddNamedProperty($Array.prototype, "constructor", $Array, DONT_ENUM); 1518 __proto__: null,
1519 copyWithin: true,
1520 entries: true,
1521 fill: true,
1522 find: true,
1523 findIndex: true,
1524 keys: true,
1525 };
1499 1526
1500 // Set up unscopable properties on the Array.prototype object. 1527 %AddNamedProperty(GlobalArray.prototype, symbolUnscopables, unscopables,
1501 var unscopables = { 1528 DONT_ENUM | READ_ONLY);
1502 __proto__: null,
1503 copyWithin: true,
1504 entries: true,
1505 fill: true,
1506 find: true,
1507 findIndex: true,
1508 keys: true,
1509 };
1510 %AddNamedProperty($Array.prototype, symbolUnscopables, unscopables,
1511 DONT_ENUM | READ_ONLY);
1512 1529
1513 // Set up non-enumerable functions on the Array object. 1530 // Set up non-enumerable functions on the Array object.
1514 InstallFunctions($Array, DONT_ENUM, [ 1531 InstallFunctions(GlobalArray, DONT_ENUM, [
1515 "isArray", ArrayIsArray 1532 "isArray", ArrayIsArray
1516 ]); 1533 ]);
1517 1534
1518 var specialFunctions = %SpecialArrayFunctions(); 1535 var specialFunctions = %SpecialArrayFunctions();
1519 1536
1520 var getFunction = function(name, jsBuiltin, len) { 1537 var getFunction = function(name, jsBuiltin, len) {
1521 var f = jsBuiltin; 1538 var f = jsBuiltin;
1522 if (specialFunctions.hasOwnProperty(name)) { 1539 if (specialFunctions.hasOwnProperty(name)) {
1523 f = specialFunctions[name]; 1540 f = specialFunctions[name];
1524 } 1541 }
1525 if (!IS_UNDEFINED(len)) { 1542 if (!IS_UNDEFINED(len)) {
1526 %FunctionSetLength(f, len); 1543 %FunctionSetLength(f, len);
1527 } 1544 }
1528 return f; 1545 return f;
1529 }; 1546 };
1530 1547
1531 // Set up non-enumerable functions of the Array.prototype object and 1548 // Set up non-enumerable functions of the Array.prototype object and
1532 // set their names. 1549 // set their names.
1533 // Manipulate the length of some of the functions to meet 1550 // Manipulate the length of some of the functions to meet
1534 // expectations set by ECMA-262 or Mozilla. 1551 // expectations set by ECMA-262 or Mozilla.
1535 InstallFunctions($Array.prototype, DONT_ENUM, [ 1552 InstallFunctions(GlobalArray.prototype, DONT_ENUM, [
1536 "toString", getFunction("toString", ArrayToString), 1553 "toString", getFunction("toString", ArrayToString),
1537 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString), 1554 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1538 "join", getFunction("join", ArrayJoin), 1555 "join", getFunction("join", ArrayJoin),
1539 "pop", getFunction("pop", ArrayPop), 1556 "pop", getFunction("pop", ArrayPop),
1540 "push", getFunction("push", ArrayPush, 1), 1557 "push", getFunction("push", ArrayPush, 1),
1541 "concat", getFunction("concat", ArrayConcatJS, 1), 1558 "concat", getFunction("concat", ArrayConcatJS, 1),
1542 "reverse", getFunction("reverse", ArrayReverse), 1559 "reverse", getFunction("reverse", ArrayReverse),
1543 "shift", getFunction("shift", ArrayShift), 1560 "shift", getFunction("shift", ArrayShift),
1544 "unshift", getFunction("unshift", ArrayUnshift, 1), 1561 "unshift", getFunction("unshift", ArrayUnshift, 1),
1545 "slice", getFunction("slice", ArraySlice, 2), 1562 "slice", getFunction("slice", ArraySlice, 2),
1546 "splice", getFunction("splice", ArraySplice, 2), 1563 "splice", getFunction("splice", ArraySplice, 2),
1547 "sort", getFunction("sort", ArraySort), 1564 "sort", getFunction("sort", ArraySort),
1548 "filter", getFunction("filter", ArrayFilter, 1), 1565 "filter", getFunction("filter", ArrayFilter, 1),
1549 "forEach", getFunction("forEach", ArrayForEach, 1), 1566 "forEach", getFunction("forEach", ArrayForEach, 1),
1550 "some", getFunction("some", ArraySome, 1), 1567 "some", getFunction("some", ArraySome, 1),
1551 "every", getFunction("every", ArrayEvery, 1), 1568 "every", getFunction("every", ArrayEvery, 1),
1552 "map", getFunction("map", ArrayMap, 1), 1569 "map", getFunction("map", ArrayMap, 1),
1553 "indexOf", getFunction("indexOf", ArrayIndexOf, 1), 1570 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1554 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1), 1571 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1555 "reduce", getFunction("reduce", ArrayReduce, 1), 1572 "reduce", getFunction("reduce", ArrayReduce, 1),
1556 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1) 1573 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1557 ]); 1574 ]);
1558 1575
1559 %FinishArrayPrototypeSetup($Array.prototype); 1576 %FinishArrayPrototypeSetup(GlobalArray.prototype);
1560 1577
1561 // The internal Array prototype doesn't need to be fancy, since it's never 1578 // The internal Array prototype doesn't need to be fancy, since it's never
1562 // exposed to user code. 1579 // exposed to user code.
1563 // Adding only the functions that are actually used. 1580 // Adding only the functions that are actually used.
1564 SetUpLockedPrototype(InternalArray, $Array(), [ 1581 SetUpLockedPrototype(InternalArray, GlobalArray(), [
1565 "concat", getFunction("concat", ArrayConcatJS), 1582 "concat", getFunction("concat", ArrayConcatJS),
1566 "indexOf", getFunction("indexOf", ArrayIndexOf), 1583 "indexOf", getFunction("indexOf", ArrayIndexOf),
1567 "join", getFunction("join", ArrayJoin), 1584 "join", getFunction("join", ArrayJoin),
1568 "pop", getFunction("pop", ArrayPop), 1585 "pop", getFunction("pop", ArrayPop),
1569 "push", getFunction("push", ArrayPush), 1586 "push", getFunction("push", ArrayPush),
1570 "splice", getFunction("splice", ArraySplice) 1587 "splice", getFunction("splice", ArraySplice)
1571 ]); 1588 ]);
1572 1589
1573 SetUpLockedPrototype(InternalPackedArray, $Array(), [ 1590 SetUpLockedPrototype(InternalPackedArray, GlobalArray(), [
1574 "join", getFunction("join", ArrayJoin), 1591 "join", getFunction("join", ArrayJoin),
1575 "pop", getFunction("pop", ArrayPop), 1592 "pop", getFunction("pop", ArrayPop),
1576 "push", getFunction("push", ArrayPush) 1593 "push", getFunction("push", ArrayPush)
1577 ]); 1594 ]);
1578 }
1579 1595
1580 SetUpArray(); 1596 $arrayConcat = ArrayConcatJS;
1597 $arrayJoin = ArrayJoin;
1598 $arrayPush = ArrayPush;
1599 $arrayPop = ArrayPop;
1600 $arrayShift = ArrayShift;
1601 $arraySlice = ArraySlice;
1602 $arraySplice = ArraySplice;
1603 $arrayUnshift = ArrayUnshift;
1604
1605 })();
OLDNEW
« no previous file with comments | « no previous file | src/builtins.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698