Title: [172087] trunk/Source
Revision
172087
Author
b...@cs.washington.edu
Date
2014-08-05 14:56:57 -0700 (Tue, 05 Aug 2014)

Log Message

Web Inspector: ReplayManager shouldn't assume replay status when the inspector is opened
https://bugs.webkit.org/show_bug.cgi?id=135212

Reviewed by Timothy Hatcher.

Source/WebCore:

The frontend should be able to introspect the session and segment state machines,
currently loaded segment and session identifiers, and replay position.

* inspector/InspectorReplayAgent.cpp:
(WebCore::buildInspectorObjectForSessionState): Added.
(WebCore::buildInspectorObjectForSegmentState): Added.
(WebCore::InspectorReplayAgent::currentReplayState): Added.
* inspector/InspectorReplayAgent.h:
* inspector/protocol/Replay.json: Add currentReplayState query command.
* replay/ReplayController.h: Add some accessors.

Source/WebInspectorUI:

The inspector could be closed and reopened at any point during capturing or replaying.
ReplayManager should query the current state on initialization rather than assuming
that the replay controller is still in its initial state.

ReplayManager's initialization code requires querying the backend for the current replay
state. This could race with replay protocol events that mutate the manager's state before
it is fully initialized, leading to undefined behavior.

To mitigate this, all protocol event handlers (called by ReplayObserver) are wrapped
with a guard that enqueues the callback if initialization is not yet complete. This
queue is implemented via multiple then-chaining of a shared 'initialization' promise
which resolves when initialization completes.

* UserInterface/Controllers/ReplayManager.js:
(WebInspector.ReplayManager.then):
(WebInspector.ReplayManager.catch):
(WebInspector.ReplayManager): Rewrite the initialization code to first query the replay
state, set the initialization flag to true, and then request and update session records.
The sessions must be loaded after querying initial state because ReplayManager.sessionCreated
requires replay state to be initialized.

(WebInspector.ReplayManager.prototype.get sessionState):
(WebInspector.ReplayManager.prototype.get segmentState):
(WebInspector.ReplayManager.prototype.get activeSessionIdentifier):
(WebInspector.ReplayManager.prototype.get activeSegmentIdentifier):
(WebInspector.ReplayManager.prototype.get playbackSpeed):
(WebInspector.ReplayManager.prototype.set playbackSpeed):
(WebInspector.ReplayManager.prototype.get currentPosition): Add assertions to catch uses of
manager state before the manager is fully initialized.

(WebInspector.ReplayManager.prototype.waitUntilInitialized): Added. It returns a shared promise
that is fulfilled when initialization is complete.

(WebInspector.ReplayManager.prototype.captureStarted):
(WebInspector.ReplayManager.prototype.captureStopped):
(WebInspector.ReplayManager.prototype.playbackStarted):
(WebInspector.ReplayManager.prototype.playbackHitPosition):
(WebInspector.ReplayManager.prototype.playbackPaused):
(WebInspector.ReplayManager.prototype.playbackFinished):
(WebInspector.ReplayManager.prototype.sessionModified):
(WebInspector.ReplayManager.prototype.sessionLoaded):
(WebInspector.ReplayManager.prototype.segmentCompleted.set catch):
(WebInspector.ReplayManager.prototype.segmentCompleted):
(WebInspector.ReplayManager.prototype.segmentRemoved.then):
(WebInspector.ReplayManager.prototype.segmentRemoved):
(WebInspector.ReplayManager.prototype.segmentLoaded): Add initialization guards.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (172086 => 172087)


--- trunk/Source/WebCore/ChangeLog	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebCore/ChangeLog	2014-08-05 21:56:57 UTC (rev 172087)
@@ -1,3 +1,21 @@
+2014-08-05  Brian J. Burg  <b...@cs.washington.edu>
+
+        Web Inspector: ReplayManager shouldn't assume replay status when the inspector is opened
+        https://bugs.webkit.org/show_bug.cgi?id=135212
+
+        Reviewed by Timothy Hatcher.
+
+        The frontend should be able to introspect the session and segment state machines,
+        currently loaded segment and session identifiers, and replay position.
+
+        * inspector/InspectorReplayAgent.cpp:
+        (WebCore::buildInspectorObjectForSessionState): Added.
+        (WebCore::buildInspectorObjectForSegmentState): Added.
+        (WebCore::InspectorReplayAgent::currentReplayState): Added.
+        * inspector/InspectorReplayAgent.h:
+        * inspector/protocol/Replay.json: Add currentReplayState query command.
+        * replay/ReplayController.h: Add some accessors.
+
 2014-08-05  Dean Jackson  <d...@apple.com>
 
         [iOS] Media controls layout incorrectly in RTL content

