in src/Clients/Web/winjs/js/winjs.js [28426:29353]
function concludeRefresh() {
beginRefreshCount = 0;
refreshHistory = new Array(100);
refreshHistoryPos = -1;
indexUpdateDeferred = true;
keyFetchIDs = {};
var i,
j,
slot,
slotPrev,
slotNext,
slotBefore,
slotAfter,
slotRefresh,
slotExisting,
slotFirstInSequence,
sequenceCountOld,
sequencesOld = [],
sequenceOld,
sequenceOldPrev,
sequenceOldBestMatch,
sequenceCountNew,
sequencesNew = [],
sequenceNew,
index,
offset;
// Assign a sequence number to each refresh slot
sequenceCountNew = 0;
for (slotRefresh = refreshStart; slotRefresh; slotRefresh = slotRefresh.next) {
slotRefresh.sequenceNumber = sequenceCountNew;
if (slotRefresh.firstInSequence) {
slotFirstInSequence = slotRefresh;
}
if (slotRefresh.lastInSequence) {
sequencesNew[sequenceCountNew] = {
first: slotFirstInSequence,
last: slotRefresh,
matchingItems: 0
};
sequenceCountNew++;
}
}
// Remove unnecessary information from main slot list, and update the items
lastSlotReleased = null;
releasedSlots = 0;
for (slot = slotsStart.next; slot !== slotsEnd;) {
slotRefresh = refreshKeyMap[slot.key];
slotNext = slot.next;
if (slot !== slotListEnd) {
if (!slotLive(slot)) {
// Some more items might have been released since the refresh started. Strip them away from the
// main slot list, as they'll just get in the way from now on. Since we're discarding these, but
// don't know if they're actually going away, split the sequence as our starting assumption must be
// that the items on either side are in separate sequences.
deleteUnnecessarySlot(slot);
} else if (slot.key && !slotRefresh) {
// Remove items that have been deleted (or moved far away) and send removed notifications
deleteSlot(slot, false);
} else if (refreshCount === 0 || (slot.indexRequested && slot.index >= refreshCount)) {
// Remove items that can't exist in the list and send mirage removed notifications
deleteSlot(slot, true);
} else if (slot.item || slot.keyRequested) {
// Store the new item; this value will be compared with that stored in slot.item later
slot.itemNew = slotRefresh.item;
} else {
// Clear keys and items that have never been observed by client
if (slot.key) {
if (!slot.keyRequested) {
delete keyMap[slot.key];
delete slot.key;
}
slot.itemNew = null;
}
}
}
slot = slotNext;
}
// Placeholders generated by itemsAtIndex should not move. Match these to items now if possible, or merge them
// with existing items if necessary.
for (slot = slotsStart.next; slot !== slotListEnd;) {
slotNext = slot.next;
if (slot.indexRequested) {
slotRefresh = refreshIndexMap[slot.index];
if (slotRefresh) {
matchSlotForRefresh(slotFromSlotRefresh(slotRefresh), slot, slotRefresh);
}
}
slot = slotNext;
}
// Match old sequences to new sequences
var bestMatch,
bestMatchCount,
previousBestMatch = 0,
newSequenceCounts = [],
slotIndexRequested,
sequenceIndexEnd,
sequenceOldEnd;
sequenceCountOld = 0;
for (slot = slotsStart; slot !== slotsEnd; slot = slot.next) {
if (slot.firstInSequence) {
slotFirstInSequence = slot;
slotIndexRequested = null;
for (i = 0; i < sequenceCountNew; i++) {
newSequenceCounts[i] = 0;
}
}
if (slot.indexRequested) {
slotIndexRequested = slot;
}
slotRefresh = slotRefreshFromSlot(slot);
if (slotRefresh) {
newSequenceCounts[slotRefresh.sequenceNumber]++;
}
if (slot.lastInSequence) {
// Determine which new sequence is the best match for this old one. Use a simple greedy algorithm to
// ensure the relative ordering of matched sequences is the same; out-of-order sequences will require
// move notifications.
bestMatchCount = 0;
for (i = previousBestMatch; i < sequenceCountNew; i++) {
if (bestMatchCount < newSequenceCounts[i]) {
bestMatchCount = newSequenceCounts[i];
bestMatch = i;
}
}
sequenceOld = {
first: slotFirstInSequence,
last: slot,
sequenceNew: (bestMatchCount > 0 ? sequencesNew[bestMatch] : undefined),
matchingItems: bestMatchCount
};
if (slotIndexRequested) {
sequenceOld.indexRequested = true;
sequenceOld.stationarySlot = slotIndexRequested;
}
sequencesOld[sequenceCountOld] = sequenceOld;
if (slot === slotListEnd) {
sequenceIndexEnd = sequenceCountOld;
sequenceOldEnd = sequenceOld;
}
sequenceCountOld++;
if (sequencesNew[bestMatch].first.index !== undefined) {
previousBestMatch = bestMatch;
}
}
}
// Special case: split the old start into a separate sequence if the new start isn't its best match
if (sequencesOld[0].sequenceNew !== sequencesNew[0]) {
splitSequence(slotsStart);
sequencesOld[0].first = slotsStart.next;
sequencesOld.unshift({
first: slotsStart,
last: slotsStart,
sequenceNew: sequencesNew[0],
matchingItems: 1
});
sequenceIndexEnd++;
sequenceCountOld++;
}
var listEndObserved = !slotListEnd.firstInSequence;
// Special case: split the old end into a separate sequence if the new end isn't its best match
if (sequenceOldEnd.sequenceNew !== sequencesNew[sequenceCountNew - 1]) {
splitSequence(slotListEnd.prev);
sequenceOldEnd.last = slotListEnd.prev;
sequenceIndexEnd++;
sequencesOld.splice(sequenceIndexEnd, 0, {
first: slotListEnd,
last: slotListEnd,
sequenceNew: sequencesNew[sequenceCountNew - 1],
matchingItems: 1
});
sequenceCountOld++;
sequenceOldEnd = sequencesOld[sequenceIndexEnd];
}
// Map new sequences to old sequences
for (i = 0; i < sequenceCountOld; i++) {
sequenceNew = sequencesOld[i].sequenceNew;
if (sequenceNew && sequenceNew.matchingItems < sequencesOld[i].matchingItems) {
sequenceNew.matchingItems = sequencesOld[i].matchingItems;
sequenceNew.sequenceOld = sequencesOld[i];
}
}
// The old end must always be the best match for the new end (if the new end is also the new start, they will
// be merged below).
sequencesNew[sequenceCountNew - 1].sequenceOld = sequenceOldEnd;
sequenceOldEnd.stationarySlot = slotListEnd;
// The old start must always be the best match for the new start
sequencesNew[0].sequenceOld = sequencesOld[0];
sequencesOld[0].stationarySlot = slotsStart;
// Merge additional indexed old sequences when possible
// First do a forward pass
for (i = 0; i <= sequenceIndexEnd; i++) {
sequenceOld = sequencesOld[i];
if (sequenceOld.sequenceNew && (sequenceOldBestMatch = sequenceOld.sequenceNew.sequenceOld) === sequenceOldPrev && sequenceOldPrev.last !== slotListEnd) {
mergeSequencesForRefresh(sequenceOldBestMatch.last);
sequenceOldBestMatch.last = sequenceOld.last;
delete sequencesOld[i];
} else {
sequenceOldPrev = sequenceOld;
}
}
// Now do a reverse pass
sequenceOldPrev = null;
for (i = sequenceIndexEnd; i >= 0; i--) {
sequenceOld = sequencesOld[i];
// From this point onwards, some members of sequencesOld may be undefined
if (sequenceOld) {
if (sequenceOld.sequenceNew && (sequenceOldBestMatch = sequenceOld.sequenceNew.sequenceOld) === sequenceOldPrev && sequenceOld.last !== slotListEnd) {
mergeSequencesForRefresh(sequenceOld.last);
sequenceOldBestMatch.first = sequenceOld.first;
delete sequencesOld[i];
} else {
sequenceOldPrev = sequenceOld;
}
}
}
// Since we may have forced the list end into a separate sequence, the mergedForRefresh flag may be incorrect
if (listEndObserved) {
delete slotListEnd.mergedForRefresh;
}
var sequencePairsToMerge = [];
// Find unchanged sequences without indices that can be merged with existing sequences without move
// notifications.
for (i = sequenceIndexEnd + 1; i < sequenceCountOld; i++) {
sequenceOld = sequencesOld[i];
if (sequenceOld && (!sequenceOld.sequenceNew || sequenceOld.sequenceNew.sequenceOld !== sequenceOld)) {
// If the order of the known items in the sequence is unchanged, then the sequence probably has not
// moved, but we now know where it belongs relative to at least one other sequence.
var orderPreserved = true,
slotRefreshFirst = null,
slotRefreshLast = null,
sequenceLength = 0;
slotRefresh = slotRefreshFromSlot(sequenceOld.first);
if (slotRefresh) {
slotRefreshFirst = slotRefreshLast = slotRefresh;
sequenceLength = 1;
}
for (slot = sequenceOld.first; slot !== sequenceOld.last; slot = slot.next) {
var slotRefreshNext = slotRefreshFromSlot(slot.next);
if (slotRefresh && slotRefreshNext && (slotRefresh.lastInSequence || slotRefresh.next !== slotRefreshNext)) {
orderPreserved = false;
break;
}
if (slotRefresh && !slotRefreshFirst) {
slotRefreshFirst = slotRefreshLast = slotRefresh;
}
if (slotRefreshNext && slotRefreshFirst) {
slotRefreshLast = slotRefreshNext;
sequenceLength++;
}
slotRefresh = slotRefreshNext;
}
// If the stationary sequence has indices, verify that there is enough space for this sequence - if
// not, then something somewhere has moved after all.
if (orderPreserved && slotRefreshFirst && slotRefreshFirst.index !== undefined) {
var indexBefore;
if (!slotRefreshFirst.firstInSequence) {
slotBefore = slotFromSlotRefresh(slotRefreshFirst.prev);
if (slotBefore) {
indexBefore = slotBefore.index;
}
}
var indexAfter;
if (!slotRefreshLast.lastInSequence) {
slotAfter = slotFromSlotRefresh(slotRefreshLast.next);
if (slotAfter) {
indexAfter = slotAfter.index;
}
}
if ((!slotAfter || slotAfter.lastInSequence || slotAfter.mergedForRefresh) &&
(indexBefore === undefined || indexAfter === undefined || indexAfter - indexBefore - 1 >= sequenceLength)) {
sequenceOld.locationJustDetermined = true;
// Mark the individual refresh slots as not requiring move notifications
for (slotRefresh = slotRefreshFirst; ; slotRefresh = slotRefresh.next) {
slotRefresh.locationJustDetermined = true;
if (slotRefresh === slotRefreshLast) {
break;
}
}
// Store any adjacent placeholders so they can be merged once the moves and insertions have
// been processed.
var slotFirstInSequence = slotFromSlotRefresh(slotRefreshFirst),
slotLastInSequence = slotFromSlotRefresh(slotRefreshLast);
sequencePairsToMerge.push({
slotBeforeSequence: (slotFirstInSequence.firstInSequence ? null : slotFirstInSequence.prev),
slotFirstInSequence: slotFirstInSequence,
slotLastInSequence: slotLastInSequence,
slotAfterSequence: (slotLastInSequence.lastInSequence ? null : slotLastInSequence.next)
});
}
}
}
}
// Remove placeholders in old sequences that don't map to new sequences (and don't contain requests for a
// specific index or key), as they no longer have meaning.
for (i = 0; i < sequenceCountOld; i++) {
sequenceOld = sequencesOld[i];
if (sequenceOld && !sequenceOld.indexRequested && !sequenceOld.locationJustDetermined && (!sequenceOld.sequenceNew || sequenceOld.sequenceNew.sequenceOld !== sequenceOld)) {
sequenceOld.sequenceNew = null;
slot = sequenceOld.first;
var sequenceEndReached;
do {
sequenceEndReached = (slot === sequenceOld.last);
slotNext = slot.next;
if (slot !== slotsStart && slot !== slotListEnd && slot !== slotsEnd && !slot.item && !slot.keyRequested) {
deleteSlot(slot, true);
if (sequenceOld.first === slot) {
if (sequenceOld.last === slot) {
delete sequencesOld[i];
break;
} else {
sequenceOld.first = slot.next;
}
} else if (sequenceOld.last === slot) {
sequenceOld.last = slot.prev;
}
}
slot = slotNext;
} while (!sequenceEndReached);
}
}
// Locate boundaries of new items in new sequences
for (i = 0; i < sequenceCountNew; i++) {
sequenceNew = sequencesNew[i];
for (slotRefresh = sequenceNew.first; !slotFromSlotRefresh(slotRefresh) && !slotRefresh.lastInSequence; slotRefresh = slotRefresh.next) {
/*@empty*/
}
if (slotRefresh.lastInSequence && !slotFromSlotRefresh(slotRefresh)) {
sequenceNew.firstInner = sequenceNew.lastInner = null;
} else {
sequenceNew.firstInner = slotRefresh;
for (slotRefresh = sequenceNew.last; !slotFromSlotRefresh(slotRefresh) ; slotRefresh = slotRefresh.prev) {
/*@empty*/
}
sequenceNew.lastInner = slotRefresh;
}
}
// Determine which items to move
for (i = 0; i < sequenceCountNew; i++) {
sequenceNew = sequencesNew[i];
if (sequenceNew && sequenceNew.firstInner) {
sequenceOld = sequenceNew.sequenceOld;
if (sequenceOld) {
// Number the slots in each new sequence with their offset in the corresponding old sequence (or
// undefined if in a different old sequence).
var ordinal = 0;
for (slot = sequenceOld.first; true; slot = slot.next, ordinal++) {
slotRefresh = slotRefreshFromSlot(slot);
if (slotRefresh && slotRefresh.sequenceNumber === sequenceNew.firstInner.sequenceNumber) {
slotRefresh.ordinal = ordinal;
}
if (slot.lastInSequence) {
break;
}
}
// Determine longest subsequence of items that are in the same order before and after
var piles = [];
for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
ordinal = slotRefresh.ordinal;
if (ordinal !== undefined) {
var searchFirst = 0,
searchLast = piles.length - 1;
while (searchFirst <= searchLast) {
var searchMidpoint = Math.floor(0.5 * (searchFirst + searchLast));
if (piles[searchMidpoint].ordinal < ordinal) {
searchFirst = searchMidpoint + 1;
} else {
searchLast = searchMidpoint - 1;
}
}
piles[searchFirst] = slotRefresh;
if (searchFirst > 0) {
slotRefresh.predecessor = piles[searchFirst - 1];
}
}
if (slotRefresh === sequenceNew.lastInner) {
break;
}
}
// The items in the longest ordered subsequence don't move; everything else does
var stationaryItems = [],
stationaryItemCount = piles.length;
slotRefresh = piles[stationaryItemCount - 1];
for (j = stationaryItemCount; j--;) {
slotRefresh.stationary = true;
stationaryItems[j] = slotRefresh;
slotRefresh = slotRefresh.predecessor;
}
sequenceOld.stationarySlot = slotFromSlotRefresh(stationaryItems[0]);
// Try to match new items before the first stationary item to placeholders
slotRefresh = stationaryItems[0];
slot = slotFromSlotRefresh(slotRefresh);
slotPrev = slot.prev;
var sequenceBoundaryReached = slot.firstInSequence;
while (!slotRefresh.firstInSequence) {
slotRefresh = slotRefresh.prev;
slotExisting = slotFromSlotRefresh(slotRefresh);
if (!slotExisting || slotRefresh.locationJustDetermined) {
// Find the next placeholder walking backwards
while (!sequenceBoundaryReached && slotPrev !== slotsStart) {
slot = slotPrev;
slotPrev = slot.prev;
sequenceBoundaryReached = slot.firstInSequence;
if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
break;
}
}
}
}
// Try to match new items between stationary items to placeholders
for (j = 0; j < stationaryItemCount - 1; j++) {
slotRefresh = stationaryItems[j];
slot = slotFromSlotRefresh(slotRefresh);
var slotRefreshStop = stationaryItems[j + 1],
slotRefreshMergePoint = null,
slotStop = slotFromSlotRefresh(slotRefreshStop),
slotExisting;
// Find all the new items
slotNext = slot.next;
for (slotRefresh = slotRefresh.next; slotRefresh !== slotRefreshStop && !slotRefreshMergePoint && slot !== slotStop; slotRefresh = slotRefresh.next) {
slotExisting = slotFromSlotRefresh(slotRefresh);
if (!slotExisting || slotRefresh.locationJustDetermined) {
// Find the next placeholder
while (slotNext !== slotStop) {
// If a merge point is reached, match the remainder of the placeholders by walking backwards
if (slotNext.mergedForRefresh) {
slotRefreshMergePoint = slotRefresh.prev;
break;
}
slot = slotNext;
slotNext = slot.next;
if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
break;
}
}
}
}
// Walk backwards to the first merge point if necessary
if (slotRefreshMergePoint) {
slotPrev = slotStop.prev;
for (slotRefresh = slotRefreshStop.prev; slotRefresh !== slotRefreshMergePoint && slotStop !== slot; slotRefresh = slotRefresh.prev) {
slotExisting = slotFromSlotRefresh(slotRefresh);
if (!slotExisting || slotRefresh.locationJustDetermined) {
// Find the next placeholder walking backwards
while (slotPrev !== slot) {
slotStop = slotPrev;
slotPrev = slotStop.prev;
if (updateSlotForRefresh(slotExisting, slotStop, slotRefresh)) {
break;
}
}
}
}
}
// Delete remaining placeholders, sending notifications
while (slotNext !== slotStop) {
slot = slotNext;
slotNext = slot.next;
if (slot !== slotsStart && isPlaceholder(slot) && !slot.keyRequested) {
// This might occur due to two sequences - requested by different clients - being
// merged. However, since only sequences with indices are merged, if this placehholder
// is no longer necessary, it means an item actually was removed, so this doesn't count
// as a mirage.
deleteSlot(slot);
}
}
}
// Try to match new items after the last stationary item to placeholders
slotRefresh = stationaryItems[stationaryItemCount - 1];
slot = slotFromSlotRefresh(slotRefresh);
slotNext = slot.next;
sequenceBoundaryReached = slot.lastInSequence;
while (!slotRefresh.lastInSequence) {
slotRefresh = slotRefresh.next;
slotExisting = slotFromSlotRefresh(slotRefresh);
if (!slotExisting || slotRefresh.locationJustDetermined) {
// Find the next placeholder
while (!sequenceBoundaryReached && slotNext !== slotListEnd) {
slot = slotNext;
slotNext = slot.next;
sequenceBoundaryReached = slot.lastInSequence;
if (updateSlotForRefresh(slotExisting, slot, slotRefresh)) {
break;
}
}
}
}
}
}
}
// Move items and send notifications
for (i = 0; i < sequenceCountNew; i++) {
sequenceNew = sequencesNew[i];
if (sequenceNew.firstInner) {
slotPrev = null;
for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
slot = slotFromSlotRefresh(slotRefresh);
if (slot) {
if (!slotRefresh.stationary) {
var slotMoveBefore,
mergeWithPrev = false,
mergeWithNext = false;
if (slotPrev) {
slotMoveBefore = slotPrev.next;
mergeWithPrev = true;
} else {
// The first item will be inserted before the first stationary item, so find that now
var slotRefreshStationary;
for (slotRefreshStationary = sequenceNew.firstInner; !slotRefreshStationary.stationary && slotRefreshStationary !== sequenceNew.lastInner; slotRefreshStationary = slotRefreshStationary.next) {
/*@empty*/
}
if (!slotRefreshStationary.stationary) {
// There are no stationary items, as all the items are moving from another old
// sequence.
index = slotRefresh.index;
// Find the best place to insert the new sequence
if (index === 0) {
// Index 0 is a special case
slotMoveBefore = slotsStart.next;
mergeWithPrev = true;
} else if (index === undefined) {
slotMoveBefore = slotsEnd;
} else {
// Use a linear search; unlike successorFromIndex, prefer the last insertion
// point between sequences over the precise index
slotMoveBefore = slotsStart.next;
var lastSequenceStart = null;
while (true) {
if (slotMoveBefore.firstInSequence) {
lastSequenceStart = slotMoveBefore;
}
if ((index < slotMoveBefore.index && lastSequenceStart) || slotMoveBefore === slotListEnd) {
break;
}
slotMoveBefore = slotMoveBefore.next;
}
if (!slotMoveBefore.firstInSequence && lastSequenceStart) {
slotMoveBefore = lastSequenceStart;
}
}
} else {
slotMoveBefore = slotFromSlotRefresh(slotRefreshStationary);
mergeWithNext = true;
}
}
// Preserve merge boundaries
if (slot.mergedForRefresh) {
delete slot.mergedForRefresh;
if (!slot.lastInSequence) {
slot.next.mergedForRefresh = true;
}
}
mergeWithPrev = mergeWithPrev || slotRefresh.mergeWithPrev;
mergeWithNext = mergeWithNext || slotRefresh.mergeWithNext;
var skipNotifications = slotRefresh.locationJustDetermined;
moveSlot(slot, slotMoveBefore, mergeWithPrev, mergeWithNext, skipNotifications);
if (skipNotifications && mergeWithNext) {
// Since this item was moved without a notification, this is an implicit merge of
// sequences. Mark the item's successor as mergedForRefresh.
slotMoveBefore.mergedForRefresh = true;
}
}
slotPrev = slot;
}
if (slotRefresh === sequenceNew.lastInner) {
break;
}
}
}
}
// Insert new items (with new indices) and send notifications
for (i = 0; i < sequenceCountNew; i++) {
sequenceNew = sequencesNew[i];
if (sequenceNew.firstInner) {
slotPrev = null;
for (slotRefresh = sequenceNew.firstInner; true; slotRefresh = slotRefresh.next) {
slot = slotFromSlotRefresh(slotRefresh);
if (!slot) {
var slotInsertBefore;
if (slotPrev) {
slotInsertBefore = slotPrev.next;
} else {
// The first item will be inserted *before* the first old item, so find that now
var slotRefreshOld;
for (slotRefreshOld = sequenceNew.firstInner; !slotFromSlotRefresh(slotRefreshOld) ; slotRefreshOld = slotRefreshOld.next) {
/*@empty*/
}
slotInsertBefore = slotFromSlotRefresh(slotRefreshOld);
}
// Create a new slot for the item
slot = addNewSlotFromRefresh(slotRefresh, slotInsertBefore, !!slotPrev);
var slotRefreshNext = slotRefreshFromSlot(slotInsertBefore);
if (!slotInsertBefore.mergedForRefresh && (!slotRefreshNext || !slotRefreshNext.locationJustDetermined)) {
prepareSlotItem(slot);
// Send the notification after the insertion
sendInsertedNotification(slot);
}
}
slotPrev = slot;
if (slotRefresh === sequenceNew.lastInner) {
break;
}
}
}
}
// Rebuild the indexMap from scratch, so it is possible to detect colliding indices
indexMap = [];
// Send indexChanged and changed notifications
var indexFirst = -1;
for (slot = slotsStart, offset = 0; slot !== slotsEnd; offset++) {
var slotNext = slot.next;
if (slot.firstInSequence) {
slotFirstInSequence = slot;
offset = 0;
}
if (indexFirst === undefined) {
var indexNew = indexForRefresh(slot);
if (indexNew !== undefined) {
indexFirst = indexNew - offset;
}
}
// See if the next slot would cause a contradiction, in which case split the sequences
if (indexFirst !== undefined && !slot.lastInSequence) {
var indexNewNext = indexForRefresh(slot.next);
if (indexNewNext !== undefined && indexNewNext !== indexFirst + offset + 1) {
splitSequence(slot);
// 'Move' the items in-place individually, so move notifications are sent. In rare cases, this
// will result in multiple move notifications being sent for a given item, but that's fine.
var first = true;
for (var slotMove = slot.next, lastInSequence = false; !lastInSequence && slotMove !== slotListEnd;) {
var slotMoveNext = slotMove.next;
lastInSequence = slotMove.lastInSequence;
moveSlot(slotMove, slotMoveNext, !first, false);
first = false;
slotMove = slotMoveNext;
}
}
}
if (slot.lastInSequence) {
index = indexFirst;
for (var slotUpdate = slotFirstInSequence; slotUpdate !== slotNext;) {
var slotUpdateNext = slotUpdate.next;
if (index >= refreshCount && slotUpdate !== slotListEnd) {
deleteSlot(slotUpdate, true);
} else {
var slotWithIndex = indexMap[index];
if (index !== slotUpdate.index) {
delete indexMap[index];
changeSlotIndex(slotUpdate, index);
} else if (+index === index && indexMap[index] !== slotUpdate) {
indexMap[index] = slotUpdate;
}
if (slotUpdate.itemNew) {
updateSlotItem(slotUpdate);
}
if (slotWithIndex) {
// Two slots' indices have collided - merge them
if (slotUpdate.key) {
sendMirageNotifications(slotUpdate, slotWithIndex, slotUpdate.bindingMap);
mergeSlots(slotUpdate, slotWithIndex);
if (+index === index) {
indexMap[index] = slotUpdate;
}
} else {
sendMirageNotifications(slotWithIndex, slotUpdate, slotWithIndex.bindingMap);
mergeSlots(slotWithIndex, slotUpdate);
if (+index === index) {
indexMap[index] = slotWithIndex;
}
}
}
if (+index === index) {
index++;
}
}
slotUpdate = slotUpdateNext;
}
indexFirst = undefined;
}
slot = slotNext;
}
// See if any sequences need to be moved and/or merged
var indexMax = -2,
listEndReached;
for (slot = slotsStart, offset = 0; slot !== slotsEnd; offset++) {
var slotNext = slot.next;
if (slot.firstInSequence) {
slotFirstInSequence = slot;
offset = 0;
}
// Clean up during this pass
delete slot.mergedForRefresh;
if (slot.lastInSequence) {
// Move sequence if necessary
if (slotFirstInSequence.index === undefined) {
slotBefore = slotFirstInSequence.prev;
var slotRefreshBefore;
if (slotBefore && (slotRefreshBefore = slotRefreshFromSlot(slotBefore)) && !slotRefreshBefore.lastInSequence &&
(slotRefresh = slotRefreshFromSlot(slot)) && slotRefresh.prev === slotRefreshBefore) {
moveSequenceAfter(slotBefore, slotFirstInSequence, slot);
mergeSequences(slotBefore);
} else if (slot !== slotListEnd && !listEndReached) {
moveSequenceBefore(slotsEnd, slotFirstInSequence, slot);
}
} else {
if (indexMax < slot.index && !listEndReached) {
indexMax = slot.index;
} else {
// Find the correct insertion point
for (slotAfter = slotsStart.next; slotAfter.index < slot.index; slotAfter = slotAfter.next) {
/*@empty*/
}
// Move the items individually, so move notifications are sent
for (var slotMove = slotFirstInSequence; slotMove !== slotNext;) {
var slotMoveNext = slotMove.next;
slotRefresh = slotRefreshFromSlot(slotMove);
moveSlot(slotMove, slotAfter, slotAfter.prev.index === slotMove.index - 1, slotAfter.index === slotMove.index + 1, slotRefresh && slotRefresh.locationJustDetermined);
slotMove = slotMoveNext;
}
}
// Store slotBefore here since the sequence might have just been moved
slotBefore = slotFirstInSequence.prev;
// See if this sequence should be merged with the previous one
if (slotBefore && slotBefore.index === slotFirstInSequence.index - 1) {
mergeSequences(slotBefore);
}
}
}
if (slot === slotListEnd) {
listEndReached = true;
}
slot = slotNext;
}
indexUpdateDeferred = false;
// Now that all the sequences have been moved, merge any colliding slots
mergeSequencePairs(sequencePairsToMerge);
// Send countChanged notification
if (refreshCount !== undefined && refreshCount !== knownCount) {
changeCount(refreshCount);
}
finishNotifications();
// Before discarding the refresh slot list, see if any fetch requests can be completed by pretending each range
// of refresh slots is an incoming array of results.
var fetchResults = [];
for (i = 0; i < sequenceCountNew; i++) {
sequenceNew = sequencesNew[i];
var results = [];
slot = null;
offset = 0;
var slotOffset;
for (slotRefresh = sequenceNew.first; true; slotRefresh = slotRefresh.next, offset++) {
if (slotRefresh === refreshStart) {
results.push(startMarker);
} else if (slotRefresh === refreshEnd) {
results.push(endMarker);
} else {
results.push(slotRefresh.item);
if (!slot) {
slot = slotFromSlotRefresh(slotRefresh);
slotOffset = offset;
}
}
if (slotRefresh.lastInSequence) {
break;
}
}
if (slot) {
fetchResults.push({
slot: slot,
results: results,
offset: slotOffset
});
}
}
resetRefreshState();
refreshInProgress = false;
// Complete any promises for newly obtained items
callFetchCompleteCallbacks();
// Now process the 'extra' results from the refresh list
for (i = 0; i < fetchResults.length; i++) {
var fetchResult = fetchResults[i];
processResults(fetchResult.slot, fetchResult.results, fetchResult.offset, knownCount, fetchResult.slot.index);
}
if (refreshSignal) {
var signal = refreshSignal;
refreshSignal = null;
signal.complete();
}
// Finally, kick-start fetches for any remaining placeholders
postFetch();
}