Web Replay: Playback position updates should be sent before the next event loop input...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / ReplayManager.js
1 /*
2  * Copyright (C) 2013 University of Washington. All rights reserved.
3  * Copyright (C) 2014 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.ReplayManager = function()
28 {
29     WebInspector.Object.call(this);
30
31     this._sessionState = WebInspector.ReplayManager.SessionState.Inactive;
32     this._segmentState = WebInspector.ReplayManager.SegmentState.Unloaded;
33
34     this._activeSessionIdentifier = null;
35     this._activeSegmentIdentifier = null;
36     this._currentPosition = new WebInspector.ReplayPosition(0, 0);
37     this._initialized = false;
38
39     // These hold actual instances of sessions and segments.
40     this._sessions = new Map;
41     this._segments = new Map;
42     // These hold promises that resolve when the instance data is recieved.
43     this._sessionPromises = new Map;
44     this._segmentPromises = new Map;
45
46     // Playback speed is specified in replayToPosition commands, and persists
47     // for the duration of the playback command until another playback begins.
48     this._playbackSpeed = WebInspector.ReplayManager.PlaybackSpeed.RealTime;
49
50     if (!window.ReplayAgent)
51         return;
52
53     var instance = this;
54
55     this._initializationPromise = ReplayAgent.currentReplayState()
56         .then(function(payload) {
57             console.assert(payload.sessionState in WebInspector.ReplayManager.SessionState, "Unknown session state: " + payload.sessionState);
58             console.assert(payload.segmentState in WebInspector.ReplayManager.SegmentState, "Unknown segment state: " + payload.segmentState);
59
60             instance._activeSessionIdentifier = payload.sessionIdentifier;
61             instance._activeSegmentIdentifier = payload.segmentIdentifier;
62             instance._sessionState = WebInspector.ReplayManager.SessionState[payload.sessionState];
63             instance._segmentState = WebInspector.ReplayManager.SegmentState[payload.segmentState];
64             instance._currentPosition = payload.replayPosition;
65
66             instance._initialized = true;
67         }).then(function() {
68             return ReplayAgent.getAvailableSessions();
69         }).then(function(payload) {
70             for (var sessionId of payload.ids)
71                 instance.sessionCreated(sessionId);
72         }).catch(function(error) {
73             console.error("ReplayManager initialization failed: ", error);
74             throw error;
75         });
76 };
77
78 WebInspector.ReplayManager.Event = {
79     CaptureStarted: "replay-manager-capture-started",
80     CaptureStopped: "replay-manager-capture-stopped",
81
82     PlaybackStarted: "replay-manager-playback-started",
83     PlaybackPaused: "replay-manager-playback-paused",
84     PlaybackFinished: "replay-manager-playback-finished",
85     PlaybackPositionChanged: "replay-manager-play-back-position-changed",
86
87     ActiveSessionChanged: "replay-manager-active-session-changed",
88     ActiveSegmentChanged: "replay-manager-active-segment-changed",
89
90     SessionSegmentAdded: "replay-manager-session-segment-added",
91     SessionSegmentRemoved: "replay-manager-session-segment-removed",
92
93     SessionAdded: "replay-manager-session-added",
94     SessionRemoved: "replay-manager-session-removed",
95 };
96
97 WebInspector.ReplayManager.SessionState = {
98     Capturing: "replay-manager-session-state-capturing",
99     Inactive: "replay-manager-session-state-inactive",
100     Replaying: "replay-manager-session-state-replaying",
101 };
102
103 WebInspector.ReplayManager.SegmentState = {
104     Appending: "replay-manager-segment-state-appending",
105     Unloaded: "replay-manager-segment-state-unloaded",
106     Loaded: "replay-manager-segment-state-loaded",
107     Dispatching: "replay-manager-segment-state-dispatching",
108 };
109
110 WebInspector.ReplayManager.PlaybackSpeed = {
111     RealTime: "replay-manager-playback-speed-real-time",
112     FastForward: "replay-manager-playback-speed-fast-forward",
113 };
114
115 WebInspector.ReplayManager.prototype = {
116     constructor: WebInspector.ReplayManager,
117     __proto__: WebInspector.Object.prototype,
118
119     // Public
120
121     // The following state is invalid unless called from a function that's chained
122     // to the (resolved) ReplayManager.waitUntilInitialized promise.
123     get sessionState()
124     {
125         console.assert(this._initialized);
126         return this._sessionState;
127     },
128
129     get segmentState()
130     {
131         console.assert(this._initialized);
132         return this._segmentState;
133     },
134
135     get activeSessionIdentifier()
136     {
137         console.assert(this._initialized);
138         return this._activeSessionIdentifier;
139     },
140
141     get activeSegmentIdentifier()
142     {
143         console.assert(this._initialized);
144         return this._activeSegmentIdentifier;
145     },
146
147     get playbackSpeed()
148     {
149         console.assert(this._initialized);
150         return this._playbackSpeed;
151     },
152
153     set playbackSpeed(value)
154     {
155         console.assert(this._initialized);
156         this._playbackSpeed = value;
157     },
158
159     get currentPosition()
160     {
161         console.assert(this._initialized);
162         return this._currentPosition;
163     },
164
165     // These return promises even if the relevant instance is already created.
166     waitUntilInitialized: function()  // --> ()
167     {
168         return this._initializationPromise;
169     },
170
171     // Return a promise that resolves to a session, if it exists.
172     getSession: function(sessionId) // --> (WebInspector.ReplaySession)
173     {
174         if (this._sessionPromises.has(sessionId))
175             return this._sessionPromises.get(sessionId);
176
177         var newPromise = ReplayAgent.getSessionData.promise(sessionId)
178             .then(function(payload) {
179                 return Promise.resolve(WebInspector.ReplaySession.fromPayload(sessionId, payload));
180             });
181
182         this._sessionPromises.set(sessionId, newPromise);
183         return newPromise;
184     },
185
186     // Return a promise that resolves to a session segment, if it exists.
187     getSegment: function(segmentId)  // --> (WebInspector.ReplaySessionSegment)
188     {
189         if (this._segmentPromises.has(segmentId))
190             return this._segmentPromises.get(segmentId);
191
192         var newPromise = ReplayAgent.getSegmentData.promise(segmentId)
193             .then(function(payload) {
194                 return Promise.resolve(new WebInspector.ReplaySessionSegment(segmentId, payload));
195             });
196
197         this._segmentPromises.set(segmentId, newPromise);
198         return newPromise;
199     },
200
201     // Switch to the specified session.
202     // Returns a promise that resolves when the switch completes.
203     switchSession: function(sessionId) // --> ()
204     {
205         var manager = this;
206         var result = this.waitUntilInitialized();
207
208         if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
209             result = result.then(function() {
210                 return WebInspector.replayManager.stopCapturing();
211             });
212         }
213
214         if (this.sessionState === WebInspector.ReplayManager.SessionState.Replaying) {
215             result = result.then(function() {
216                 return WebInspector.replayManager.cancelPlayback();
217             });
218         }
219
220         result = result.then(function() {
221                 console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Inactive);
222                 console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
223
224                 return manager.getSession(sessionId);
225             }).then(function ensureSessionDataIsLoaded(session) {
226                 return ReplayAgent.switchSession(session.identifier);
227             }).catch(function(error) {
228                 console.error("Failed to switch to session: ", error);
229                 throw error;
230             });
231
232         return result;
233     },
234
235     // Start capturing into the current session as soon as possible.
236     // Returns a promise that resolves when capturing begins.
237     startCapturing: function() // --> ()
238     {
239         var manager = this;
240         var result = this.waitUntilInitialized();
241
242         if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing)
243             return result; // Already capturing.
244
245         if (this.sessionState === WebInspector.ReplayManager.SessionState.Replaying) {
246             result = result.then(function() {
247                 return WebInspector.replayManager.cancelPlayback();
248             });
249         }
250
251         result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
252
253         result = result.then(function() {
254                 console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Inactive);
255                 console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
256
257                 return ReplayAgent.startCapturing();
258             }).catch(function(error) {
259                 console.error("Failed to start capturing: ", error);
260                 throw error;
261             });
262
263         return result;
264     },
265
266     // Stop capturing into the current session as soon as possible.
267     // Returns a promise that resolves when capturing ends.
268     stopCapturing: function() // --> ()
269     {
270         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Capturing, "Cannot stop capturing unless capture is active.");
271         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Appending);
272
273         return ReplayAgent.stopCapturing()
274             .catch(function(error) {
275                 console.error("Failed to stop capturing: ", error);
276                 throw error;
277             });
278     },
279
280     // Pause playback as soon as possible.
281     // Returns a promise that resolves when playback is paused.
282     pausePlayback: function() // --> ()
283     {
284         console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing, "Cannot pause playback while capturing.");
285
286         var manager = this;
287         var result = this.waitUntilInitialized();
288
289         if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
290             return result; // Already stopped.
291
292         if (this.segmentState !== WebInspector.ReplayManager.SegmentState.Dispatching)
293             return result; // Already stopped.
294
295         result = result.then(function() {
296                 console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
297                 console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
298
299                 return ReplayAgent.pausePlayback();
300             }).catch(function(error) {
301                 console.error("Failed to pause playback: ", error);
302                 throw error;
303             });
304
305         return result;
306     },
307
308     // Pause playback and unload the current session segment as soon as possible.
309     // Returns a promise that resolves when the current segment is unloaded.
310     cancelPlayback: function() // --> ()
311     {
312         console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing, "Cannot stop playback while capturing.");
313
314         var manager = this;
315         var result = this.waitUntilInitialized();
316
317         if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
318             return result; // Already stopped.
319
320         result = result.then(function() {
321                 console.assert(manager.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
322                 console.assert(manager.segmentState !== WebInspector.ReplayManager.SegmentState.Appending);
323
324                 return ReplayAgent.cancelPlayback();
325             }).catch(function(error) {
326                 console.error("Failed to stop playback: ", error);
327                 throw error;
328             });
329
330         return result;
331     },
332
333     // Replay to the specified position as soon as possible using the current replay speed.
334     // Returns a promise that resolves when replay has begun (NOT when the position is reached).
335     replayToPosition: function(replayPosition) // --> ()
336     {
337         console.assert(replayPosition instanceof WebInspector.ReplayPosition, "Cannot replay to a position while capturing.");
338
339         var manager = this;
340         var result = this.waitUntilInitialized();
341
342         if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
343             result = result.then(function() {
344                 return WebInspector.replayManager.stopCapturing();
345             });
346         }
347
348         result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
349
350         result = result.then(function() {
351                 console.assert(manager.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
352                 console.assert(manager.segmentState !== WebInspector.ReplayManager.SegmentState.Appending);
353
354                 return ReplayAgent.replayToPosition(replayPosition, manager.playbackSpeed === WebInspector.ReplayManager.PlaybackSpeed.FastForward);
355             }).catch(function(error) {
356                 console.error("Failed to start playback to position: ", replayPosition, error);
357                 throw error;
358             });
359
360         return result;
361     },
362
363     // Replay to the end of the session as soon as possible using the current replay speed.
364     // Returns a promise that resolves when replay has begun (NOT when the end is reached).
365     replayToCompletion: function() // --> ()
366     {
367         var manager = this;
368         var result = this.waitUntilInitialized();
369
370         if (this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching)
371             return result; // Already running.
372
373         if (this.sessionState === WebInspector.ReplayManager.SessionState.Capturing) {
374             result = result.then(function() {
375                 return WebInspector.replayManager.stopCapturing();
376             });
377         }
378
379         result = result.then(this._suppressBreakpointsAndResumeIfNeeded());
380
381         result = result.then(function() {
382                 console.assert(manager.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
383                 console.assert(manager.segmentState === WebInspector.ReplayManager.SegmentState.Loaded || manager.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
384
385                 return ReplayAgent.replayToCompletion(manager.playbackSpeed === WebInspector.ReplayManager.PlaybackSpeed.FastForward)
386             }).catch(function(error) {
387                 console.error("Failed to start playback to completion: ", error);
388                 throw error;
389             });
390
391         return result;
392     },
393
394     // Protected (called by ReplayObserver)
395
396     // Since these methods update session and segment state, they depend on the manager
397     // being properly initialized. So, each function body is prepended with a retry guard.
398     // This makes call sites simpler and avoids an extra event loop turn in the common case.
399
400     captureStarted: function()
401     {
402         if (!this._initialized)
403             return this.waitUntilInitialized().then(this.captureStarted.bind(this));
404
405         this._changeSessionState(WebInspector.ReplayManager.SessionState.Capturing);
406
407         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStarted);
408     },
409
410     captureStopped: function()
411     {
412         if (!this._initialized)
413             return this.waitUntilInitialized().then(this.captureStopped.bind(this));
414
415         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
416         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
417
418         if (this._breakpointsWereSuppressed) {
419             delete this._breakpointsWereSuppressed;
420             WebInspector.debuggerManager.breakpointsEnabled = true;
421         }
422
423         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStopped);
424     },
425
426     playbackStarted: function()
427     {
428         if (!this._initialized)
429             return this.waitUntilInitialized().then(this.playbackStarted.bind(this));
430
431         if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
432             this._changeSessionState(WebInspector.ReplayManager.SessionState.Replaying);
433
434         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Dispatching);
435
436         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackStarted);
437     },
438
439     playbackHitPosition: function(replayPosition, timestamp)
440     {
441         if (!this._initialized)
442             return this.waitUntilInitialized().then(this.playbackHitPosition.bind(this, replayPosition, timestamp));
443
444         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
445         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
446         console.assert(replayPosition instanceof WebInspector.ReplayPosition);
447
448         this._currentPosition = replayPosition;
449         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPositionChanged);
450     },
451
452     playbackPaused: function(position)
453     {
454         if (!this._initialized)
455             return this.waitUntilInitialized().then(this.playbackPaused.bind(this, position));
456
457         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
458         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
459
460         if (this._breakpointsWereSuppressed) {
461             delete this._breakpointsWereSuppressed;
462             WebInspector.debuggerManager.breakpointsEnabled = true;
463         }
464
465         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPaused);
466     },
467
468     playbackFinished: function()
469     {
470         if (!this._initialized)
471             return this.waitUntilInitialized().then(this.playbackFinished.bind(this));
472
473         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
474         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
475
476         if (this._breakpointsWereSuppressed) {
477             delete this._breakpointsWereSuppressed;
478             WebInspector.debuggerManager.breakpointsEnabled = true;
479         }
480
481         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackFinished);
482     },
483
484     sessionCreated: function(sessionId)
485     {
486         if (!this._initialized)
487             return this.waitUntilInitialized().then(this.sessionCreated.bind(this, sessionId));
488
489         console.assert(!this._sessions.has(sessionId), "Tried to add duplicate session identifier:", sessionId);
490         var sessionMap = this._sessions;
491         this.getSession(sessionId)
492             .then(function(session) {
493                 sessionMap.set(sessionId, session);
494             }).catch(function(error) {
495                 console.error("Error obtaining session data: ", error);
496                 throw error;
497             });
498
499         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionAdded, {sessionId: sessionId});
500     },
501
502     sessionModified: function(sessionId)
503     {
504         if (!this._initialized)
505             return this.waitUntilInitialized().then(this.sessionModified.bind(this, sessionId));
506
507         this.getSession(sessionId).then(function(session) {
508             session.segmentsChanged();
509         });
510     },
511
512     sessionRemoved: function(sessionId)
513     {
514         if (!this._initialized)
515             return this.waitUntilInitialized().then(this.sessionRemoved.bind(this, sessionId));
516
517         console.assert(this._sessions.has(sessionId), "Unknown session identifier:", sessionId);
518
519         if (!this._sessionPromises.has(sessionId))
520             return;
521
522         var manager = this;
523
524         this.getSession(sessionId)
525             .catch(function(error) {
526                 // Wait for any outstanding promise to settle so it doesn't get re-added.
527             }).then(function() {
528                 manager._sessionPromises.delete(sessionId);
529                 var removedSession = manager._sessions.take(sessionId);
530                 console.assert(removedSession);
531                 manager.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionRemoved, {removedSession: removedSession});
532             });
533     },
534
535     segmentCreated: function(segmentId)
536     {
537         if (!this._initialized)
538             return this.waitUntilInitialized().then(this.segmentCreated.bind(this, segmentId));
539
540         console.assert(!this._segments.has(segmentId), "Tried to add duplicate segment identifier:", segmentId);
541
542         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Appending);
543
544         // Create a dummy segment, and don't try to load any data for it. It will
545         // be removed once the segment is complete, and then its data will be fetched.
546         var incompleteSegment = new WebInspector.IncompleteSessionSegment(segmentId);
547         this._segments.set(segmentId, incompleteSegment);
548         this._segmentPromises.set(segmentId, Promise.resolve(incompleteSegment));
549
550         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionSegmentAdded, {segmentIdentifier: segmentId});
551     },
552
553     segmentCompleted: function(segmentId)
554     {
555         if (!this._initialized)
556             return this.waitUntilInitialized().then(this.segmentCompleted.bind(this, segmentId));
557
558         var placeholderSegment = this._segments.take(segmentId);
559         console.assert(placeholderSegment instanceof WebInspector.IncompleteSessionSegment);
560         this._segmentPromises.delete(segmentId);
561
562         var segmentMap = this._segments;
563         this.getSegment(segmentId)
564             .then(function(segment) {
565                 segmentMap.set(segmentId, segment);
566             }).catch(function(error) {
567                 console.error("Error obtaining segment data: ", error);
568                 throw error;
569             });
570     },
571
572     segmentRemoved: function(segmentId)
573     {
574         if (!this._initialized)
575             return this.waitUntilInitialized().then(this.segmentRemoved.bind(this, segmentId));
576
577         console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
578
579         if (!this._segmentPromises.has(segmentId))
580             return;
581
582         var manager = this;
583
584         // Wait for any outstanding promise to settle so it doesn't get re-added.
585         this.getSegment(segmentId)
586             .catch(function(error) {
587                 return Promise.resolve();
588             }).then(function() {
589                 manager._segmentPromises.delete(segmentId);
590                 var removedSegment = manager._segments.take(segmentId);
591                 console.assert(removedSegment);
592                 manager.dispatchEventToListeners(WebInspector.ReplayManager.Event.SessionSegmentRemoved, {removedSegment: removedSegment});
593             });
594     },
595
596     segmentLoaded: function(segmentId)
597     {
598         if (!this._initialized)
599             return this.waitUntilInitialized().then(this.segmentLoaded.bind(this, segmentId));
600
601         console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
602
603         console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
604         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
605
606         var previousIdentifier = this._activeSegmentIdentifier;
607         this._activeSegmentIdentifier = segmentId;
608         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.ActiveSegmentChanged, {previousSegmentIdentifier: previousIdentifier});
609    },
610
611     segmentUnloaded: function()
612     {
613         if (!this._initialized)
614             return this.waitUntilInitialized().then(this.segmentUnloaded.bind(this));
615
616         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
617         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
618
619         var previousIdentifier = this._activeSegmentIdentifier;
620         this._activeSegmentIdentifier = null;
621         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.ActiveSegmentChanged, {previousSegmentIdentifier: previousIdentifier});
622     },
623
624     // Private
625
626     _changeSessionState: function(newState)
627     {
628         // Warn about no-op state changes. We shouldn't be seeing them.
629         var isAllowed = this._sessionState !== newState;
630
631         switch (this._sessionState) {
632         case WebInspector.ReplayManager.SessionState.Capturing:
633             isAllowed &= newState === WebInspector.ReplayManager.SessionState.Inactive;
634             break;
635
636         case WebInspector.ReplayManager.SessionState.Replaying:
637             isAllowed &= newState === WebInspector.ReplayManager.SessionState.Inactive;
638             break;
639         }
640
641         console.assert(isAllowed, "Invalid session state change: ", this._sessionState, " to ", newState);
642         if (isAllowed)
643             this._sessionState = newState;
644     },
645
646     _changeSegmentState: function(newState)
647     {
648         // Warn about no-op state changes. We shouldn't be seeing them.
649         var isAllowed = this._segmentState !== newState;
650
651         switch (this._segmentState) {
652             case WebInspector.ReplayManager.SegmentState.Appending:
653                 isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Unloaded;
654                 break;
655             case WebInspector.ReplayManager.SegmentState.Unloaded:
656                 isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Appending || newState === WebInspector.ReplayManager.SegmentState.Loaded;
657                 break;
658             case WebInspector.ReplayManager.SegmentState.Loaded:
659                 isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Unloaded || newState === WebInspector.ReplayManager.SegmentState.Dispatching;
660                 break;
661             case WebInspector.ReplayManager.SegmentState.Dispatching:
662                 isAllowed &= newState === WebInspector.ReplayManager.SegmentState.Loaded;
663                 break;
664         }
665
666         console.assert(isAllowed, "Invalid segment state change: ", this._segmentState, " to ", newState);
667         if (isAllowed)
668             this._segmentState = newState;
669     },
670
671     _suppressBreakpointsAndResumeIfNeeded: function()
672     {
673         var manager = this;
674
675         return new Promise(function(resolve, reject) {
676             manager._breakpointsWereSuppressed = WebInspector.debuggerManager.breakpointsEnabled;
677             WebInspector.debuggerManager.breakpointsEnabled = false;
678
679             return WebInspector.debuggerManager.resume();
680         });
681     }
682 };
683