@checksub_team/peaks_timeline 2.3.0-alpha.0 → 2.3.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/peaks.js +473 -251
- package/src/components/axis.js +24 -13
- package/src/components/line-group.js +1 -1
- package/src/components/line-groups.js +0 -32
- package/src/components/line-indicator.js +70 -20
- package/src/components/mode-layer.js +67 -7
- package/src/components/playhead-layer.js +13 -3
- package/src/components/source-group.js +272 -31
- package/src/components/sources-layer.js +38 -125
- package/src/components/waveform-shape.js +96 -100
- package/src/view.js +87 -20
|
@@ -63,9 +63,12 @@ define([
|
|
|
63
63
|
this._selected = this._source.selected;
|
|
64
64
|
this._hovered = false;
|
|
65
65
|
this._isDragged = false;
|
|
66
|
-
this.
|
|
66
|
+
this._isHandleDragged = false;
|
|
67
67
|
this._destroyed = false;
|
|
68
68
|
|
|
69
|
+
// Optional Konva element used as a drag "ghost" preview.
|
|
70
|
+
this._dragGhost = null;
|
|
71
|
+
|
|
69
72
|
// Performance: track if draw is needed
|
|
70
73
|
this._drawScheduled = false;
|
|
71
74
|
|
|
@@ -151,11 +154,54 @@ define([
|
|
|
151
154
|
SourceGroup.prototype._onHoverEnd = function() {
|
|
152
155
|
this._hovered = false;
|
|
153
156
|
this._manualHover = false;
|
|
157
|
+
this._disableManualHoverTracking();
|
|
154
158
|
this._view.setHoveredElement(null);
|
|
155
159
|
this._hideButtons();
|
|
156
160
|
this._scheduleBatchDraw();
|
|
157
161
|
};
|
|
158
162
|
|
|
163
|
+
SourceGroup.prototype._enableManualHoverTracking = function() {
|
|
164
|
+
if (this._manualHoverTrackingEnabled) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!this._group || this._destroyed) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
var stage = this._group.getStage && this._group.getStage();
|
|
173
|
+
|
|
174
|
+
if (!stage) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this._manualHoverTrackingEnabled = true;
|
|
179
|
+
this._manualHoverNamespace = '.manualHover.' + this._source.id;
|
|
180
|
+
|
|
181
|
+
this._manualHoverMoveHandler = function() {
|
|
182
|
+
this._manageManualHoverStop();
|
|
183
|
+
}.bind(this);
|
|
184
|
+
|
|
185
|
+
stage.on('mousemove' + this._manualHoverNamespace, this._manualHoverMoveHandler);
|
|
186
|
+
stage.on('touchmove' + this._manualHoverNamespace, this._manualHoverMoveHandler);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
SourceGroup.prototype._disableManualHoverTracking = function() {
|
|
190
|
+
if (!this._manualHoverTrackingEnabled) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var stage = this._group && this._group.getStage && this._group.getStage();
|
|
195
|
+
|
|
196
|
+
if (stage && this._manualHoverNamespace) {
|
|
197
|
+
stage.off(this._manualHoverNamespace);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this._manualHoverTrackingEnabled = false;
|
|
201
|
+
this._manualHoverMoveHandler = null;
|
|
202
|
+
this._manualHoverNamespace = null;
|
|
203
|
+
};
|
|
204
|
+
|
|
159
205
|
SourceGroup.prototype._onDragStart = function(element) {
|
|
160
206
|
this._isDragged = true;
|
|
161
207
|
this._layer.onSourcesGroupDragStart(element);
|
|
@@ -192,7 +238,11 @@ define([
|
|
|
192
238
|
};
|
|
193
239
|
|
|
194
240
|
SourceGroup.prototype.isActive = function() {
|
|
195
|
-
return this._isDragged || this.
|
|
241
|
+
return this._isDragged || this._isHandleDragged;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
SourceGroup.prototype.isDragged = function() {
|
|
245
|
+
return this._isDragged;
|
|
196
246
|
};
|
|
197
247
|
|
|
198
248
|
SourceGroup.prototype.addToContent = function(newChild) {
|
|
@@ -204,7 +254,7 @@ define([
|
|
|
204
254
|
}
|
|
205
255
|
};
|
|
206
256
|
|
|
207
|
-
SourceGroup.prototype.
|
|
257
|
+
SourceGroup.prototype._updateHandles = function() {
|
|
208
258
|
var handleWidth = Math.min(this._peaks.options.sourceHandleWidth, this._width / 2);
|
|
209
259
|
|
|
210
260
|
this._leftHandle.width(handleWidth);
|
|
@@ -231,7 +281,7 @@ define([
|
|
|
231
281
|
this._layer.batchDraw();
|
|
232
282
|
}
|
|
233
283
|
}.bind(this)
|
|
234
|
-
);
|
|
284
|
+
, null, false);
|
|
235
285
|
|
|
236
286
|
return {
|
|
237
287
|
x: draggedElement.absolutePosition().x,
|
|
@@ -263,8 +313,6 @@ define([
|
|
|
263
313
|
this._currentTimeToPixelsScaleUsed = newTimeToPixelsScale;
|
|
264
314
|
|
|
265
315
|
this._updateMarkers();
|
|
266
|
-
|
|
267
|
-
this._rightHandle.x(this._width - this._rightHandle.width());
|
|
268
316
|
}
|
|
269
317
|
else {
|
|
270
318
|
// the zoom was not changed, but the source was resized
|
|
@@ -278,6 +326,7 @@ define([
|
|
|
278
326
|
}
|
|
279
327
|
}
|
|
280
328
|
|
|
329
|
+
this._updateHandles();
|
|
281
330
|
this._updateVolumeSlider();
|
|
282
331
|
this._updateButtons();
|
|
283
332
|
this._updateLoadingOverlay();
|
|
@@ -285,6 +334,13 @@ define([
|
|
|
285
334
|
// update unwrap
|
|
286
335
|
this.updatePreviews();
|
|
287
336
|
}
|
|
337
|
+
|
|
338
|
+
// Keep the drag ghost in sync automatically while dragging.
|
|
339
|
+
// This lets SourcesLayer avoid manually updating ghosts.
|
|
340
|
+
if (this._isDragged) {
|
|
341
|
+
this.createDragGhost();
|
|
342
|
+
this.updateDragGhost();
|
|
343
|
+
}
|
|
288
344
|
};
|
|
289
345
|
|
|
290
346
|
SourceGroup.prototype.setWrapping = function(wrap, forceCreate, notify) {
|
|
@@ -324,20 +380,14 @@ define([
|
|
|
324
380
|
start: this._source.startTime,
|
|
325
381
|
end: this._source.endTime
|
|
326
382
|
};
|
|
327
|
-
this.
|
|
328
|
-
|
|
383
|
+
this._isHandleDragged = true;
|
|
329
384
|
this._hideButtons();
|
|
330
385
|
};
|
|
331
386
|
|
|
332
387
|
SourceGroup.prototype._onHandleDragEnd = function() {
|
|
333
|
-
this.
|
|
388
|
+
this._isHandleDragged = false;
|
|
334
389
|
this._showButtons();
|
|
335
|
-
|
|
336
|
-
// When resizing via handles, drag events no longer bubble to the parent
|
|
337
|
-
// group, so we won't hit the move-drag end path that calls prepareDragEnd.
|
|
338
|
-
// Normalize handle geometry here so handles snap to the updated width.
|
|
339
|
-
this.update();
|
|
340
|
-
this.prepareDragEnd();
|
|
390
|
+
this._layer.processSourceUpdates([this._source]);
|
|
341
391
|
};
|
|
342
392
|
|
|
343
393
|
SourceGroup.prototype._addHandles = function(forceCreate) {
|
|
@@ -356,9 +406,6 @@ define([
|
|
|
356
406
|
}
|
|
357
407
|
});
|
|
358
408
|
|
|
359
|
-
// Prevent handle drag events from bubbling to the parent group.
|
|
360
|
-
// Otherwise the parent SourceGroup dragstart/dragend handlers run and
|
|
361
|
-
// the sources-layer creates move-drag ghosts during resize.
|
|
362
409
|
this._leftHandle.on('dragstart', function(event) {
|
|
363
410
|
event.cancelBubble = true;
|
|
364
411
|
self._onHandleDragStart(event);
|
|
@@ -392,9 +439,6 @@ define([
|
|
|
392
439
|
}
|
|
393
440
|
});
|
|
394
441
|
|
|
395
|
-
// Prevent handle drag events from bubbling to the parent group.
|
|
396
|
-
// Otherwise the parent SourceGroup dragstart/dragend handlers run and
|
|
397
|
-
// the sources-layer creates move-drag ghosts during resize.
|
|
398
442
|
this._rightHandle.on('dragstart', function(event) {
|
|
399
443
|
event.cancelBubble = true;
|
|
400
444
|
self._onHandleDragStart(event);
|
|
@@ -467,16 +511,15 @@ define([
|
|
|
467
511
|
)
|
|
468
512
|
);
|
|
469
513
|
|
|
470
|
-
var actualX = this._group.x() + this._view.getFrameOffset();
|
|
471
514
|
var x = Math.max(
|
|
472
515
|
0,
|
|
473
|
-
this.
|
|
516
|
+
-(this._group.x() + 2 * radius)
|
|
474
517
|
);
|
|
475
518
|
var width = Math.min(
|
|
476
519
|
this._width - x,
|
|
477
520
|
this._view.getWidth() + 4 * radius - Math.max(
|
|
478
521
|
0,
|
|
479
|
-
|
|
522
|
+
this._group.x()
|
|
480
523
|
)
|
|
481
524
|
);
|
|
482
525
|
|
|
@@ -506,7 +549,7 @@ define([
|
|
|
506
549
|
if (this._selected) {
|
|
507
550
|
backgroundColor = this._source.selectedBackgroundColor;
|
|
508
551
|
}
|
|
509
|
-
else if (this._hovered) {
|
|
552
|
+
else if (this._hovered && this._view.getCurrentMode() !== 'cut') {
|
|
510
553
|
backgroundColor = this._source.hoverBackgroundColor;
|
|
511
554
|
}
|
|
512
555
|
else {
|
|
@@ -753,6 +796,7 @@ define([
|
|
|
753
796
|
|
|
754
797
|
SourceGroup.prototype.startHover = function() {
|
|
755
798
|
this._manualHover = true;
|
|
799
|
+
this._enableManualHoverTracking();
|
|
756
800
|
this._group.fire('mouseenter', { evt: new MouseEvent('mouseenter') }, true);
|
|
757
801
|
};
|
|
758
802
|
|
|
@@ -762,6 +806,14 @@ define([
|
|
|
762
806
|
|
|
763
807
|
SourceGroup.prototype.setDragging = function(isDragging) {
|
|
764
808
|
this._isDragged = isDragging;
|
|
809
|
+
|
|
810
|
+
// Ghost lifecycle is tied to dragging state.
|
|
811
|
+
if (isDragging) {
|
|
812
|
+
this.createDragGhost();
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
this.destroyDragGhost();
|
|
816
|
+
}
|
|
765
817
|
};
|
|
766
818
|
|
|
767
819
|
SourceGroup.prototype.startDrag = function() {
|
|
@@ -948,12 +1000,15 @@ define([
|
|
|
948
1000
|
}
|
|
949
1001
|
}
|
|
950
1002
|
|
|
1003
|
+
var self = this;
|
|
1004
|
+
|
|
951
1005
|
var waveform = new WaveformShape({
|
|
952
|
-
layer: this._layer,
|
|
953
1006
|
view: this._view,
|
|
954
|
-
|
|
1007
|
+
color: this._source.color,
|
|
955
1008
|
height: preview.group.height(),
|
|
956
|
-
|
|
1009
|
+
waveformDataFunc: function() {
|
|
1010
|
+
return self._createWaveformPointsIterator(url);
|
|
1011
|
+
}
|
|
957
1012
|
});
|
|
958
1013
|
|
|
959
1014
|
preview.group.add(waveform);
|
|
@@ -966,6 +1021,101 @@ define([
|
|
|
966
1021
|
this._previewList.push(preview);
|
|
967
1022
|
};
|
|
968
1023
|
|
|
1024
|
+
SourceGroup.prototype._createWaveformPointsIterator = function(url) {
|
|
1025
|
+
var loaded = this._layer.getLoadedData(url + '-scaled');
|
|
1026
|
+
var waveformData = loaded && loaded.data;
|
|
1027
|
+
|
|
1028
|
+
if (!waveformData) {
|
|
1029
|
+
return {
|
|
1030
|
+
next: function() {
|
|
1031
|
+
return { done: true };
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
var view = this._view;
|
|
1037
|
+
var source = this._source;
|
|
1038
|
+
|
|
1039
|
+
var groupX = this._group && typeof this._group.x === 'function' ? this._group.x() : 0;
|
|
1040
|
+
var groupWidth = this._width;
|
|
1041
|
+
var viewWidth = view.getWidth();
|
|
1042
|
+
|
|
1043
|
+
var startPixel = 0;
|
|
1044
|
+
var startOffset = 0;
|
|
1045
|
+
var endPixel = Math.min(viewWidth, waveformData.length);
|
|
1046
|
+
var targetSpeed = 1.0;
|
|
1047
|
+
|
|
1048
|
+
if (source) {
|
|
1049
|
+
targetSpeed = source.targetSpeed || 1.0;
|
|
1050
|
+
|
|
1051
|
+
// Determine which part of the source is visible in the view based on
|
|
1052
|
+
// its current on-canvas geometry (supports dragging without relying on
|
|
1053
|
+
// startTime/endTime).
|
|
1054
|
+
var hiddenLeftPixels = Math.max(-groupX, 0);
|
|
1055
|
+
var hiddenRightPixels = Math.max(groupX + groupWidth - viewWidth, 0);
|
|
1056
|
+
|
|
1057
|
+
startPixel = Math.floor(
|
|
1058
|
+
(view.timeToPixels(source.mediaStartTime) + hiddenLeftPixels) * targetSpeed
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
startOffset = view.timeToPixels(source.mediaStartTime);
|
|
1062
|
+
|
|
1063
|
+
endPixel = Math.min(
|
|
1064
|
+
Math.ceil(
|
|
1065
|
+
(view.timeToPixels(source.mediaEndTime) - hiddenRightPixels) * targetSpeed
|
|
1066
|
+
),
|
|
1067
|
+
waveformData.length
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (startPixel < 0) {
|
|
1072
|
+
startPixel = 0;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (endPixel < startPixel) {
|
|
1076
|
+
endPixel = startPixel;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
var channels = waveformData.channels;
|
|
1080
|
+
|
|
1081
|
+
var channelData = new Array(channels);
|
|
1082
|
+
|
|
1083
|
+
for (var c = 0; c < channels; c++) {
|
|
1084
|
+
channelData[c] = waveformData.channel(c);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
var x = startPixel;
|
|
1088
|
+
|
|
1089
|
+
return {
|
|
1090
|
+
next: function() {
|
|
1091
|
+
if (x >= endPixel) {
|
|
1092
|
+
return { done: true };
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
var min = new Array(channels);
|
|
1096
|
+
var max = new Array(channels);
|
|
1097
|
+
|
|
1098
|
+
for (var i = 0; i < channels; i++) {
|
|
1099
|
+
min[i] = channelData[i].min_sample(x);
|
|
1100
|
+
max[i] = channelData[i].max_sample(x);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
var value = {
|
|
1104
|
+
x: x / targetSpeed - startOffset + 0.5,
|
|
1105
|
+
min: min,
|
|
1106
|
+
max: max
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
x++;
|
|
1110
|
+
|
|
1111
|
+
return {
|
|
1112
|
+
done: false,
|
|
1113
|
+
value: value
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
};
|
|
1118
|
+
|
|
969
1119
|
SourceGroup.prototype.getAudioPreview = function() {
|
|
970
1120
|
return this._previewList.filter(function(preview) {
|
|
971
1121
|
return preview.type === 'audio';
|
|
@@ -1271,6 +1421,90 @@ define([
|
|
|
1271
1421
|
return this._height;
|
|
1272
1422
|
};
|
|
1273
1423
|
|
|
1424
|
+
/**
|
|
1425
|
+
* Creates the drag ghost preview element if it does not exist.
|
|
1426
|
+
* The ghost shows where the source will be placed when released.
|
|
1427
|
+
*/
|
|
1428
|
+
SourceGroup.prototype.createDragGhost = function() {
|
|
1429
|
+
if (this._dragGhost) {
|
|
1430
|
+
return this._dragGhost;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
var frameOffset = this._view.getFrameOffset();
|
|
1434
|
+
var x = this._view.timeToPixels(this._source.startTime) - frameOffset;
|
|
1435
|
+
var width = this._view.timeToPixels(this._source.endTime - this._source.startTime);
|
|
1436
|
+
var height = this.getCurrentHeight();
|
|
1437
|
+
var y = this.getAbsoluteY();
|
|
1438
|
+
|
|
1439
|
+
this._dragGhost = new Konva.Rect({
|
|
1440
|
+
x: x,
|
|
1441
|
+
y: y,
|
|
1442
|
+
width: width,
|
|
1443
|
+
height: height,
|
|
1444
|
+
fill: this._source.backgroundColor,
|
|
1445
|
+
opacity: 0.4,
|
|
1446
|
+
stroke: this._source.selectedBorderColor,
|
|
1447
|
+
strokeWidth: 2,
|
|
1448
|
+
dash: [8, 4],
|
|
1449
|
+
cornerRadius: 8,
|
|
1450
|
+
listening: false
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// Add to the main sources layer (not the group) so it stays behind sources.
|
|
1454
|
+
this._layer.add(this._dragGhost);
|
|
1455
|
+
this._dragGhost.moveToBottom();
|
|
1456
|
+
|
|
1457
|
+
// Ensure initial Y snaps to the current line position.
|
|
1458
|
+
this.updateDragGhost();
|
|
1459
|
+
|
|
1460
|
+
return this._dragGhost;
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
/**
|
|
1464
|
+
* Updates the drag ghost preview position and size.
|
|
1465
|
+
*
|
|
1466
|
+
* @param {Object} lineGroupsById Map of lineId -> Konva.Group
|
|
1467
|
+
*/
|
|
1468
|
+
SourceGroup.prototype.updateDragGhost = function(lineGroupsById) {
|
|
1469
|
+
if (!this._dragGhost) {
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Allow callers to omit the lookup; resolve via the owning layer.
|
|
1474
|
+
if (!lineGroupsById
|
|
1475
|
+
&& this._layer
|
|
1476
|
+
&& typeof this._layer.getLineGroups === 'function') {
|
|
1477
|
+
var lineGroups = this._layer.getLineGroups();
|
|
1478
|
+
|
|
1479
|
+
if (lineGroups && typeof lineGroups.getLineGroupsById === 'function') {
|
|
1480
|
+
lineGroupsById = lineGroups.getLineGroupsById();
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
var frameOffset = this._view.getFrameOffset();
|
|
1485
|
+
var x = this._view.timeToPixels(this._source.startTime) - frameOffset;
|
|
1486
|
+
var width = this._view.timeToPixels(this._source.endTime - this._source.startTime);
|
|
1487
|
+
|
|
1488
|
+
this._dragGhost.x(x);
|
|
1489
|
+
this._dragGhost.width(width);
|
|
1490
|
+
this._dragGhost.height(this.getCurrentHeight());
|
|
1491
|
+
|
|
1492
|
+
if (lineGroupsById) {
|
|
1493
|
+
var lineGroup = lineGroupsById[this._source.lineId];
|
|
1494
|
+
|
|
1495
|
+
if (lineGroup) {
|
|
1496
|
+
this._dragGhost.y(lineGroup.y());
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
SourceGroup.prototype.destroyDragGhost = function() {
|
|
1502
|
+
if (this._dragGhost) {
|
|
1503
|
+
this._dragGhost.destroy();
|
|
1504
|
+
this._dragGhost = null;
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1274
1508
|
SourceGroup.prototype.getHeights = function() {
|
|
1275
1509
|
return {
|
|
1276
1510
|
unwrapped: this._unwrappedHeight,
|
|
@@ -1746,12 +1980,14 @@ define([
|
|
|
1746
1980
|
volumeSliderGroup.add(volumeSliderRect);
|
|
1747
1981
|
volumeSliderGroup.add(volumeSliderLine);
|
|
1748
1982
|
|
|
1749
|
-
volumeSliderGroup.on('dragstart', function() {
|
|
1983
|
+
volumeSliderGroup.on('dragstart', function(evt) {
|
|
1984
|
+
evt.cancelBubble = true;
|
|
1750
1985
|
volumeText.visible(true);
|
|
1751
1986
|
self._peaks.emit('source.startVolumeChange', self._source);
|
|
1752
1987
|
});
|
|
1753
1988
|
|
|
1754
|
-
volumeSliderGroup.on('dragmove', function() {
|
|
1989
|
+
volumeSliderGroup.on('dragmove', function(evt) {
|
|
1990
|
+
evt.cancelBubble = true;
|
|
1755
1991
|
var volume = self._getVolumeFromY(volumeSliderGroup.y());
|
|
1756
1992
|
|
|
1757
1993
|
volumeText.text((volume * 100).toFixed(0) + '%');
|
|
@@ -1762,7 +1998,8 @@ define([
|
|
|
1762
1998
|
self._scheduleBatchDraw();
|
|
1763
1999
|
});
|
|
1764
2000
|
|
|
1765
|
-
volumeSliderGroup.on('dragend', function() {
|
|
2001
|
+
volumeSliderGroup.on('dragend', function(evt) {
|
|
2002
|
+
evt.cancelBubble = true;
|
|
1766
2003
|
volumeText.visible(false);
|
|
1767
2004
|
self._peaks.emit('source.endVolumeChange', self._source);
|
|
1768
2005
|
});
|
|
@@ -1782,6 +2019,10 @@ define([
|
|
|
1782
2019
|
};
|
|
1783
2020
|
|
|
1784
2021
|
SourceGroup.prototype.destroy = function() {
|
|
2022
|
+
this.destroyDragGhost();
|
|
2023
|
+
|
|
2024
|
+
this._disableManualHoverTracking();
|
|
2025
|
+
|
|
1785
2026
|
// Cancel any pending idle callbacks to prevent memory leaks
|
|
1786
2027
|
if (this._pendingIdleCallbacks) {
|
|
1787
2028
|
this._pendingIdleCallbacks.forEach(function(id) {
|