Modified: trunk/Source/WebCore/inspector/InspectorReplayAgent.cpp (172086 => 172087)


--- trunk/Source/WebCore/inspector/InspectorReplayAgent.cpp	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebCore/inspector/InspectorReplayAgent.cpp	2014-08-05 21:56:57 UTC (rev 172087)
@@ -93,6 +93,25 @@
     return sessionObject.release();
 }
 
+static Inspector::TypeBuilder::Replay::SessionState::Enum buildInspectorObjectForSessionState(SessionState sessionState)
+{
+    switch (sessionState) {
+    case SessionState::Capturing: return Inspector::TypeBuilder::Replay::SessionState::Capturing;
+    case SessionState::Inactive: return Inspector::TypeBuilder::Replay::SessionState::Inactive;
+    case SessionState::Replaying: return Inspector::TypeBuilder::Replay::SessionState::Replaying;
+    }
+}
+
+static Inspector::TypeBuilder::Replay::SegmentState::Enum buildInspectorObjectForSegmentState(SegmentState segmentState)
+{
+    switch (segmentState) {
+    case SegmentState::Appending: return Inspector::TypeBuilder::Replay::SegmentState::Appending;
+    case SegmentState::Unloaded: return Inspector::TypeBuilder::Replay::SegmentState::Unloaded;
+    case SegmentState::Loaded: return Inspector::TypeBuilder::Replay::SegmentState::Loaded;
+    case SegmentState::Dispatching: return Inspector::TypeBuilder::Replay::SegmentState::Dispatching;
+    }
+}
+
 class SerializeInputToJSONFunctor {
 public:
     typedef PassRefPtr<TypeBuilder::Array<TypeBuilder::Replay::ReplayInput>> ReturnType;
@@ -464,6 +483,18 @@
     return it->value;
 }
 
