<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[172087] trunk/Source</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/172087">172087</a></dd>
<dt>Author</dt> <dd>burg@cs.washington.edu</dd>
<dt>Date</dt> <dd>2014-08-05 14:56:57 -0700 (Tue, 05 Aug 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>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.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreinspectorInspectorReplayAgentcpp">trunk/Source/WebCore/inspector/InspectorReplayAgent.cpp</a></li>
<li><a href="#trunkSourceWebCoreinspectorInspectorReplayAgenth">trunk/Source/WebCore/inspector/InspectorReplayAgent.h</a></li>
<li><a href="#trunkSourceWebCoreinspectorprotocolReplayjson">trunk/Source/WebCore/inspector/protocol/Replay.json</a></li>
<li><a href="#trunkSourceWebCorereplayReplayControllerh">trunk/Source/WebCore/replay/ReplayController.h</a></li>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceControllersReplayManagerjs">trunk/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,21 @@
</span><ins>+2014-08-05  Brian J. Burg  &lt;burg@cs.washington.edu&gt;
+
+        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.
+
</ins><span class="cx"> 2014-08-05  Dean Jackson  &lt;dino@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [iOS] Media controls layout incorrectly in RTL content
</span></span></pre></div>
<a id="trunkSourceWebCoreinspectorInspectorReplayAgentcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/inspector/InspectorReplayAgent.cpp (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -93,6 +93,25 @@
</span><span class="cx">     return sessionObject.release();
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+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;
+    }
+}
+
</ins><span class="cx"> class SerializeInputToJSONFunctor {
</span><span class="cx"> public:
</span><span class="cx">     typedef PassRefPtr&lt;TypeBuilder::Array&lt;TypeBuilder::Replay::ReplayInput&gt;&gt; ReturnType;
</span><span class="lines">@@ -464,6 +483,18 @@
</span><span class="cx">     return it-&gt;value;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void InspectorReplayAgent::currentReplayState(ErrorString*, SessionIdentifier* sessionIdentifier, Inspector::TypeBuilder::OptOutput&lt;int&gt;* segmentIdentifier, Inspector::TypeBuilder::Replay::SessionState::Enum* sessionState, Inspector::TypeBuilder::Replay::SegmentState::Enum* segmentState, RefPtr&lt;Inspector::TypeBuilder::Replay::ReplayPosition&gt;&amp; replayPosition)
+{
+    *sessionState = buildInspectorObjectForSessionState(m_page.replayController().sessionState());
+    *segmentState = buildInspectorObjectForSegmentState(m_page.replayController().segmentState());
+
+    *sessionIdentifier = m_page.replayController().loadedSession()-&gt;identifier();
+    if (m_page.replayController().loadedSegment())
+        *segmentIdentifier = m_page.replayController().loadedSegment()-&gt;identifier();
+
+    replayPosition = buildInspectorObjectForPosition(m_page.replayController().currentPosition());
+}
+
</ins><span class="cx"> void InspectorReplayAgent::getAvailableSessions(ErrorString*, RefPtr&lt;Inspector::TypeBuilder::Array&lt;SessionIdentifier&gt;&gt;&amp; sessionsList)
</span><span class="cx"> {
</span><span class="cx">     sessionsList = TypeBuilder::Array&lt;SessionIdentifier&gt;::create();
</span></span></pre></div>
<a id="trunkSourceWebCoreinspectorInspectorReplayAgenth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/inspector/InspectorReplayAgent.h (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -104,6 +104,7 @@
</span><span class="cx">     virtual void insertSessionSegment(ErrorString*, SessionIdentifier, SegmentIdentifier, int segmentIndex) override;
</span><span class="cx">     virtual void removeSessionSegment(ErrorString*, SessionIdentifier, int segmentIndex) override;
</span><span class="cx"> 
</span><ins>+    virtual void currentReplayState(ErrorString*, SessionIdentifier*, Inspector::TypeBuilder::OptOutput&lt;int&gt;* segmentIdentifier, Inspector::TypeBuilder::Replay::SessionState::Enum* sessionState, Inspector::TypeBuilder::Replay::SegmentState::Enum* segmentState, RefPtr&lt;Inspector::TypeBuilder::Replay::ReplayPosition&gt;&amp;) override;
</ins><span class="cx">     virtual void getAvailableSessions(ErrorString*, RefPtr&lt;Inspector::TypeBuilder::Array&lt;SessionIdentifier&gt;&gt;&amp;) override;
</span><span class="cx">     virtual void getSessionData(ErrorString*, SessionIdentifier, RefPtr&lt;Inspector::TypeBuilder::Replay::ReplaySession&gt;&amp;) override;
</span><span class="cx">     virtual void getSegmentData(ErrorString*, SegmentIdentifier, RefPtr&lt;Inspector::TypeBuilder::Replay::SessionSegment&gt;&amp;) override;
</span></span></pre></div>
<a id="trunkSourceWebCoreinspectorprotocolReplayjson"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/inspector/protocol/Replay.json (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -11,6 +11,16 @@
</span><span class="cx">             &quot;type&quot;: &quot;integer&quot;
</span><span class="cx">         },
</span><span class="cx">         {
</span><ins>+            &quot;id&quot;: &quot;SessionState&quot;, &quot;description&quot;: &quot;State machine's state for the session.&quot;,
+            &quot;type&quot;: &quot;string&quot;,
+            &quot;enum&quot;: [&quot;Capturing&quot;, &quot;Inactive&quot;, &quot;Replaying&quot;]
+        },
+        {
+            &quot;id&quot;: &quot;SegmentState&quot;, &quot;description&quot;: &quot;State machine's state for the session segment.&quot;,
+            &quot;type&quot;: &quot;string&quot;,
+            &quot;enum&quot;: [&quot;Appending&quot;, &quot;Unloaded&quot;, &quot;Loaded&quot;, &quot;Dispatching&quot;]
+        },
+        {
</ins><span class="cx">             &quot;id&quot;: &quot;ReplayPosition&quot;,
</span><span class="cx">             &quot;type&quot;: &quot;object&quot;,
</span><span class="cx">             &quot;properties&quot;: [
</span><span class="lines">@@ -112,6 +122,17 @@
</span><span class="cx">             ]
</span><span class="cx">         },
</span><span class="cx">         {
</span><ins>+            &quot;name&quot;: &quot;currentReplayState&quot;,
+            &quot;description&quot;: &quot;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.&quot;,
+            &quot;returns&quot;: [
+                { &quot;name&quot;: &quot;sessionIdentifier&quot;, &quot;$ref&quot;: &quot;SessionIdentifier&quot; },
+                { &quot;name&quot;: &quot;segmentIdentifier&quot;, &quot;$ref&quot;: &quot;SegmentIdentifier&quot;, &quot;optional&quot;: true, &quot;description&quot;: &quot;If no segment is currently loaded, then there is no valid segment identifier.&quot; },
+                { &quot;name&quot;: &quot;sessionState&quot;, &quot;$ref&quot;: &quot;SessionState&quot; },
+                { &quot;name&quot;: &quot;segmentState&quot;, &quot;$ref&quot;: &quot;SegmentState&quot; },
+                { &quot;name&quot;: &quot;replayPosition&quot;, &quot;$ref&quot;: &quot;ReplayPosition&quot; }
+            ]
+        },
+        {
</ins><span class="cx">             &quot;name&quot;: &quot;getAvailableSessions&quot;,
</span><span class="cx">             &quot;description&quot;: &quot;Returns identifiers of all available sessions.&quot;,
</span><span class="cx">             &quot;returns&quot;: [
</span></span></pre></div>
<a id="trunkSourceWebCorereplayReplayControllerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/replay/ReplayController.h (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -136,10 +136,15 @@
</span><span class="cx">     void willDispatchEvent(const Event&amp;, Frame*);
</span><span class="cx"> 
</span><span class="cx">     Page&amp; page() const { return m_page; }
</span><ins>+
</ins><span class="cx">     SessionState sessionState() const { return m_sessionState; }
</span><ins>+    SegmentState segmentState() const { return m_segmentState; }
+
</ins><span class="cx">     PassRefPtr&lt;ReplaySession&gt; loadedSession() const;
</span><span class="cx">     PassRefPtr&lt;ReplaySessionSegment&gt; loadedSegment() const;
</span><ins>+
</ins><span class="cx">     JSC::InputCursor&amp; activeInputCursor() const;
</span><ins>+    ReplayPosition currentPosition() const { return m_currentPosition; }
</ins><span class="cx"> 
</span><span class="cx"> private:
</span><span class="cx">     // EventLoopInputDispatcherClient API
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,5 +1,59 @@
</span><span class="cx"> 2014-08-05  Brian J. Burg  &lt;burg@cs.washington.edu&gt;
</span><span class="cx"> 
</span><ins>+        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  &lt;burg@cs.washington.edu&gt;
+
</ins><span class="cx">         Web Replay: rename protocol methods for getting replay session/segment data
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=135618
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersReplayManagerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/ReplayManager.js (172086 => 172087)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -34,6 +34,7 @@
</span><span class="cx">     this._activeSessionIdentifier = null;
</span><span class="cx">     this._activeSegmentIdentifier = null;
</span><span class="cx">     this._currentPosition = new WebInspector.ReplayPosition(0, 0);
</span><ins>+    this._initialized = false;
</ins><span class="cx"> 
</span><span class="cx">     // These hold actual instances of sessions and segments.
</span><span class="cx">     this._sessions = new Map;
</span><span class="lines">@@ -49,10 +50,27 @@
</span><span class="cx">     if (!window.ReplayAgent)
</span><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    ReplayAgent.getAvailableSessions.promise()
</del><ins>+    var instance = this;
+
+    this._initializationPromise = ReplayAgent.currentReplayState.promise()
</ins><span class="cx">         .then(function(payload) {
</span><ins>+            console.assert(payload.sessionState in WebInspector.ReplayManager.SessionState, &quot;Unknown session state: &quot; + payload.sessionState);
+            console.assert(payload.segmentState in WebInspector.ReplayManager.SegmentState, &quot;Unknown segment state: &quot; + 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) {
</ins><span class="cx">             for (var sessionId of payload.ids)
</span><del>-                WebInspector.replayManager.sessionCreated(sessionId);
</del><ins>+                instance.sessionCreated(sessionId);
+        }).catch(function(err) {
+            console.error(&quot;ReplayManager initialization failed: &quot;, err);
</ins><span class="cx">         });
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="lines">@@ -99,42 +117,56 @@
</span><span class="cx"> 
</span><span class="cx">     // Public
</span><span class="cx"> 
</span><ins>+    // The following state is invalid unless called from a function that's chained
+    // to the (resolved) ReplayManager.waitUntilInitialized promise.
</ins><span class="cx">     get sessionState()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._sessionState;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get segmentState()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._segmentState;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get activeSessionIdentifier()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._activeSessionIdentifier;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get activeSegmentIdentifier()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._activeSegmentIdentifier;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get playbackSpeed()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._playbackSpeed;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     set playbackSpeed(value)
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         this._playbackSpeed = value;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     get currentPosition()
</span><span class="cx">     {
</span><ins>+        console.assert(this._initialized);
</ins><span class="cx">         return this._currentPosition;
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     // These return promises even if the relevant instance is already created.
</span><ins>+    waitUntilInitialized: function()
+    {
+        return this._initializationPromise;
+    },
+
</ins><span class="cx">     getSession: function(sessionId)
</span><span class="cx">     {
</span><span class="cx">         if (this._sessionPromises.has(sessionId))
</span><span class="lines">@@ -165,8 +197,15 @@
</span><span class="cx"> 
</span><span class="cx">     // Protected (called by ReplayObserver)
</span><span class="cx"> 
</span><ins>+    // 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.
+
</ins><span class="cx">     captureStarted: function()
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.captureStarted.bind(this));
+
</ins><span class="cx">         this._changeSessionState(WebInspector.ReplayManager.SessionState.Capturing);
</span><span class="cx"> 
</span><span class="cx">         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.CaptureStarted);
</span><span class="lines">@@ -174,6 +213,9 @@
</span><span class="cx"> 
</span><span class="cx">     captureStopped: function()
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.captureStopped.bind(this));
+
</ins><span class="cx">         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
</span><span class="cx">         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
</span><span class="cx"> 
</span><span class="lines">@@ -182,6 +224,9 @@
</span><span class="cx"> 
</span><span class="cx">     playbackStarted: function()
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackStarted.bind(this));
+
</ins><span class="cx">         if (this.sessionState === WebInspector.ReplayManager.SessionState.Inactive)
</span><span class="cx">             this._changeSessionState(WebInspector.ReplayManager.SessionState.Replaying);
</span><span class="cx"> 
</span><span class="lines">@@ -192,6 +237,9 @@
</span><span class="cx"> 
</span><span class="cx">     playbackHitPosition: function(replayPosition, timestamp)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackHitPosition.bind(this, replayPosition, timestamp));
+
</ins><span class="cx">         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
</span><span class="cx">         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Dispatching);
</span><span class="cx">         console.assert(replayPosition instanceof WebInspector.ReplayPosition);
</span><span class="lines">@@ -200,8 +248,11 @@
</span><span class="cx">         this.dispatchEventToListeners(WebInspector.ReplayManager.Event.PlaybackPositionChanged);
</span><span class="cx">     },
</span><span class="cx"> 
</span><del>-    playbackPaused: function(mark)
</del><ins>+    playbackPaused: function(position)
</ins><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackPaused.bind(this, position));
+
</ins><span class="cx">         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
</span><span class="cx">         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Loaded);
</span><span class="cx"> 
</span><span class="lines">@@ -210,6 +261,9 @@
</span><span class="cx"> 
</span><span class="cx">     playbackFinished: function()
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.playbackFinished.bind(this));
+
</ins><span class="cx">         this._changeSessionState(WebInspector.ReplayManager.SessionState.Inactive);
</span><span class="cx">         console.assert(this.segmentState === WebInspector.ReplayManager.SegmentState.Unloaded);
</span><span class="cx"> 
</span><span class="lines">@@ -218,13 +272,15 @@
</span><span class="cx"> 
</span><span class="cx">     sessionCreated: function(sessionId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionCreated.bind(this, sessionId));
+
</ins><span class="cx">         console.assert(!this._sessions.has(sessionId), &quot;Tried to add duplicate session identifier:&quot;, sessionId);
</span><span class="cx">         var sessionMap = this._sessions;
</span><span class="cx">         this.getSession(sessionId)
</span><span class="cx">             .then(function(session) {
</span><span class="cx">                 sessionMap.set(sessionId, session);
</span><del>-            })
-            .catch(function(error) {
</del><ins>+            }).catch(function(error) {
</ins><span class="cx">                 console.error(&quot;Error obtaining session data: &quot;, error);
</span><span class="cx">             });
</span><span class="cx"> 
</span><span class="lines">@@ -233,6 +289,9 @@
</span><span class="cx"> 
</span><span class="cx">     sessionModified: function(sessionId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionModified.bind(this, sessionId));
+
</ins><span class="cx">         this.getSession(sessionId).then(function(session) {
</span><span class="cx">             session.segmentsChanged();
</span><span class="cx">         });
</span><span class="lines">@@ -240,6 +299,9 @@
</span><span class="cx"> 
</span><span class="cx">     sessionRemoved: function(sessionId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.sessionRemoved.bind(this, sessionId));
+
</ins><span class="cx">         console.assert(this._sessions.has(sessionId), &quot;Unknown session identifier:&quot;, sessionId);
</span><span class="cx"> 
</span><span class="cx">         if (!this._sessionPromises.has(sessionId))
</span><span class="lines">@@ -251,8 +313,7 @@
</span><span class="cx">         this.getSession(sessionId)
</span><span class="cx">             .catch(function(error) {
</span><span class="cx">                 return Promise.resolve();
</span><del>-            })
-            .then(function() {
</del><ins>+            }).then(function() {
</ins><span class="cx">                 manager._sessionPromises.delete(sessionId);
</span><span class="cx">                 var removedSession = manager._sessions.take(sessionId);
</span><span class="cx">                 console.assert(removedSession);
</span><span class="lines">@@ -262,6 +323,9 @@
</span><span class="cx"> 
</span><span class="cx">     segmentCreated: function(segmentId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentCreated.bind(this, segmentId));
+
</ins><span class="cx">         console.assert(!this._segments.has(segmentId), &quot;Tried to add duplicate segment identifier:&quot;, segmentId);
</span><span class="cx"> 
</span><span class="cx">         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Appending);
</span><span class="lines">@@ -277,6 +341,9 @@
</span><span class="cx"> 
</span><span class="cx">     segmentCompleted: function(segmentId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentCompleted.bind(this, segmentId));
+
</ins><span class="cx">         var placeholderSegment = this._segments.take(segmentId);
</span><span class="cx">         console.assert(placeholderSegment instanceof WebInspector.IncompleteSessionSegment);
</span><span class="cx">         this._segmentPromises.delete(segmentId);
</span><span class="lines">@@ -285,14 +352,16 @@
</span><span class="cx">         this.getSegment(segmentId)
</span><span class="cx">             .then(function(segment) {
</span><span class="cx">                 segmentMap.set(segmentId, segment);
</span><del>-            })
-            .catch(function(error) {
</del><ins>+            }).catch(function(error) {
</ins><span class="cx">                 console.error(&quot;Error obtaining segment data: &quot;, error);
</span><span class="cx">             });
</span><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     segmentRemoved: function(segmentId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentRemoved.bind(this, segmentId));
+
</ins><span class="cx">         console.assert(this._segments.has(segmentId), &quot;Unknown segment identifier:&quot;, segmentId);
</span><span class="cx"> 
</span><span class="cx">         if (!this._segmentPromises.has(segmentId))
</span><span class="lines">@@ -304,8 +373,7 @@
</span><span class="cx">         this.getSegment(segmentId)
</span><span class="cx">             .catch(function(error) {
</span><span class="cx">                 return Promise.resolve();
</span><del>-            })
-            .then(function() {
</del><ins>+            }).then(function() {
</ins><span class="cx">                 manager._segmentPromises.delete(segmentId);
</span><span class="cx">                 var removedSegment = manager._segments.take(segmentId);
</span><span class="cx">                 console.assert(removedSegment);
</span><span class="lines">@@ -315,6 +383,9 @@
</span><span class="cx"> 
</span><span class="cx">     segmentLoaded: function(segmentId)
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentLoaded.bind(this, segmentId));
+
</ins><span class="cx">         console.assert(this._segments.has(segmentId), &quot;Unknown segment identifier:&quot;, segmentId);
</span><span class="cx"> 
</span><span class="cx">         console.assert(this.sessionState !== WebInspector.ReplayManager.SessionState.Capturing);
</span><span class="lines">@@ -327,6 +398,9 @@
</span><span class="cx"> 
</span><span class="cx">     segmentUnloaded: function()
</span><span class="cx">     {
</span><ins>+        if (!this._initialized)
+            return this.waitUntilInitialized().then(this.segmentUnloaded.bind(this));
+
</ins><span class="cx">         console.assert(this.sessionState === WebInspector.ReplayManager.SessionState.Replaying);
</span><span class="cx">         this._changeSegmentState(WebInspector.ReplayManager.SegmentState.Unloaded);
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>