cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From t...@apache.org
Subject [06/16] cordova-windows git commit: CB-9252: Migrate WinJS to an NPM dependency
Date Fri, 26 Jun 2015 17:01:28 GMT
http://git-wip-us.apache.org/repos/asf/cordova-windows/blob/99107c62/node_modules/winjs/js/ui.js
----------------------------------------------------------------------
diff --git a/node_modules/winjs/js/ui.js b/node_modules/winjs/js/ui.js
new file mode 100644
index 0000000..367d5fa
--- /dev/null
+++ b/node_modules/winjs/js/ui.js
@@ -0,0 +1,54913 @@
+
+/*! Copyright (c) Microsoft Corporation.  All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */
+(function () {
+
+    var globalObject = 
+        typeof window !== 'undefined' ? window :
+        typeof self !== 'undefined' ? self :
+        typeof global !== 'undefined' ? global :
+        {};
+    (function (factory) {
+        if (typeof define === 'function' && define.amd) {
+            // amd
+            define(["./base"], factory);
+        } else {
+            globalObject.msWriteProfilerMark && msWriteProfilerMark('WinJS.4.0 4.0.1.winjs.2015.6.10 ui.js,StartTM');
+            if (typeof module !== 'undefined') {
+                // CommonJS
+                factory(require("./base"));
+            } else {
+                // No module system
+                factory(globalObject.WinJS);
+            }
+            globalObject.msWriteProfilerMark && msWriteProfilerMark('WinJS.4.0 4.0.1.winjs.2015.6.10 ui.js,StopTM');
+        }
+    }(function (WinJS) {
+
+
+var require = WinJS.Utilities._require;
+var define = WinJS.Utilities._define;
+
+// Copyright (c) Microsoft Corporation.  All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
+// Virtualized Data Source
+define('WinJS/VirtualizedDataSource/_VirtualizedDataSourceImpl',[
+    'exports',
+    '../Core/_Global',
+    '../Core/_Base',
+    '../Core/_BaseUtils',
+    '../Core/_ErrorFromName',
+    '../Core/_Events',
+    '../Core/_Log',
+    '../Core/_Resources',
+    '../Core/_WriteProfilerMark',
+    '../Promise',
+    '../Scheduler',
+    '../_Signal',
+    '../Utilities/_UI'
+    ], function listDataSourceInit(exports, _Global, _Base, _BaseUtils, _ErrorFromName, _Events, _Log, _Resources, _WriteProfilerMark, Promise, Scheduler, _Signal, _UI) {
+    "use strict";
+
+    _Base.Namespace._moduleDefine(exports, "WinJS.UI", {
+
+        VirtualizedDataSource: _Base.Namespace._lazy(function () {
+            var MAX_BEGINREFRESH_COUNT = 100;
+            var uniqueID = 1;
+
+            var DataSourceStatus = _UI.DataSourceStatus,
+            CountResult = _UI.CountResult,
+            FetchError = _UI.FetchError,
+            EditError = _UI.EditError;
+
+            // Private statics
+
+            var strings = {
+                get listDataAdapterIsInvalid() { return "Invalid argument: listDataAdapter must be an object or an array."; },
+                get indexIsInvalid() { return "Invalid argument: index must be a non-negative integer."; },
+                get keyIsInvalid() { return "Invalid argument: key must be a string."; },
+                get invalidItemReturned() { return "Error: data adapter returned item that is not an object."; },
+                get invalidKeyReturned() { return "Error: data adapter returned item with undefined or null key."; },
+                get invalidIndexReturned() { return "Error: data adapter should return undefined, null or a non-negative integer for the index."; },
+                get invalidCountReturned() { return "Error: data adapter should return undefined, null, CountResult.unknown, or a non-negative integer for the count."; },
+                get invalidRequestedCountReturned() { return "Error: data adapter should return CountResult.unknown, CountResult.failure, or a non-negative integer for the count."; },
+                get refreshCycleIdentified() { return "refresh cycle found, likely data inconsistency"; },
+            };
+
+            var statusChangedEvent = "statuschanged";
+
+            function _baseDataSourceConstructor(listDataAdapter, options) {
+                /// <signature helpKeyword="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor">
+                /// <summary locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor">
+                /// Initializes the VirtualizedDataSource base class of a custom data source.
+                /// </summary>
+                /// <param name="listDataAdapter" type="IListDataAdapter" locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor_p:itemIndex">
+                /// An object that implements IListDataAdapter and supplies data to the VirtualizedDataSource.
+                /// </param>
+                /// <param name="options" optional="true" type="Object" locid="WinJS.UI.VirtualizedDataSource._baseDataSourceConstructor_p:options">
+                /// An object that contains properties that specify additonal options for the VirtualizedDataSource:
+                ///
+                /// cacheSize
+                /// A Number that specifies minimum number of unrequested items to cache in case they are requested.
+                ///
+                /// The options parameter is optional.
+                /// </param>
+                /// </signature>
+
+                // Private members
+
+                /*jshint validthis: true */
+
+                var listDataNotificationHandler,
+                    cacheSize,
+                    status,
+                    statusPending,
+                    statusChangePosted,
+                    bindingMap,
+                    nextListBindingID,
+                    nextHandle,
+                    nextListenerID,
+                    getCountPromise,
+                    resultsProcessed,
+                    beginEditsCalled,
+                    editsInProgress,
+                    firstEditInProgress,
+                    editQueue,
+                    editsQueued,
+                    synchronousEdit,
+                    waitForRefresh,
+                    dataNotificationsInProgress,
+                    countDelta,
+                    indexUpdateDeferred,
+                    nextTempKey,
+                    currentRefreshID,
+                    fetchesPosted,
+                    nextFetchID,
+                    fetchesInProgress,
+                    fetchCompleteCallbacks,
+                    startMarker,
+                    endMarker,
+                    knownCount,
+                    slotsStart,
+                    slotListEnd,
+                    slotsEnd,
+                    handleMap,
+                    keyMap,
+                    indexMap,
+                    releasedSlots,
+                    lastSlotReleased,
+                    reduceReleasedSlotCountPosted,
+                    refreshRequested,
+                    refreshInProgress,
+                    refreshSignal,
+                    refreshFetchesInProgress,
+                    refreshItemsFetched,
+                    refreshCount,
+                    refreshStart,
+                    refreshEnd,
+                    keyFetchIDs,
+                    refreshKeyMap,
+                    refreshIndexMap,
+                    deletedKeys,
+                    synchronousProgress,
+                    reentrantContinue,
+                    synchronousRefresh,
+                    reentrantRefresh;
+
+                var beginRefreshCount = 0,
+                    refreshHistory = new Array(100),
+                    refreshHistoryPos = -1;
+
+                var itemsFromKey,
+                    itemsFromIndex,
+                    itemsFromStart,
+                    itemsFromEnd,
+                    itemsFromDescription;
+
+                if (listDataAdapter.itemsFromKey) {
+                    itemsFromKey = function (fetchID, key, countBefore, countAfter, hints) {
+                        var perfID = "fetchItemsFromKey id=" + fetchID + " key=" + key + " countBefore=" + countBefore + " countAfter=" + countAfter;
+                        profilerMarkStart(perfID);
+                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromKey", key: key, countBefore: countBefore, countAfter: countAfter };
+                        var result = listDataAdapter.itemsFromKey(key, countBefore, countAfter, hints);
+                        profilerMarkEnd(perfID);
+                        return result;
+                    };
+                }
+                if (listDataAdapter.itemsFromIndex) {
+                    itemsFromIndex = function (fetchID, index, countBefore, countAfter) {
+                        var perfID = "fetchItemsFromIndex id=" + fetchID + " index=" + index + " countBefore=" + countBefore + " countAfter=" + countAfter;
+                        profilerMarkStart(perfID);
+                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromIndex", index: index, countBefore: countBefore, countAfter: countAfter };
+                        var result = listDataAdapter.itemsFromIndex(index, countBefore, countAfter);
+                        profilerMarkEnd(perfID);
+                        return result;
+                    };
+                }
+                if (listDataAdapter.itemsFromStart) {
+                    itemsFromStart = function (fetchID, count) {
+                        var perfID = "fetchItemsFromStart id=" + fetchID + " count=" + count;
+                        profilerMarkStart(perfID);
+                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromStart", count: count };
+                        var result = listDataAdapter.itemsFromStart(count);
+                        profilerMarkEnd(perfID);
+                        return result;
+                    };
+                }
+                if (listDataAdapter.itemsFromEnd) {
+                    itemsFromEnd = function (fetchID, count) {
+                        var perfID = "fetchItemsFromEnd id=" + fetchID + " count=" + count;
+                        profilerMarkStart(perfID);
+                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromEnd", count: count };
+                        var result = listDataAdapter.itemsFromEnd(count);
+                        profilerMarkEnd(perfID);
+                        return result;
+                    };
+                }
+                if (listDataAdapter.itemsFromDescription) {
+                    itemsFromDescription = function (fetchID, description, countBefore, countAfter) {
+                        var perfID = "fetchItemsFromDescription id=" + fetchID + " desc=" + description + " countBefore=" + countBefore + " countAfter=" + countAfter;
+                        profilerMarkStart(perfID);
+                        refreshHistory[++refreshHistoryPos % refreshHistory.length] = { kind: "itemsFromDescription", description: description, countBefore: countBefore, countAfter: countAfter };
+                        var result = listDataAdapter.itemsFromDescription(description, countBefore, countAfter);
+                        profilerMarkEnd(perfID);
+                        return result;
+                    };
+                }
+
+                var dataSourceID = ++uniqueID;
+
+                function profilerMarkStart(text) {
+                    var message = "WinJS.UI.VirtualizedDataSource:" + dataSourceID + ":" + text + ",StartTM";
+                    _WriteProfilerMark(message);
+                    _Log.log && _Log.log(message, "winjs vds", "perf");
+                }
+                function profilerMarkEnd(text) {
+                    var message = "WinJS.UI.VirtualizedDataSource:" + dataSourceID + ":" + text + ",StopTM";
+                    _WriteProfilerMark(message);
+                    _Log.log && _Log.log(message, "winjs vds", "perf");
+                }
+
+                function isNonNegativeNumber(n) {
+                    return (typeof n === "number") && n >= 0;
+                }
+
+                function isNonNegativeInteger(n) {
+                    return isNonNegativeNumber(n) && n === Math.floor(n);
+                }
+
+                function validateIndexReturned(index) {
+                    // Ensure that index is always undefined or a non-negative integer
+                    if (index === null) {
+                        index = undefined;
+                    } else if (index !== undefined && !isNonNegativeInteger(index)) {
+                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidIndexReturned", strings.invalidIndexReturned);
+                    }
+
+                    return index;
+                }
+
+                function validateCountReturned(count) {
+                    // Ensure that count is always undefined or a non-negative integer
+                    if (count === null) {
+                        count = undefined;
+                    } else if (count !== undefined && !isNonNegativeInteger(count) && count !== CountResult.unknown) {
+                        throw new _ErrorFromName("WinJS.UI.ListDataSource.InvalidCountReturned", strings.invalidCountReturned);
+                    }
+
+                    return count;
+                }
+
+                // Slot List
+
+                function createSlot() {
+                    var handle = (nextHandle++).toString(),
+                        slotNew = {
+                            handle: handle,
+                            item: null,
+                            itemNew: null,
+                            fetchListeners: null,
+                            cursorCount: 0,
+                            bindingMap: null
+                        };
+
+                    // Deliberately not initialized:
+                    //   - directFetchListeners
+
+                    handleMap[handle] = slotNew;
+
+                    return slotNew;
+                }
+
+                function createPrimarySlot() {
+                    return createSlot();
+                }
+
+                function insertSlot(slot, slotNext) {
+                    slot.prev = slotNext.prev;
+                    slot.next = slotNext;
+
+                    slot.prev.next = slot;
+                    slotNext.prev = slot;
+                }
+
+                function removeSlot(slot) {
+                    if (slot.lastInSequence) {
+                        delete slot.lastInSequence;
+                        slot.prev.lastInSequence = true;
+                    }
+                    if (slot.firstInSequence) {
+                        delete slot.firstInSequence;
+                        slot.next.firstInSequence = true;
+                    }
+                    slot.prev.next = slot.next;
+                    slot.next.prev = slot.prev;
+                }
+
+                function sequenceStart(slot) {
+                    while (!slot.firstInSequence) {
+                        slot = slot.prev;
+                    }
+
+                    return slot;
+                }
+
+                function sequenceEnd(slot) {
+                    while (!slot.lastInSequence) {
+                        slot = slot.next;
+                    }
+
+                    return slot;
+                }
+
+                // Does a little careful surgery to the slot sequence from slotFirst to slotLast before slotNext
+                function moveSequenceBefore(slotNext, slotFirst, slotLast) {
+                    slotFirst.prev.next = slotLast.next;
+                    slotLast.next.prev = slotFirst.prev;
+
+                    slotFirst.prev = slotNext.prev;
+                    slotLast.next = slotNext;
+
+                    slotFirst.prev.next = slotFirst;
+                    slotNext.prev = slotLast;
+
+                    return true;
+                }
+
+                // Does a little careful surgery to the slot sequence from slotFirst to slotLast after slotPrev
+                function moveSequenceAfter(slotPrev, slotFirst, slotLast) {
+                    slotFirst.prev.next = slotLast.next;
+                    slotLast.next.prev = slotFirst.prev;
+
+                    slotFirst.prev = slotPrev;
+                    slotLast.next = slotPrev.next;
+
+                    slotPrev.next = slotFirst;
+                    slotLast.next.prev = slotLast;
+
+                    return true;
+                }
+
+                function mergeSequences(slotPrev) {
+                    delete slotPrev.lastInSequence;
+                    delete slotPrev.next.firstInSequence;
+                }
+
+                function splitSequence(slotPrev) {
+                    var slotNext = slotPrev.next;
+
+                    slotPrev.lastInSequence = true;
+                    slotNext.firstInSequence = true;
+
+                    if (slotNext === slotListEnd) {
+                        // Clear slotListEnd's index, as that's now unknown
+                        changeSlotIndex(slotListEnd, undefined);
+                    }
+                }
+
+                // Inserts a slot in the middle of a sequence or between sequences.  If the latter, mergeWithPrev and mergeWithNext
+                // parameters specify whether to merge the slot with the previous sequence, or next, or neither.
+                function insertAndMergeSlot(slot, slotNext, mergeWithPrev, mergeWithNext) {
+                    insertSlot(slot, slotNext);
+
+                    var slotPrev = slot.prev;
+
+                    if (slotPrev.lastInSequence) {
+                        if (mergeWithPrev) {
+                            delete slotPrev.lastInSequence;
+                        } else {
+                            slot.firstInSequence = true;
+                        }
+
+                        if (mergeWithNext) {
+                            delete slotNext.firstInSequence;
+                        } else {
+                            slot.lastInSequence = true;
+                        }
+                    }
+                }
+
+                // Keys and Indices
+
+                function setSlotKey(slot, key) {
+                    slot.key = key;
+
+                    // Add the slot to the keyMap, so it is possible to quickly find the slot given its key
+                    keyMap[slot.key] = slot;
+                }
+
+                function setSlotIndex(slot, index, indexMapForSlot) {
+                    // Tolerate NaN, so clients can pass (undefined - 1) or (undefined + 1)
+                    if (+index === index) {
+                        slot.index = index;
+
+                        // Add the slot to the indexMap, so it is possible to quickly find the slot given its index
+                        indexMapForSlot[index] = slot;
+
+                        if (!indexUpdateDeferred) {
+                            // See if any sequences should be merged
+                            if (slot.firstInSequence && slot.prev && slot.prev.index === index - 1) {
+                                mergeSequences(slot.prev);
+                            }
+                            if (slot.lastInSequence && slot.next && slot.next.index === index + 1) {
+                                mergeSequences(slot);
+                            }
+                        }
+                    }
+                }
+
+                // Creates a new slot and adds it to the slot list before slotNext
+                function createAndAddSlot(slotNext, indexMapForSlot) {
+                    var slotNew = (indexMapForSlot === indexMap ? createPrimarySlot() : createSlot());
+
+                    insertSlot(slotNew, slotNext);
+
+                    return slotNew;
+                }
+
+                function createSlotSequence(slotNext, index, indexMapForSlot) {
+                    var slotNew = createAndAddSlot(slotNext, indexMapForSlot);
+
+                    slotNew.firstInSequence = true;
+                    slotNew.lastInSequence = true;
+
+                    setSlotIndex(slotNew, index, indexMapForSlot);
+
+                    return slotNew;
+                }
+
+                function createPrimarySlotSequence(slotNext, index) {
+                    return createSlotSequence(slotNext, index, indexMap);
+                }
+
+                function addSlotBefore(slotNext, indexMapForSlot) {
+                    var slotNew = createAndAddSlot(slotNext, indexMapForSlot);
+                    delete slotNext.firstInSequence;
+
+                    // See if we've bumped into the previous sequence
+                    if (slotNew.prev.index === slotNew.index - 1) {
+                        delete slotNew.prev.lastInSequence;
+                    } else {
+                        slotNew.firstInSequence = true;
+                    }
+
+                    setSlotIndex(slotNew, slotNext.index - 1, indexMapForSlot);
+
+                    return slotNew;
+                }
+
+                function addSlotAfter(slotPrev, indexMapForSlot) {
+                    var slotNew = createAndAddSlot(slotPrev.next, indexMapForSlot);
+                    delete slotPrev.lastInSequence;
+
+                    // See if we've bumped into the next sequence
+                    if (slotNew.next.index === slotNew.index + 1) {
+                        delete slotNew.next.firstInSequence;
+                    } else {
+                        slotNew.lastInSequence = true;
+                    }
+
+                    setSlotIndex(slotNew, slotPrev.index + 1, indexMapForSlot);
+
+                    return slotNew;
+                }
+
+                function reinsertSlot(slot, slotNext, mergeWithPrev, mergeWithNext) {
+                    insertAndMergeSlot(slot, slotNext, mergeWithPrev, mergeWithNext);
+                    keyMap[slot.key] = slot;
+                    if (slot.index !== undefined) {
+                        indexMap[slot.index] = slot;
+                    }
+                }
+
+                function removeSlotPermanently(slot) {
+                    removeSlot(slot);
+
+                    if (slot.key) {
+                        delete keyMap[slot.key];
+                    }
+                    if (slot.index !== undefined && indexMap[slot.index] === slot) {
+                        delete indexMap[slot.index];
+                    }
+
+                    var bindingMap = slot.bindingMap;
+                    for (var listBindingID in bindingMap) {
+                        var handle = bindingMap[listBindingID].handle;
+                        if (handle && handleMap[handle] === slot) {
+                            delete handleMap[handle];
+                        }
+                    }
+
+                    // Invalidating the slot's handle marks it as deleted
+                    if (handleMap[slot.handle] === slot) {
+                        delete handleMap[slot.handle];
+                    }
+                }
+
+                function slotPermanentlyRemoved(slot) {
+                    return !handleMap[slot.handle];
+                }
+
+                function successorFromIndex(index, indexMapForSlot, listStart, listEnd, skipPreviousIndex) {
+                    // Try the previous index
+                    var slotNext = (skipPreviousIndex ? null : indexMapForSlot[index - 1]);
+                    if (slotNext && (slotNext.next !== listEnd || listEnd.firstInSequence)) {
+                        // We want the successor
+                        slotNext = slotNext.next;
+                    } else {
+                        // Try the next index
+                        slotNext = indexMapForSlot[index + 1];
+                        if (!slotNext) {
+                            // Resort to a linear search
+                            slotNext = listStart.next;
+                            var lastSequenceStart;
+                            while (true) {
+                                if (slotNext.firstInSequence) {
+                                    lastSequenceStart = slotNext;
+                                }
+
+                                if (!(index >= slotNext.index) || slotNext === listEnd) {
+                                    break;
+                                }
+
+                                slotNext = slotNext.next;
+                            }
+
+                            if (slotNext === listEnd && !listEnd.firstInSequence) {
+                                // Return the last insertion point between sequences, or undefined if none
+                                slotNext = (lastSequenceStart && lastSequenceStart.index === undefined ? lastSequenceStart : undefined);
+                            }
+                        }
+                    }
+
+                    return slotNext;
+                }
+
+                // Slot Items
+
+                function isPlaceholder(slot) {
+                    return !slot.item && !slot.itemNew && slot !== slotListEnd;
+                }
+
+                function defineHandleProperty(item, handle) {
+                    Object.defineProperty(item, "handle", {
+                        value: handle,
+                        writable: false,
+                        enumerable: false,
+                        configurable: true
+                    });
+                }
+
+                function defineCommonItemProperties(item, slot, handle) {
+                    defineHandleProperty(item, handle);
+
+                    Object.defineProperty(item, "index", {
+                        get: function () {
+                            while (slot.slotMergedWith) {
+                                slot = slot.slotMergedWith;
+                            }
+
+                            return slot.index;
+                        },
+                        enumerable: false,
+                        configurable: true
+                    });
+                }
+
+                function validateData(data) {
+                    if (data === undefined) {
+                        return data;
+                    } else {
+                        // Convert the data object to JSON to enforce the constraints we want.  For example, we don't want
+                        // functions, arrays with extra properties, DOM objects, cyclic or acyclic graphs, or undefined values.
+                        var dataValidated = JSON.stringify(data);
+
+                        if (dataValidated === undefined) {
+                            throw new _ErrorFromName("WinJS.UI.ListDataSource.ObjectIsNotValidJson", strings.objectIsNotValidJson);
+                        }
+
+                        return dataValidated;
+                    }
+                }
+
+                function itemSignature(item) {
+                    return (
+                        listDataAdapter.itemSignature ?
+                            listDataAdapter.itemSignature(item.data) :
+                            validateData(item.data)
+                    );
+                }
+
+                function prepareSlotItem(slot) {
+                    var item = slot.itemNew;
+                    slot.itemNew = null;
+
+                    if (item) {
+                        item = Object.create(item);
+                        defineCommonItemProperties(item, slot, slot.handle);
+
+                        if (!listDataAdapter.compareByIdentity) {
+                            // Store the item signature or a stringified copy of the data for comparison later
+                            slot.signature = itemSignature(item);
+                        }
+                    }
+
+                    slot.item = item;
+
+                    delete slot.indexRequested;
+                    delete slot.keyRequested;
+                }
+
+                // Slot Caching
+
+                function slotRetained(slot) {
+                    return slot.bindingMap || slot.cursorCount > 0;
+                }
+
+                function slotRequested(slot) {
+                    return slotRetained(slot) || slot.fetchListeners || slot.directFetchListeners;
+                }
+
+                function slotLive(slot) {
+                    return slotRequested(slot) || (!slot.firstInSequence && slotRetained(slot.prev)) || (!slot.lastInSequence && slotRetained(slot.next)) ||
+                        (!itemsFromIndex && (
+                            (!slot.firstInSequence && slot.prev !== slotsStart && !(slot.prev.item || slot.prev.itemNew)) |
+                            (!slot.lastInSequence && slot.next !== slotListEnd && !(slot.next.item || slot.next.itemNew))
+                        ));
+                }
+
+                function deleteUnnecessarySlot(slot) {
+                    splitSequence(slot);
+                    removeSlotPermanently(slot);
+                }
+
+                function reduceReleasedSlotCount() {
+                    // Must not release slots while edits are queued, as undo queue might refer to them
+                    if (!editsQueued) {
+                        // If lastSlotReleased is no longer valid, use the end of the list instead
+                        if (!lastSlotReleased || slotPermanentlyRemoved(lastSlotReleased)) {
+                            lastSlotReleased = slotListEnd.prev;
+                        }
+
+                        // Now use the simple heuristic of walking outwards in both directions from lastSlotReleased until the
+                        // desired cache size is reached, then removing everything else.
+                        var slotPrev = lastSlotReleased.prev,
+                            slotNext = lastSlotReleased.next,
+                            releasedSlotsFound = 0;
+
+                        var considerDeletingSlot = function (slotToDelete) {
+                            if (slotToDelete !== slotListEnd && !slotLive(slotToDelete)) {
+                                if (releasedSlotsFound <= cacheSize) {
+                                    releasedSlotsFound++;
+                                } else {
+                                    deleteUnnecessarySlot(slotToDelete);
+                                }
+                            }
+                        };
+
+                        while (slotPrev || slotNext) {
+                            if (slotPrev) {
+                                var slotPrevToDelete = slotPrev;
+                                slotPrev = slotPrevToDelete.prev;
+                                if (slotPrevToDelete !== slotsStart) {
+                                    considerDeletingSlot(slotPrevToDelete);
+                                }
+                            }
+                            if (slotNext) {
+                                var slotNextToDelete = slotNext;
+                                slotNext = slotNextToDelete.next;
+                                if (slotNextToDelete !== slotsEnd) {
+                                    considerDeletingSlot(slotNextToDelete);
+                                }
+                            }
+                        }
+
+                        // Reset the count to zero, so this method is only called periodically
+                        releasedSlots = 0;
+                    }
+                }
+
+                function releaseSlotIfUnrequested(slot) {
+                    if (!slotRequested(slot)) {
+
+                        releasedSlots++;
+
+                        // Must not release slots while edits are queued, as undo queue might refer to them.  If a refresh is in
+                        // progress, retain all slots, just in case the user re-requests some of them before the refresh completes.
+                        if (!editsQueued && !refreshInProgress) {
+                            // Track which slot was released most recently
+                            lastSlotReleased = slot;
+
+                            // See if the number of released slots has exceeded the cache size.  In practice there will be more
+                            // live slots than retained slots, so this is just a heuristic to periodically shrink the cache.
+                            if (releasedSlots > cacheSize && !reduceReleasedSlotCountPosted) {
+                                reduceReleasedSlotCountPosted = true;
+                                Scheduler.schedule(function VDS_async_releaseSlotIfUnrequested() {
+                                    reduceReleasedSlotCountPosted = false;
+                                    reduceReleasedSlotCount();
+                                }, Scheduler.Priority.idle, null, "WinJS.UI.VirtualizedDataSource.releaseSlotIfUnrequested");
+                            }
+                        }
+                    }
+                }
+
+                // Notifications
+
+                function forEachBindingRecord(callback) {
+                    for (var listBindingID in bindingMap) {
+                        callback(bindingMap[listBindingID]);
+                    }
+                }
+
+                function forEachBindingRecordOfSlot(slot, callback) {
+                    for (var listBindingID in slot.bindingMap) {
+                        callback(slot.bindingMap[listBindingID].bindingRecord, listBindingID);
+                    }
+                }
+
+                function handlerToNotify(bindingRecord) {
+                    if (!bindingRecord.notificationsSent) {
+                        bindingRecord.notificationsSent = true;
+
+                        if (bindingRecord.notificationHandler.beginNotifications) {
+                            bindingRecord.notificationHandler.beginNotifications();
+                        }
+                    }
+                    return bindingRecord.notificationHandler;
+                }
+
+                function finishNotifications() {
+                    if (!editsInProgress && !dataNotificationsInProgress) {
+                        forEachBindingRecord(function (bindingRecord) {
+                            if (bindingRecord.notificationsSent) {
+                                bindingRecord.notificationsSent = false;
+
+                                if (bindingRecord.notificationHandler.endNotifications) {
+                                    bindingRecord.notificationHandler.endNotifications();
+                                }
+                            }
+                        });
+                    }
+                }
+
+                function handleForBinding(slot, listBindingID) {
+                    var bindingMap = slot.bindingMap;
+                    if (bindingMap) {
+                        var slotBinding = bindingMap[listBindingID];
+                        if (slotBinding) {
+                            var handle = slotBinding.handle;
+                            if (handle) {
+                                return handle;
+                            }
+                        }
+                    }
+                    return slot.handle;
+                }
+
+                function itemForBinding(item, handle) {
+                    if (item && item.handle !== handle) {
+                        item = Object.create(item);
+                        defineHandleProperty(item, handle);
+                    }
+                    return item;
+                }
+
+                function changeCount(count) {
+                    var oldCount = knownCount;
+                    knownCount = count;
+
+                    forEachBindingRecord(function (bindingRecord) {
+                        if (bindingRecord.notificationHandler && bindingRecord.notificationHandler.countChanged) {
+                            handlerToNotify(bindingRecord).countChanged(knownCount, oldCount);
+                        }
+                    });
+                }
+
+                function sendIndexChangedNotifications(slot, indexOld) {
+                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
+                        if (bindingRecord.notificationHandler.indexChanged) {
+                            handlerToNotify(bindingRecord).indexChanged(handleForBinding(slot, listBindingID), slot.index, indexOld);
+                        }
+                    });
+                }
+
+                function changeSlotIndex(slot, index) {
+                    var indexOld = slot.index;
+
+                    if (indexOld !== undefined && indexMap[indexOld] === slot) {
+                        // Remove the slot's old index from the indexMap
+                        delete indexMap[indexOld];
+                    }
+
+                    // Tolerate NaN, so clients can pass (undefined - 1) or (undefined + 1)
+                    if (+index === index) {
+                        setSlotIndex(slot, index, indexMap);
+                    } else if (+indexOld === indexOld) {
+                        delete slot.index;
+                    } else {
+                        // If neither the new index or the old index is defined then there was no index changed.
+                        return;
+                    }
+
+                    sendIndexChangedNotifications(slot, indexOld);
+                }
+
+                function insertionNotificationRecipients(slot, slotPrev, slotNext, mergeWithPrev, mergeWithNext) {
+                    var bindingMapRecipients = {};
+
+                    // Start with the intersection of the bindings for the two adjacent slots
+                    if ((mergeWithPrev || !slotPrev.lastInSequence) && (mergeWithNext || !slotNext.firstInSequence)) {
+                        if (slotPrev === slotsStart) {
+                            if (slotNext === slotListEnd) {
+                                // Special case: if the list was empty, broadcast the insertion to all ListBindings with
+                                // notification handlers.
+                                for (var listBindingID in bindingMap) {
+                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
+                                }
+                            } else {
+                                // Include every binding on the next slot
+                                for (var listBindingID in slotNext.bindingMap) {
+                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
+                                }
+                            }
+                        } else if (slotNext === slotListEnd || slotNext.bindingMap) {
+                            for (var listBindingID in slotPrev.bindingMap) {
+                                if (slotNext === slotListEnd || slotNext.bindingMap[listBindingID]) {
+                                    bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
+                                }
+                            }
+                        }
+                    }
+
+                    // Use the union of that result with the bindings for the slot being inserted
+                    for (var listBindingID in slot.bindingMap) {
+                        bindingMapRecipients[listBindingID] = bindingMap[listBindingID];
+                    }
+
+                    return bindingMapRecipients;
+                }
+
+                function sendInsertedNotification(slot) {
+                    var slotPrev = slot.prev,
+                        slotNext = slot.next,
+                        bindingMapRecipients = insertionNotificationRecipients(slot, slotPrev, slotNext),
+                        listBindingID;
+
+                    for (listBindingID in bindingMapRecipients) {
+                        var bindingRecord = bindingMapRecipients[listBindingID];
+                        if (bindingRecord.notificationHandler) {
+                            handlerToNotify(bindingRecord).inserted(bindingRecord.itemPromiseFromKnownSlot(slot),
+                                slotPrev.lastInSequence || slotPrev === slotsStart ? null : handleForBinding(slotPrev, listBindingID),
+                                slotNext.firstInSequence || slotNext === slotListEnd ? null : handleForBinding(slotNext, listBindingID)
+                            );
+                        }
+                    }
+                }
+
+                function changeSlot(slot) {
+                    var itemOld = slot.item;
+                    prepareSlotItem(slot);
+
+                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
+                        var handle = handleForBinding(slot, listBindingID);
+                        handlerToNotify(bindingRecord).changed(itemForBinding(slot.item, handle), itemForBinding(itemOld, handle));
+                    });
+                }
+
+                function moveSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext, skipNotifications) {
+                    var slotMoveAfter = slotMoveBefore.prev,
+                        listBindingID;
+
+                    // If the slot is being moved before or after itself, adjust slotMoveAfter or slotMoveBefore accordingly. If
+                    // nothing is going to change in the slot list, don't send a notification.
+                    if (slotMoveBefore === slot) {
+                        if (!slot.firstInSequence || !mergeWithPrev) {
+                            return;
+                        }
+                        slotMoveBefore = slot.next;
+                    } else if (slotMoveAfter === slot) {
+                        if (!slot.lastInSequence || !mergeWithNext) {
+                            return;
+                        }
+                        slotMoveAfter = slot.prev;
+                    }
+
+                    if (!skipNotifications) {
+                        // Determine which bindings to notify
+
+                        var bindingMapRecipients = insertionNotificationRecipients(slot, slotMoveAfter, slotMoveBefore, mergeWithPrev, mergeWithNext);
+
+                        // Send the notification before the move
+                        for (listBindingID in bindingMapRecipients) {
+                            var bindingRecord = bindingMapRecipients[listBindingID];
+                            handlerToNotify(bindingRecord).moved(bindingRecord.itemPromiseFromKnownSlot(slot),
+                                ((slotMoveAfter.lastInSequence || slotMoveAfter === slot.prev) && !mergeWithPrev) || slotMoveAfter === slotsStart ? null : handleForBinding(slotMoveAfter, listBindingID),
+                                ((slotMoveBefore.firstInSequence || slotMoveBefore === slot.next) && !mergeWithNext) || slotMoveBefore === slotListEnd ? null : handleForBinding(slotMoveBefore, listBindingID)
+                            );
+                        }
+
+                        // If a ListBinding cursor is at the slot that's moving, adjust the cursor
+                        forEachBindingRecord(function (bindingRecord) {
+                            bindingRecord.adjustCurrentSlot(slot);
+                        });
+                    }
+
+                    removeSlot(slot);
+                    insertAndMergeSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext);
+                }
+
+                function deleteSlot(slot, mirage) {
+                    completeFetchPromises(slot, true);
+
+                    forEachBindingRecordOfSlot(slot, function (bindingRecord, listBindingID) {
+                        handlerToNotify(bindingRecord).removed(handleForBinding(slot, listBindingID), mirage);
+                    });
+
+                    // If a ListBinding cursor is at the slot that's being removed, adjust the cursor
+                    forEachBindingRecord(function (bindingRecord) {
+                        bindingRecord.adjustCurrentSlot(slot);
+                    });
+
+                    removeSlotPermanently(slot);
+                }
+
+                function deleteMirageSequence(slot) {
+                    // Remove the slots in order
+
+                    while (!slot.firstInSequence) {
+                        slot = slot.prev;
+                    }
+
+                    var last;
+                    do {
+                        last = slot.lastInSequence;
+
+                        var slotNext = slot.next;
+                        deleteSlot(slot, true);
+                        slot = slotNext;
+                    } while (!last);
+                }
+
+                // Deferred Index Updates
+
+                // Returns the index of the slot taking into account any outstanding index updates
+                function adjustedIndex(slot) {
+                    var undefinedIndex;
+
+                    if (!slot) {
+                        return undefinedIndex;
+                    }
+
+                    var delta = 0;
+                    while (!slot.firstInSequence) {
+                        delta++;
+                        slot = slot.prev;
+                    }
+
+                    return (
+                        typeof slot.indexNew === "number" ?
+                            slot.indexNew + delta :
+                        typeof slot.index === "number" ?
+                            slot.index + delta :
+                            undefinedIndex
+                    );
+                }
+
+                // Updates the new index of the first slot in each sequence after the given slot
+                function updateNewIndicesAfterSlot(slot, indexDelta) {
+                    // Adjust all the indexNews after this slot
+                    for (slot = slot.next; slot; slot = slot.next) {
+                        if (slot.firstInSequence) {
+                            var indexNew = (slot.indexNew !== undefined ? slot.indexNew : slot.index);
+                            if (indexNew !== undefined) {
+                                slot.indexNew = indexNew + indexDelta;
+                            }
+                        }
+                    }
+
+                    // Adjust the overall count
+                    countDelta += indexDelta;
+
+                    indexUpdateDeferred = true;
+
+                    // Increment currentRefreshID so any outstanding fetches don't cause trouble.  If a refresh is in progress,
+                    // restart it (which will also increment currentRefreshID).
+                    if (refreshInProgress) {
+                        beginRefresh();
+                    } else {
+                        currentRefreshID++;
+                    }
+                }
+
+                // Updates the new index of the given slot if necessary, and all subsequent new indices
+                function updateNewIndices(slot, indexDelta) {
+                    // If this slot is at the start of a sequence, transfer the indexNew
+                    if (slot.firstInSequence) {
+                        var indexNew;
+
+                        if (indexDelta < 0) {
+                            // The given slot is about to be removed
+                            indexNew = slot.indexNew;
+                            if (indexNew !== undefined) {
+                                delete slot.indexNew;
+                            } else {
+                                indexNew = slot.index;
+                            }
+
+                            if (!slot.lastInSequence) {
+                                // Update the next slot now
+                                slot = slot.next;
+                                if (indexNew !== undefined) {
+                                    slot.indexNew = indexNew;
+                                }
+                            }
+                        } else {
+                            // The given slot was just inserted
+                            if (!slot.lastInSequence) {
+                                var slotNext = slot.next;
+
+                                indexNew = slotNext.indexNew;
+                                if (indexNew !== undefined) {
+                                    delete slotNext.indexNew;
+                                } else {
+                                    indexNew = slotNext.index;
+                                }
+
+                                if (indexNew !== undefined) {
+                                    slot.indexNew = indexNew;
+                                }
+                            }
+                        }
+                    }
+
+                    updateNewIndicesAfterSlot(slot, indexDelta);
+                }
+
+                // Updates the new index of the first slot in each sequence after the given new index
+                function updateNewIndicesFromIndex(index, indexDelta) {
+                    for (var slot = slotsStart; slot !== slotListEnd; slot = slot.next) {
+                        var indexNew = slot.indexNew;
+
+                        if (indexNew !== undefined && index <= indexNew) {
+                            updateNewIndicesAfterSlot(slot, indexDelta);
+                            break;
+                        }
+                    }
+                }
+
+                // Adjust the indices of all slots to be consistent with any indexNew properties, and strip off the indexNews
+                function updateIndices() {
+                    var slot,
+                        slotFirstInSequence,
+                        indexNew;
+
+                    for (slot = slotsStart; ; slot = slot.next) {
+                        if (slot.firstInSequence) {
+                            slotFirstInSequence = slot;
+                            if (slot.indexNew !== undefined) {
+                                indexNew = slot.indexNew;
+                                delete slot.indexNew;
+                                if (isNaN(indexNew)) {
+                                    break;
+                                }
+                            } else {
+                                indexNew = slot.index;
+                            }
+
+                            // See if this sequence should be merged with the previous one
+                            if (slot !== slotsStart && slot.prev.index === indexNew - 1) {
+                                mergeSequences(slot.prev);
+                            }
+                        }
+
+                        if (slot.lastInSequence) {
+                            var index = indexNew;
+                            for (var slotUpdate = slotFirstInSequence; slotUpdate !== slot.next; slotUpdate = slotUpdate.next) {
+                                if (index !== slotUpdate.index) {
+                                    changeSlotIndex(slotUpdate, index);
+                                }
+                                if (+index === index) {
+                                    index++;
+                                }
+                            }
+                        }
+
+                        if (slot === slotListEnd) {
+                            break;
+                        }
+                    }
+
+                    // Clear any indices on slots that were moved adjacent to slots without indices
+                    for (; slot !== slotsEnd; slot = slot.next) {
+                        if (slot.index !== undefined && slot !== slotListEnd) {
+                            changeSlotIndex(slot, undefined);
+                        }
+                    }
+
+                    indexUpdateDeferred = false;
+
+                    if (countDelta && +knownCount === knownCount) {
+                        if (getCountPromise) {
+                            getCountPromise.reset();
+                        } else {
+                            changeCount(knownCount + countDelta);
+                        }
+
+                        countDelta = 0;
+                    }
+                }
+
+                // Fetch Promises
+
+                function createFetchPromise(slot, listenersProperty, listenerID, listBindingID, onComplete) {
+                    if (slot.item) {
+                        return new Promise(function (complete) {
+                            if (onComplete) {
+                                onComplete(complete, slot.item);
+                            } else {
+                                complete(slot.item);
+                            }
+                        });
+                    } else {
+                        var listener = {
+                            listBindingID: listBindingID,
+                            retained: false
+                        };
+
+                        if (!slot[listenersProperty]) {
+                            slot[listenersProperty] = {};
+                        }
+                        slot[listenersProperty][listenerID] = listener;
+
+                        listener.promise = new Promise(function (complete, error) {
+                            listener.complete = (onComplete ? function (item) {
+                                onComplete(complete, item);
+                            } : complete);
+                            listener.error = error;
+                        }, function () {
+                            // By now the slot might have been merged with another
+
+                            while (slot.slotMergedWith) {
+                                slot = slot.slotMergedWith;
+                            }
+
+                            var fetchListeners = slot[listenersProperty];
+                            if (fetchListeners) {
+                                delete fetchListeners[listenerID];
+
+                                // See if there are any other listeners
+                                if (Object.keys(fetchListeners).length > 0) {
+                                    return;
+                                }
+                                delete slot[listenersProperty];
+                            }
+                            releaseSlotIfUnrequested(slot);
+                        });
+
+                        return listener.promise;
+                    }
+                }
+
+                function completePromises(item, listeners) {
+                    for (var listenerID in listeners) {
+                        listeners[listenerID].complete(item);
+                    }
+                }
+
+                function completeFetchPromises(slot, completeSynchronously) {
+                    var fetchListeners = slot.fetchListeners,
+                        directFetchListeners = slot.directFetchListeners;
+
+                    if (fetchListeners || directFetchListeners) {
+                        prepareSlotItem(slot);
+
+                        // By default, complete asynchronously to minimize reentrancy
+
+                        var item = slot.item;
+
+                        var completeOrQueuePromises = function (listeners) {
+                            if (completeSynchronously) {
+                                completePromises(item, listeners);
+                            } else {
+                                fetchCompleteCallbacks.push(function () {
+                                    completePromises(item, listeners);
+                                });
+                            }
+                        };
+
+                        if (directFetchListeners) {
+                            slot.directFetchListeners = null;
+                            completeOrQueuePromises(directFetchListeners);
+                        }
+
+                        if (fetchListeners) {
+                            slot.fetchListeners = null;
+                            completeOrQueuePromises(fetchListeners);
+                        }
+
+                        releaseSlotIfUnrequested(slot);
+                    }
+                }
+
+                function callFetchCompleteCallbacks() {
+                    var callbacks = fetchCompleteCallbacks;
+
+                    // Clear fetchCompleteCallbacks first to avoid reentrancy problems
+                    fetchCompleteCallbacks = [];
+
+                    for (var i = 0, len = callbacks.length; i < len; i++) {
+                        callbacks[i]();
+                    }
+                }
+
+                function returnDirectFetchError(slot, error) {
+                    var directFetchListeners = slot.directFetchListeners;
+                    if (directFetchListeners) {
+                        slot.directFetchListeners = null;
+
+                        for (var listenerID in directFetchListeners) {
+                            directFetchListeners[listenerID].error(error);
+                        }
+
+                        releaseSlotIfUnrequested(slot);
+                    }
+                }
+
+                // Item Requests
+
+                function requestSlot(slot) {
+                    // Ensure that there's a slot on either side of each requested item
+                    if (slot.firstInSequence) {
+                        addSlotBefore(slot, indexMap);
+                    }
+                    if (slot.lastInSequence) {
+                        addSlotAfter(slot, indexMap);
+                    }
+
+                    // If the item has already been fetched, prepare it now to be returned to the client
+                    if (slot.itemNew) {
+                        prepareSlotItem(slot);
+                    }
+
+                    // Start a new fetch if necessary
+                    postFetch();
+
+                    return slot;
+                }
+
+                function requestSlotBefore(slotNext) {
+                    // First, see if the previous slot already exists
+                    if (!slotNext.firstInSequence) {
+                        var slotPrev = slotNext.prev;
+
+                        // Next, see if the item is known to not exist
+                        return (slotPrev === slotsStart ? null : requestSlot(slotPrev));
+                    }
+
+                    return requestSlot(addSlotBefore(slotNext, indexMap));
+                }
+
+                function requestSlotAfter(slotPrev) {
+                    // First, see if the next slot already exists
+                    if (!slotPrev.lastInSequence) {
+                        var slotNext = slotPrev.next;
+
+                        // Next, see if the item is known to not exist
+                        return (slotNext === slotListEnd ? null : requestSlot(slotNext));
+                    }
+
+                    return requestSlot(addSlotAfter(slotPrev, indexMap));
+                }
+
+                function itemDirectlyFromSlot(slot) {
+                    // Return a complete promise for a non-existent slot
+                    return (
+                        slot ?
+                            createFetchPromise(slot, "directFetchListeners", (nextListenerID++).toString()) :
+                            Promise.wrap(null)
+                    );
+                }
+
+                function validateKey(key) {
+                    if (typeof key !== "string" || !key) {
+                        throw new _ErrorFromName("WinJS.UI.ListDataSource.KeyIsInvalid", strings.keyIsInvalid);
+                    }
+                }
+
+                function createSlotForKey(key) {
+                    var slot = createPrimarySlotSequence(slotsEnd);
+
+                    setSlotKey(slot, key);
+                    slot.keyRequested = true;
+
+                    return slot;
+                }
+
+                function slotFromKey(key, hints) {
+                    validateKey(key);
+
+                    var slot = keyMap[key];
+
+                    if (!slot) {
+                        slot = createSlotForKey(key);
+                        slot.hints = hints;
+                    }
+
+                    return requestSlot(slot);
+                }
+
+                function slotFromIndex(index) {
+                    if (typeof index !== "number" || index < 0) {
+                        throw new _ErrorFromName("WinJS.UI.ListDataSource.IndexIsInvalid", strings.indexIsInvalid);
+                    }
+
+                    if (slotListEnd.index <= index) {
+                        return null;
+                    }
+
+                    var slot = indexMap[index];
+
+                    if (!slot) {
+                        var slotNext = successorFromIndex(index, indexMap, slotsStart, slotListEnd);
+
+                        if (!slotNext) {
+                            // The complete list has been observed, and this index isn't a part of it; a refresh may be necessary
+                            return null;
+                        }
+
+                        if (slotNext === slotListEnd && index >= slotListEnd) {
+                            // Clear slotListEnd's index, as that's now unknown
+                            changeSlotIndex(slotListEnd, undefined);
+                        }
+
+                        // Create a new slot and start a request for it
+                        if (slotNext.prev.index === index - 1) {
+                            slot = addSlotAfter(slotNext.prev, indexMap);
+                        } else if (slotNext.index === index + 1) {
+                            slot = addSlotBefore(slotNext, indexMap);
+                        } else {
+                            slot = createPrimarySlotSequence(slotNext, index);
+                        }
+                    }
+
+                    if (!slot.item) {
+                        slot.indexRequested = true;
+                    }
+
+                    return requestSlot(slot);
+                }
+
+                function slotFromDescription(description) {
+                    // Create a new slot and start a request for it
+                    var slot = createPrimarySlotSequence(slotsEnd);
+
+                    slot.description = description;
+
+                    return requestSlot(slot);
+                }
+
+                // Status
+                var that = this;
+                function setStatus(statusNew) {
+                    statusPending = statusNew;
+                    if (status !== statusPending) {
+                        var dispatch = function () {
+                            statusChangePosted = false;
+
+                            if (status !== statusPending) {
+                                status = statusPending;
+                                that.dispatchEvent(statusChangedEvent, status);
+                            }
+                        };
+                        if (statusPending === DataSourceStatus.failure) {
+                            dispatch();
+                        } else if (!statusChangePosted) {
+                            statusChangePosted = true;
+
+                            // Delay the event to filter out rapid changes
+                            _Global.setTimeout(dispatch, 40);
+                        }
+                    }
+                }
+
+                // Slot Fetching
+
+                function slotFetchInProgress(slot) {
+                    var fetchID = slot.fetchID;
+                    return fetchID && fetchesInProgress[fetchID];
+                }
+
+                function setFetchID(slot, fetchID) {
+                    slot.fetchID = fetchID;
+                }
+
+                function newFetchID() {
+                    var fetchID = nextFetchID;
+                    nextFetchID++;
+
+                    fetchesInProgress[fetchID] = true;
+
+                    return fetchID;
+                }
+
+                function setFetchIDs(slot, countBefore, countAfter) {
+                    var fetchID = newFetchID();
+                    setFetchID(slot, fetchID);
+
+                    var slotBefore = slot;
+                    while (!slotBefore.firstInSequence && countBefore > 0) {
+                        slotBefore = slotBefore.prev;
+                        countBefore--;
+                        setFetchID(slotBefore, fetchID);
+                    }
+
+                    var slotAfter = slot;
+                    while (!slotAfter.lastInSequence && countAfter > 0) {
+                        slotAfter = slotAfter.next;
+                        countAfter--;
+                        setFetchID(slotAfter, fetchID);
+                    }
+
+                    return fetchID;
+                }
+
+                // Adds markers on behalf of the data adapter if their presence can be deduced
+                function addMarkers(fetchResult) {
+                    var items = fetchResult.items,
+                        offset = fetchResult.offset,
+                        totalCount = fetchResult.totalCount,
+                        absoluteIndex = fetchResult.absoluteIndex,
+                        atStart = fetchResult.atStart,
+                        atEnd = fetchResult.atEnd;
+
+                    if (isNonNegativeNumber(absoluteIndex)) {
+                        if (isNonNegativeNumber(totalCount)) {
+                            var itemsLength = items.length;
+                            if (absoluteIndex - offset + itemsLength === totalCount) {
+                                atEnd = true;
+                            }
+                        }
+
+                        if (offset === absoluteIndex) {
+                            atStart = true;
+                        }
+                    }
+
+                    if (atStart) {
+                        items.unshift(startMarker);
+                        fetchResult.offset++;
+                    }
+
+                    if (atEnd) {
+                        items.push(endMarker);
+                    }
+                }
+
+                function resultsValid(slot, refreshID, fetchID) {
+                    // This fetch has completed, whatever it has returned
+                    delete fetchesInProgress[fetchID];
+
+                    if (refreshID !== currentRefreshID || slotPermanentlyRemoved(slot)) {
+                        // This information is out of date, or the slot has since been discarded
+
+                        postFetch();
+                        return false;
+                    }
+
+                    return true;
+                }
+
+                function fetchItems(slot, fetchID, promiseItems, index) {
+                    var refreshID = currentRefreshID;
+                    promiseItems.then(function (fetchResult) {
+                        if (fetchResult.items && fetchResult.items.length) {
+                            var perfID = "itemsFetched id=" + fetchID + " count=" + fetchResult.items.length;
+                            profilerMarkStart(perfID);
+                            if (resultsValid(slot, refreshID, fetchID)) {
+                                if (+index === index) {
+                                    fetchResult.absoluteIndex = index;
+                                }
+                                addMarkers(fetchResult);
+                                processResults(slot, fetchResult.items, fetchResult.offset, fetchResult.totalCount, fetchResult.absoluteIndex);
+                            }
+                            profilerMarkEnd(perfID);
+                        } else {
+                            return Promise.wrapError(new _ErrorFromName(FetchError.doesNotExist));
+                        }
+                    }).then(null, function (error) {
+                        if (resultsValid(slot, refreshID, fetchID)) {
+                            processErrorResult(slot, error);
+                        }
+                    });
+                }
+
+                function fetchItemsForIndex(indexRequested, slot, fetchID, promiseItems) {
+                    var refreshID = currentRefreshID;
+                    promiseItems.then(function (fetchResult) {
+                        if (fetchResult.items && fetchResult.items.length) {
+                            var perfID = "itemsFetched id=" + fetchID + " count=" + fetchResult.items.length;
+                            profilerMarkStart(perfID);
+                            if (resultsValid(slot, refreshID, fetchID)) {
+                                fetchResult.absoluteIndex = indexRequested;
+                                addMarkers(fetchResult);
+                                processResultsForIndex(indexRequested, slot, fetchResult.items, fetchResult.offset, fetchResult.totalCount, fetchResult.absoluteIndex);
+                            }
+                            profilerMarkEnd(perfID);
+                        } else {
+                            return Promise.wrapError(new _ErrorFromName(FetchError.doesNotExist));
+                        }
+                    }).then(null, function () {
+                        if (resultsValid(slot, refreshID, fetchID)) {
+                            processErrorResultForIndex(indexRequested, slot, refreshID);
+                        }
+                    });
+                }
+
+                function fetchItemsFromStart(slot, count) {
+                    var fetchID = setFetchIDs(slot, 0, count - 1);
+                    if (itemsFromStart) {
+                        fetchItems(slot, fetchID, itemsFromStart(fetchID, count), 0);
+                    } else {
+                        fetchItems(slot, fetchID, itemsFromIndex(fetchID, 0, 0, count - 1), 0);
+                    }
+                }
+
+                function fetchItemsFromEnd(slot, count) {
+                    var fetchID = setFetchIDs(slot, count - 1, 0);
+                    fetchItems(slot, fetchID, itemsFromEnd(fetchID, count));
+                }
+
+                function fetchItemsFromKey(slot, countBefore, countAfter) {
+                    var fetchID = setFetchIDs(slot, countBefore, countAfter);
+                    fetchItems(slot, fetchID, itemsFromKey(fetchID, slot.key, countBefore, countAfter, slot.hints));
+                }
+
+                function fetchItemsFromIndex(slot, countBefore, countAfter) {
+                    var index = slot.index;
+
+                    // Don't ask for items with negative indices
+                    if (countBefore > index) {
+                        countBefore = index;
+                    }
+
+                    if (itemsFromIndex) {
+                        var fetchID = setFetchIDs(slot, countBefore, countAfter);
+                        fetchItems(slot, fetchID, itemsFromIndex(fetchID, index, countBefore, countAfter), index);
+                    } else {
+                        // If the slot key is known, we just need to request the surrounding items
+                        if (slot.key) {
+                            fetchItemsFromKey(slot, countBefore, countAfter);
+                        } else {
+                            // Search for the slot with the closest index that has a known key (using the start of the list as a
+                            // last resort).
+                            var slotClosest = slotsStart,
+                                closestDelta = index + 1,
+                                slotSearch,
+                                delta;
+
+                            // First search backwards
+                            for (slotSearch = slot.prev; slotSearch !== slotsStart; slotSearch = slotSearch.prev) {
+                                if (slotSearch.index !== undefined && slotSearch.key) {
+                                    delta = index - slotSearch.index;
+                                    if (closestDelta > delta) {
+                                        closestDelta = delta;
+                                        slotClosest = slotSearch;
+                                    }
+                                    break;
+                                }
+                            }
+
+                            // Then search forwards
+                            for (slotSearch = slot.next; slotSearch !== slotListEnd; slotSearch = slotSearch.next) {
+                                if (slotSearch.index !== undefined && slotSearch.key) {
+                                    delta = slotSearch.index - index;
+                                    if (closestDelta > delta) {
+                                        closestDelta = delta;
+                                        slotClosest = slotSearch;
+                                    }
+                                    break;
+                                }
+                            }
+
+                            if (slotClosest === slotsStart) {
+                                var fetchID = setFetchIDs(slot, 0, index + 1);
+                                fetchItemsForIndex(0, slot, fetchID, itemsFromStart(fetchID, index + 1));
+                            } else {
+                                var fetchBefore = Math.max(slotClosest.index - index, 0);
+                                var fetchAfter = Math.max(index - slotClosest.index, 0);
+                                var fetchID = setFetchIDs(slotClosest, fetchBefore, fetchAfter);
+                                fetchItemsForIndex(slotClosest.index, slot, fetchID, itemsFromKey(fetchID,
+                                    slotClosest.key,
+                                    fetchBefore,
+                                    fetchAfter,
+                                    slot.hints
+                                ));
+                            }
+                        }
+                    }
+                }
+
+                function fetchItemsFromDescription(slot, countBefore, countAfter) {
+                    var fetchID = setFetchIDs(slot, countBefore, countAfter);
+                    fetchItems(slot, fetchID, itemsFromDescription(fetchID, slot.description, countBefore, countAfter));
+                }
+
+                function fetchItemsForAllSlots() {
+                    if (!refreshInProgress) {
+                        var slotFirstPlaceholder,
+                            placeholderCount,
+                            fetchInProgress = false,
+                            fetchesInProgress = false,
+                            slotRequestedByKey,
+                            requestedKeyOffset,
+                            slotRequestedByDescription,
+                            requestedDescriptionOffset,
+                            slotRequestedByIndex,
+                            requestedIndexOffset;
+
+                        for (var slot = slotsStart.next; slot !== slotsEnd;) {
+                            var slotNext = slot.next;
+
+                            if (slot !== slotListEnd && isPlaceholder(slot)) {
+                                fetchesInProgress = true;
+
+                                if (!slotFirstPlaceholder) {
+                                    slotFirstPlaceholder = slot;
+                                    placeholderCount = 1;
+                                } else {
+                                    placeholderCount++;
+                                }
+
+                                if (slotFetchInProgress(slot)) {
+                                    fetchInProgress = true;
+                                }
+
+                                if (slot.keyRequested && !slotRequestedByKey) {
+                                    slotRequestedByKey = slot;
+                                    requestedKeyOffset = placeholderCount - 1;
+                                }
+
+                                if (slot.description !== undefined && !slotRequestedByDescription) {
+                                    slotRequestedByDescription = slot;
+                                    requestedDescriptionOffset = placeholderCount - 1;
+                                }
+
+                                if (slot.indexRequested && !slotRequestedByIndex) {
+                                    slotRequestedByIndex = slot;
+                                    requestedIndexOffset = placeholderCount - 1;
+                                }
+
+                                if (slot.lastInSequence || slotNext === slotsEnd || !isPlaceholder(slotNext)) {
+                                    if (fetchInProgress) {
+                                        fetchInProgress = false;
+                                    } else {
+                                        resultsProcessed = false;
+
+                                        // Start a new fetch for this placeholder sequence
+
+                                        // Prefer fetches in terms of a known item
+                                        if (!slotFirstPlaceholder.firstInSequence && slotFirstPlaceholder.prev.key && itemsFromKey) {
+                                            fetchItemsFromKey(slotFirstPlaceholder.prev, 0, placeholderCount);
+                                        } else if (!slot.lastInSequence && slotNext.key && itemsFromKey) {
+                                            fetchItemsFromKey(slotNext, placeholderCount, 0);
+                                        } else if (slotFirstPlaceholder.prev === slotsStart && !slotFirstPlaceholder.firstInSequence && (itemsFromStart || itemsFromIndex)) {
+                                            fetchItemsFromStart(slotFirstPlaceholder, placeholderCount);
+                                        } else if (slotNext === slotListEnd && !slot.lastInSequence && itemsFromEnd) {
+                                            fetchItemsFromEnd(slot, placeholderCount);
+                                        } else if (slotRequestedByKey) {
+                                            fetchItemsFromKey(slotRequestedByKey, requestedKeyOffset, placeholderCount - 1 - requestedKeyOffset);
+                                        } else if (slotRequestedByDescription) {
+                                            fetchItemsFromDescription(slotRequestedByDescription, requestedDescriptionOffset, placeholderCount - 1 - requestedDescriptionOffset);
+                                        } else if (slotRequestedByIndex) {
+                                            fetchItemsFromIndex(slotRequestedByIndex, requestedIndexOffset, placeholderCount - 1 - requestedIndexOffset);
+                                        } else if (typeof slotFirstPlaceholder.index === "number") {
+                                            fetchItemsFromIndex(slotFirstPlaceholder, placeholderCount - 1, 0);
+                                        } else {
+                                            // There is no way to fetch anything in this sequence
+                                            deleteMirageSequence(slotFirstPlaceholder);
+                                        }
+
+                                        if (resultsProcessed) {
+                                            // A re-entrant fetch might have altered the slots list - start again
+                                            postFetch();
+                                            return;
+                                        }
+
+                                        if (refreshInProgress) {
+                                            // A re-entrant fetch might also have caused a refresh
+                                            return;
+                                        }
+                                    }
+
+                                    slotFirstPlaceholder = slotRequestedByIndex = slotRequestedByKey = null;
+                                }
+                            }
+
+                            slot = slotNext;
+                        }
+
+                        setStatus(fetchesInProgress ? DataSourceStatus.waiting : DataSourceStatus.ready);
+                    }
+                }
+
+                function postFetch() {
+                    if (!fetchesPosted) {
+                        fetchesPosted = true;
+                        Scheduler.schedule(function VDS_async_postFetch() {
+                            fetchesPosted = false;
+                            fetchItemsForAllSlots();
+
+                            // A mirage sequence might have been removed
+                            finishNotifications();
+                        }, Scheduler.Priority.max, null, "WinJS.UI.ListDataSource._fetch");
+                    }
+                }
+
+                // Fetch Result Processing
+
+                function itemChanged(slot) {
+                    var itemNew = slot.itemNew;
+
+                    if (!itemNew) {
+                        return false;
+                    }
+
+                    var item = slot.item;
+
+                    for (var property in item) {
+                        switch (property) {
+                            case "data":
+                                // This is handled below
+                                break;
+
+                            default:
+                                if (item[property] !== itemNew[property]) {
+                                    return true;
+                                }
+                                break;
+                        }
+                    }
+
+                    return (
+                        listDataAdapter.compareByIdentity ?
+                            item.data !== itemNew.data :
+                            slot.signature !== itemSignature(itemNew)
+                    );
+                }
+
+                function changeSlotIfNecessary(slot) {
+                    if (!slotRequested(slot)) {
+                        // There's no need for any notifications, just delete the old item
+                        slot.item = null;
+                    } else if (itemChanged(slot)) {
+                        changeSlot(slot);
+                    } else {
+                        slot.itemNew = null;
+                    }
+                }
+
+                function updateSlotItem(slot) {
+                    if (slot.item) {
+                        changeSlotIfNecessary(slot);
+                    } else {
+                        completeFetchPromises(slot);
+                    }
+                }
+
+                function updateSlot(slot, item) {
+                    if (!slot.key) {
+                        setSlotKey(slot, item.key);
+                    }
+                    slot.itemNew = item;
+
+                    updateSlotItem(slot);
+                }
+
+                function sendMirageNotifications(slot, slotToDiscard, listBindingIDsToDelete) {
+                    var bindingMap = slotToDiscard.bindingMap;
+                    if (bindingMap) {
+                        for (var listBindingID in listBindingIDsToDelete) {
+                            if (bindingMap[listBindingID]) {
+                                var fetchListeners = slotToDiscard.fetchListeners;
+                                for (var listenerID in fetchListeners) {
+                                    var listener = fetchListeners[listenerID];
+
+                                    if (listener.listBindingID === listBindingID && listener.retained) {
+                                        delete fetchListeners[listenerID];
+                                        listener.complete(null);
+                                    }
+                                }
+
+                                var bindingRecord = bindingMap[listBindingID].bindingRecord;
+                                handlerToNotify(bindingRecord).removed(handleForBinding(slotToDiscard, listBindingID), true, handleForBinding(slot, listBindingID));
+
+                                // A re-entrant call to release from the removed handler might have cleared slotToDiscard.bindingMap
+                                if (slotToDiscard.bindingMap) {
+                                    delete slotToDiscard.bindingMap[listBindingID];
+                                }
+                            }
+                        }
+                    }
+                }
+
+                function mergeSlots(slot, slotToDiscard) {
+                    // This shouldn't be called on a slot that has a pending change notification
+                    // Only one of the two slots should have a key
+                    // If slotToDiscard is about to acquire an index, send the notifications now; in rare cases, multiple
+                    // indexChanged notifications will be sent for a given item during a refresh, but that's fine.
+                    if (slot.index !== slotToDiscard.index) {
+                        // If slotToDiscard has a defined index, that should have been transferred already
+                        var indexOld = slotToDiscard.index;
+                        slotToDiscard.index = slot.index;
+                        sendIndexChangedNotifications(slotToDiscard, indexOld);
+                    }
+
+                    slotToDiscard.slotMergedWith = slot;
+
+                    // Transfer the slotBindings from slotToDiscard to slot
+                    var bindingMap = slotToDiscard.bindingMap;
+                    for (var listBindingID in bindingMap) {
+                        if (!slot.bindingMap) {
+                            slot.bindingMap = {};
+                        }
+
+                        var slotBinding = bindingMap[listBindingID];
+
+                        if (!slotBinding.handle) {
+                            slotBinding.handle = slotToDiscard.handle;
+                        }
+                        handleMap[slotBinding.handle] = slot;
+
+                        slot.bindingMap[listBindingID] = slotBinding;
+                    }
+
+                    // Update any ListBinding cursors pointing to slotToDiscard
+                    forEachBindingRecord(function (bindingRecord) {
+                        bindingRecord.adjustCurrentSlot(slotToDiscard, slot);
+                    });
+
+                    // See if the item needs to be transferred from slotToDiscard to slot
+                    var item = slotToDiscard.itemNew || slotToDiscard.item;
+                    if (item) {
+                        item = Object.create(item);
+                        defineCommonItemProperties(item, slot, slot.handle);
+                        updateSlot(slot, item);
+                    }
+
+                    // Transfer the fetch listeners from slotToDiscard to slot, or complete them if item is known
+                    if (slot.item) {
+                        if (slotToDiscard.directFetchListeners) {
+                            fetchCompleteCallbacks.push(function () {
+                                completePromises(slot.item, slotToDiscard.directFetchListeners);
+                            });
+                        }
+                        if (slotToDiscard.fetchListeners) {
+                            fetchCompleteCallbacks.push(function () {
+                                completePromises(slot.item, slotToDiscard.fetchListeners);
+                            });
+                        }
+                    } else {
+                        var listenerID;
+
+                        for (listenerID in slotToDiscard.directFetchListeners) {
+                            if (!slot.directFetchListeners) {
+                                slot.directFetchListeners = {};
+                            }
+                            slot.directFetchListeners[listenerID] = slotToDiscard.directFetchListeners[listenerID];
+                        }
+
+                        for (listenerID in slotToDiscard.fetchListeners) {
+                            if (!slot.fetchListeners) {
+                                slot.fetchListeners = {};
+                            }
+                            slot.fetchListeners[listenerID] = slotToDiscard.fetchListeners[listenerID];
+                        }
+                    }
+
+                    // This might be the first time this slot's item can be prepared
+                    if (slot.itemNew) {
+                        completeFetchPromises(slot);
+                    }
+
+                    // Give slotToDiscard an unused handle so it appears to be permanently removed
+                    slotToDiscard.handle = (nextHandle++).toString();
+
+                    splitSequence(slotToDiscard);
+                    removeSlotPermanently(slotToDiscard);
+                }
+
+                function mergeSlotsAndItem(slot, slotToDiscard, item) {
+                    if (slotToDiscard && slotToDiscard.key) {
+                        if (!item) {
+                            item = slotToDiscard.itemNew || slotToDiscard.item;
+                        }
+
+                        // Free up the key for the promoted slot
+                        delete slotToDiscard.key;
+                        delete keyMap[item.key];
+
+                        slotToDiscard.itemNew = null;
+                        slotToDiscard.item = null;
+                    }
+
+                    if (item) {
+                        updateSlot(slot, item);
+                    }
+
+                    if (slotToDiscard) {
+                        mergeSlots(slot, slotToDiscard);
+       

<TRUNCATED>

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


Mime
View raw message