+void InspectorReplayAgent::currentReplayState(ErrorString*, SessionIdentifier* sessionIdentifier, Inspector::TypeBuilder::OptOutput<int>* segmentIdentifier, Inspector::TypeBuilder::Replay::SessionState::Enum* sessionState, Inspector::TypeBuilder::Replay::SegmentState::Enum* segmentState, RefPtr<Inspector::TypeBuilder::Replay::ReplayPosition>& replayPosition)
+{
+    *sessionState = buildInspectorObjectForSessionState(m_page.replayController().sessionState());
+    *segmentState = buildInspectorObjectForSegmentState(m_page.replayController().segmentState());
+
+    *sessionIdentifier = m_page.replayController().loadedSession()->identifier();
+    if (m_page.replayController().loadedSegment())
+        *segmentIdentifier = m_page.replayController().loadedSegment()->identifier();
+
+    replayPosition = buildInspectorObjectForPosition(m_page.replayController().currentPosition());
+}
+
 void InspectorReplayAgent::getAvailableSessions(ErrorString*, RefPtr<Inspector::TypeBuilder::Array<SessionIdentifier>>& sessionsList)
 {
     sessionsList = TypeBuilder::Array<SessionIdentifier>::create();

Modified: trunk/Source/WebCore/inspector/InspectorReplayAgent.h (172086 => 172087)


--- trunk/Source/WebCore/inspector/InspectorReplayAgent.h	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebCore/inspector/InspectorReplayAgent.h	2014-08-05 21:56:57 UTC (rev 172087)
@@ -104,6 +104,7 @@
     virtual void insertSessionSegment(ErrorString*, SessionIdentifier, SegmentIdentifier, int segmentIndex) override;
     virtual void removeSessionSegment(ErrorString*, SessionIdentifier, int segmentIndex) override;
 
+    virtual void currentReplayState(ErrorString*, SessionIdentifier*, Inspector::TypeBuilder::OptOutput<int>* segmentIdentifier, Inspector::TypeBuilder::Replay::SessionState::Enum* sessionState, Inspector::TypeBuilder::Replay::SegmentState::Enum* segmentState, RefPtr<Inspector::TypeBuilder::Replay::ReplayPosition>&) override;
     virtual void getAvailableSessions(ErrorString*, RefPtr<Inspector::TypeBuilder::Array<SessionIdentifier>>&) override;
     virtual void getSessionData(ErrorString*, SessionIdentifier, RefPtr<Inspector::TypeBuilder::Replay::ReplaySession>&) override;
     virtual void getSegmentData(ErrorString*, SegmentIdentifier, RefPtr<Inspector::TypeBuilder::Replay::SessionSegment>&) override;

Modified: trunk/Source/WebCore/inspector/protocol/Replay.json (172086 => 172087)


--- trunk/Source/WebCore/inspector/protocol/Replay.json	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebCore/inspector/protocol/Replay.json	2014-08-05 21:56:57 UTC (rev 172087)
@@ -11,6 +11,16 @@
             "type": "integer"
         },
         {
+            "id": "SessionState", "description": "State machine's state for the session.",
+            "type": "string",
+            "enum": ["Capturing", "Inactive", "Replaying"]
+        },
+        {
+            "id": "SegmentState", "description": "State machine's state for the session segment.",
+            "type": "string",
+            "enum": ["Appending", "Unloaded", "Loaded", "Dispatching"]
+        },
+        {
             "id": "ReplayPosition",
             "type": "object",
             "properties": [
@@ -112,6 +122,17 @@
             ]
         },
         {
+            "name": "currentReplayState",
+            "description": "Returns the identifier, position, session state and segment state of the currently loaded session. This is necessary because the inspector may be closed and reopened in the middle of replay.",
+            "returns": [
+                { "name": "sessionIdentifier", "$ref": "SessionIdentifier" },
+                { "name": "segmentIdentifier", "$ref": "SegmentIdentifier", "optional": true, "description": "If no segment is currently loaded, then there is no valid segment identifier." },
+                { "name": "sessionState", "$ref": "SessionState" },
+                { "name": "segmentState", "$ref": "SegmentState" },
+                { "name": "replayPosition", "$ref": "ReplayPosition" }
+            ]
+        },
+        {
             "name": "getAvailableSessions",
             "description": "Returns identifiers of all available sessions.",
             "returns": [

Modified: trunk/Source/WebCore/replay/ReplayController.h (172086 => 172087)


--- trunk/Source/WebCore/replay/ReplayController.h	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebCore/replay/ReplayController.h	2014-08-05 21:56:57 UTC (rev 172087)
@@ -136,10 +136,15 @@
     void willDispatchEvent(const Event&, Frame*);
 
     Page& page() const { return m_page; }
+
     SessionState sessionState() const { return m_sessionState; }
+    SegmentState segmentState() const { return m_segmentState; }
+
     PassRefPtr<ReplaySession> loadedSession() const;
     PassRefPtr<ReplaySessionSegment> loadedSegment() const;
+
     JSC::InputCursor& activeInputCursor() const;
+    ReplayPosition currentPosition() const { return m_currentPosition; }
 
 private:
     // EventLoopInputDispatcherClient API

Modified: trunk/Source/WebInspectorUI/ChangeLog (172086 => 172087)


--- trunk/Source/WebInspectorUI/ChangeLog	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebInspectorUI/ChangeLog	2014-08-05 21:56:57 UTC (rev 172087)
@@ -1,5 +1,59 @@
 2014-08-05  Brian J. Burg  <b...@cs.washington.edu>
 
+        Web Inspector: ReplayManager shouldn't assume replay status when the inspector is opened
+        https://bugs.webkit.org/show_bug.cgi?id=135212
+
+        Reviewed by Timothy Hatcher.
+
+        The inspector could be closed and reopened at any point during capturing or replaying.
+        ReplayManager should query the current state on initialization rather than assuming
+        that the replay controller is still in its initial state.
+
+        ReplayManager's initialization code requires querying the backend for the current replay
+        state. This could race with replay protocol events that mutate the manager's state before
+        it is fully initialized, leading to undefined behavior.
+
+        To mitigate this, all protocol event handlers (called by ReplayObserver) are wrapped
+        with a guard that enqueues the callback if initialization is not yet complete. This
+        queue is implemented via multiple then-chaining of a shared 'initialization' promise
+        which resolves when initialization completes.
+
+        * UserInterface/Controllers/ReplayManager.js:
+        (WebInspector.ReplayManager.then):
+        (WebInspector.ReplayManager.catch):
+        (WebInspector.ReplayManager): Rewrite the initialization code to first query the replay
+        state, set the initialization flag to true, and then request and update session records.
+        The sessions must be loaded after querying initial state because ReplayManager.sessionCreated
+        requires replay state to be initialized.
+
+        (WebInspector.ReplayManager.prototype.get sessionState):
+        (WebInspector.ReplayManager.prototype.get segmentState):
+        (WebInspector.ReplayManager.prototype.get activeSessionIdentifier):
+        (WebInspector.ReplayManager.prototype.get activeSegmentIdentifier):
+        (WebInspector.ReplayManager.prototype.get playbackSpeed):
+        (WebInspector.ReplayManager.prototype.set playbackSpeed):
+        (WebInspector.ReplayManager.prototype.get currentPosition): Add assertions to catch uses of
+        manager state before the manager is fully initialized.
+
+        (WebInspector.ReplayManager.prototype.waitUntilInitialized): Added. It returns a shared promise
+        that is fulfilled when initialization is complete.
+
+        (WebInspector.ReplayManager.prototype.captureStarted):
+        (WebInspector.ReplayManager.prototype.captureStopped):
+        (WebInspector.ReplayManager.prototype.playbackStarted):
+        (WebInspector.ReplayManager.prototype.playbackHitPosition):
+        (WebInspector.ReplayManager.prototype.playbackPaused):
+        (WebInspector.ReplayManager.prototype.playbackFinished):
+        (WebInspector.ReplayManager.prototype.sessionModified):
+        (WebInspector.ReplayManager.prototype.sessionLoaded):
+        (WebInspector.ReplayManager.prototype.segmentCompleted.set catch):
+        (WebInspector.ReplayManager.prototype.segmentCompleted):
+        (WebInspector.ReplayManager.prototype.segmentRemoved.then):
+        (WebInspector.ReplayManager.prototype.segmentRemoved):
+        (WebInspector.ReplayManager.prototype.segmentLoaded): Add initialization guards.
+
+2014-08-05  Brian J. Burg  <b...@cs.washington.edu>
+
         Web Replay: rename protocol methods for getting replay session/segment data
         https://bugs.webkit.org/show_bug.cgi?id=135618
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js (172086 => 172087)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js	2014-08-05 21:41:11 UTC (rev 172086)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js	2014-08-05 21:56:57 UTC (rev 172087)
@@ -34,6 +34,7 @@
     this._activeSessionIdentifier = null;
     this._activeSegmentIdentifier = null;
     this._currentPosition = new WebInspector.ReplayPosition(0, 0);
+    this._initialized = false;
 
     // These hold actual instances of sessions and segments.
     this._sessions = new Map;
@@ -49,10 +50,27 @@
     if (!window.ReplayAgent)
         return;
 
-    ReplayAgent.getAvailableSessions.promise()
+    var instance = this;
+
+    this._initializationPromise = ReplayAgent.currentReplayState.promise()
         .then(function(payload) {
+            console.assert(payload.sessionState in WebInspector.ReplayManager.SessionState, "Unknown session state: " + payload.sessionState);
+            console.assert(payload.segmentState in WebInspector.ReplayManager.SegmentState, "Unknown segment state: " + payload.segmentState);
+
+            instance._activeSessionIdentifier = payload.sessionIdentifier;
+            instance._activeSegmentIdentifier = payload.segmentIdentifier;
+            instance._sessionState = WebInspector.ReplayManager.SessionState[payload.sessionState];
+            instance._segmentState = WebInspector.ReplayManager.SegmentState[payload.segmentState];
+            instance._currentPosition = payload.replayPosition;
+
+            instance._initialized = true;
+        }).then(function() {
+            return ReplayAgent.getAvailableSessions.promise();
+        }).then(function(payload) {
             for (var sessionId of payload.ids)
-                WebInspector.replayManager.sessionCreated(sessionId);
+                instance.sessionCreated(sessionId);
+        }).catch(function(err) {
+            console.error("ReplayManager initialization failed: ", err);
         });
 };
 
@@ -99,42 +117,56 @@
 
     // Public
 
+    // The following state is invalid unless called from a function that's chained
+    // to the (resolved) ReplayManager.waitUntilInitialized promise.
     get sessionState()
     {
+        console.assert(this._initialized);
         return this._sessionState;
     },
 
     get segmentState()
     {
+        console.assert(this._initialized);
         return this._segmentState;
     },
 
     get activeSessionIdentifier()
     {
+        console.assert(this._initialized);
         return this._activeSessionIdentifier;
     },
 
     get activeSegmentIdentifier()
     {
+        console.assert(this._initialized);
         return this._activeSegmentIdentifier;
     },
 
     get playbackSpeed()
     {
+        console.assert(this._initialized);
         return this._playbackSpeed;
     },
 
     set playbackSpeed(value)
     {
+        console.assert(this._initialized);
         this._playbackSpeed = value;
     },
 
     get currentPosition()
     {
+        console.assert(this._initialized);
         return this._currentPosition;
     },
 
     // These return promises even if the relevant instance is already created.
+    waitUntilInitialized: function()
+    {
+        return this._initializationPromise;
+    },
+
     getSession: function(sessionId)
     {
         if (this._sessionPromises.has(sessionId))
@@ -165,8 +197,15 @@
 
     // Protected (called by ReplayObserver)
 
+    // Since these methods update session and segment state, they depend on the manager
+    // being properly initialized. So, each function body is prepended with a retry guard.
+    // This makes call sites simpler and avoids an extra event loop turn in the common case.
+
     captureStarted: function()
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.captureStarted.bind(this));
+
         this._changeSessionState(WebInspector.ReplayManager.SessionState.Capturing);
 
         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStarted);
@@ -174,6 +213,9 @@
 
     captureStopped: function()
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.captureStopped.bind(this));
+
         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
 
@@ -182,6 +224,9 @@
 
     playbackStarted: function()
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackStarted.bind(this));
+
         if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
             this._changeSessionState(WebInspector.ReplayManager.SessionState.Replaying);
 
@@ -192,6 +237,9 @@
 
     playbackHitPosition: function(replayPosition, timestamp)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackHitPosition.bind(this, replayPosition, timestamp));
+
         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
         console.assert(replayPosition instanceof WebInspector.ReplayPosition);
@@ -200,8 +248,11 @@
         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPositionChanged);
     },
 
-    playbackPaused: function(mark)
+    playbackPaused: function(position)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackPaused.bind(this, position));
+
         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
 
@@ -210,6 +261,9 @@
 
     playbackFinished: function()
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackFinished.bind(this));
+
         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
 
@@ -218,13 +272,15 @@
 
     sessionCreated: function(sessionId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionCreated.bind(this, sessionId));
+
         console.assert(!this._sessions.has(sessionId), "Tried to add duplicate session identifier:", sessionId);
         var sessionMap = this._sessions;
         this.getSession(sessionId)
             .then(function(session) {
                 sessionMap.set(sessionId, session);
-            })
-            .catch(function(error) {
+            }).catch(function(error) {
                 console.error("Error obtaining session data: ", error);
             });
 
@@ -233,6 +289,9 @@
 
     sessionModified: function(sessionId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionModified.bind(this, sessionId));
+
         this.getSession(sessionId).then(function(session) {
             session.segmentsChanged();
         });
@@ -240,6 +299,9 @@
 
     sessionRemoved: function(sessionId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionRemoved.bind(this, sessionId));
+
         console.assert(this._sessions.has(sessionId), "Unknown session identifier:", sessionId);
 
         if (!this._sessionPromises.has(sessionId))
@@ -251,8 +313,7 @@
         this.getSession(sessionId)
             .catch(function(error) {
                 return Promise.resolve();
-            })
-            .then(function() {
+            }).then(function() {
                 manager._sessionPromises.delete(sessionId);
                 var removedSession = manager._sessions.take(sessionId);
                 console.assert(removedSession);
@@ -262,6 +323,9 @@
 
     segmentCreated: function(segmentId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentCreated.bind(this, segmentId));
+
         console.assert(!this._segments.has(segmentId), "Tried to add duplicate segment identifier:", segmentId);
 
         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Appending);
@@ -277,6 +341,9 @@
 
     segmentCompleted: function(segmentId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentCompleted.bind(this, segmentId));
+
         var placeholderSegment = this._segments.take(segmentId);
         console.assert(placeholderSegment instanceof WebInspector.IncompleteSessionSegment);
         this._segmentPromises.delete(segmentId);
@@ -285,14 +352,16 @@
         this.getSegment(segmentId)
             .then(function(segment) {
                 segmentMap.set(segmentId, segment);
-            })
-            .catch(function(error) {
+            }).catch(function(error) {
                 console.error("Error obtaining segment data: ", error);
             });
     },
 
     segmentRemoved: function(segmentId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentRemoved.bind(this, segmentId));
+
         console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
 
         if (!this._segmentPromises.has(segmentId))
@@ -304,8 +373,7 @@
         this.getSegment(segmentId)
             .catch(function(error) {
                 return Promise.resolve();
-            })
-            .then(function() {
+            }).then(function() {
                 manager._segmentPromises.delete(segmentId);
                 var removedSegment = manager._segments.take(segmentId);
                 console.assert(removedSegment);
@@ -315,6 +383,9 @@
 
     segmentLoaded: function(segmentId)
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentLoaded.bind(this, segmentId));
+
         console.assert(this._segments.has(segmentId), "Unknown segment identifier:", segmentId);
 
         console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
@@ -327,6 +398,9 @@
 
     segmentUnloaded: function()
     {
+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentUnloaded.bind(this));
+
         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to