summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
committerOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
commit881da28418d380042aa95a97f0cbd42560a64f7c (patch)
treea794dff3274695e99c651902dde93d934ea7a5af /Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
parent7e104c57a70fdf551bb3d22a5d637cdcbc69dbea (diff)
parent0fcedcd17cc00d3dd44c718b3cb36c1033319671 (diff)
downloadqtwebkit-881da28418d380042aa95a97f0cbd42560a64f7c.tar.gz
Merge 'wip/next' into dev
Change-Id: Iff9ee5e23bb326c4371ec8ed81d56f2f05d680e9
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js808
1 files changed, 808 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
new file mode 100644
index 000000000..c9da69331
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
@@ -0,0 +1,808 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.TimelineManager = class TimelineManager extends WebInspector.Object
+{
+ constructor()
+ {
+ super();
+
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ProvisionalLoadStarted, this._startAutoCapturing, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+ WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
+
+ WebInspector.heapManager.addEventListener(WebInspector.HeapManager.Event.GarbageCollected, this._garbageCollected, this);
+
+ this._persistentNetworkTimeline = new WebInspector.NetworkTimeline;
+
+ this._isCapturing = false;
+ this._isCapturingPageReload = false;
+ this._autoCapturingMainResource = null;
+ this._boundStopCapturing = this.stopCapturing.bind(this);
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents = null;
+ this._scriptProfilerRecords = null;
+
+ this._callingContextTree = null;
+
+ this.reset();
+ }
+
+ // Static
+
+ static defaultInstruments()
+ {
+ if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
+ return [new WebInspector.ScriptInstrument];
+
+ let defaults = [
+ new WebInspector.NetworkInstrument,
+ new WebInspector.LayoutInstrument,
+ new WebInspector.ScriptInstrument,
+ ];
+
+ if (WebInspector.FPSInstrument.supported())
+ defaults.push(new WebInspector.FPSInstrument);
+
+ return defaults;
+ }
+
+ // Public
+
+ reset()
+ {
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._recordings = [];
+ this._activeRecording = null;
+ this._nextRecordingIdentifier = 1;
+
+ this._loadNewRecording();
+ }
+
+ // The current recording that new timeline records will be appended to, if any.
+ get activeRecording()
+ {
+ console.assert(this._activeRecording || !this._isCapturing);
+ return this._activeRecording;
+ }
+
+ get persistentNetworkTimeline()
+ {
+ return this._persistentNetworkTimeline;
+ }
+
+ get recordings()
+ {
+ return this._recordings.slice();
+ }
+
+ get autoCaptureOnPageLoad()
+ {
+ return this._autoCaptureOnPageLoad;
+ }
+
+ set autoCaptureOnPageLoad(autoCapture)
+ {
+ this._autoCaptureOnPageLoad = autoCapture;
+ }
+
+ isCapturing()
+ {
+ return this._isCapturing;
+ }
+
+ isCapturingPageReload()
+ {
+ return this._isCapturingPageReload;
+ }
+
+ startCapturing(shouldCreateRecording)
+ {
+ console.assert(!this._isCapturing, "TimelineManager is already capturing.");
+
+ if (!this._activeRecording || shouldCreateRecording)
+ this._loadNewRecording();
+
+ this._activeRecording.start();
+ }
+
+ stopCapturing()
+ {
+ console.assert(this._isCapturing, "TimelineManager is not capturing.");
+
+ this._activeRecording.stop();
+
+ // NOTE: Always stop immediately instead of waiting for a Timeline.recordingStopped event.
+ // This way the UI feels as responsive to a stop as possible.
+ // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
+ this.capturingStopped();
+ }
+
+ unloadRecording()
+ {
+ if (!this._activeRecording)
+ return;
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._activeRecording.unloaded();
+ this._activeRecording = null;
+ }
+
+ computeElapsedTime(timestamp)
+ {
+ if (!this._activeRecording)
+ return 0;
+
+ return this._activeRecording.computeElapsedTime(timestamp);
+ }
+
+ scriptProfilerIsTracking()
+ {
+ return this._scriptProfilerRecords !== null;
+ }
+
+ // Protected
+
+ capturingStarted(startTime)
+ {
+ if (this._isCapturing)
+ return;
+
+ this._isCapturing = true;
+
+ if (startTime)
+ this.activeRecording.initializeTimeBoundsIfNecessary(startTime);
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents = [];
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStarted, {startTime});
+ }
+
+ capturingStopped(endTime)
+ {
+ if (!this._isCapturing)
+ return;
+
+ if (this._stopCapturingTimeout) {
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = undefined;
+ }
+
+ if (this._deadTimeTimeout) {
+ clearTimeout(this._deadTimeTimeout);
+ this._deadTimeTimeout = undefined;
+ }
+
+ this._isCapturing = false;
+ this._isCapturingPageReload = false;
+ this._autoCapturingMainResource = null;
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.CapturingStopped, {endTime});
+ }
+
+ eventRecorded(recordPayload)
+ {
+ // Called from WebInspector.TimelineObserver.
+
+ if (!this._isCapturing)
+ return;
+
+ var records = [];
+
+ // Iterate over the records tree using a stack. Doing this recursively has
+ // been known to cause a call stack overflow. https://webkit.org/b/79106
+ var stack = [{array: [recordPayload], parent: null, parentRecord: null, index: 0}];
+ while (stack.length) {
+ var entry = stack.lastValue;
+ var recordPayloads = entry.array;
+
+ if (entry.index < recordPayloads.length) {
+ var recordPayload = recordPayloads[entry.index];
+ var record = this._processEvent(recordPayload, entry.parent);
+ if (record) {
+ record.parent = entry.parentRecord;
+ records.push(record);
+ if (entry.parentRecord)
+ entry.parentRecord.children.push(record);
+ }
+
+ if (recordPayload.children && recordPayload.children.length)
+ stack.push({array: recordPayload.children, parent: recordPayload, parentRecord: record || entry.parentRecord, index: 0});
+ ++entry.index;
+ } else
+ stack.pop();
+ }
+
+ for (var record of records) {
+ if (record.type === WebInspector.TimelineRecord.Type.RenderingFrame) {
+ if (!record.children.length)
+ continue;
+ record.setupFrameIndex();
+ }
+
+ this._addRecord(record);
+ }
+ }
+
+ // Protected
+
+ pageDOMContentLoadedEventFired(timestamp)
+ {
+ // Called from WebInspector.PageObserver.
+
+ console.assert(this._activeRecording);
+ console.assert(isNaN(WebInspector.frameResourceManager.mainFrame.domContentReadyEventTimestamp));
+
+ let computedTimestamp = this.activeRecording.computeElapsedTime(timestamp);
+
+ WebInspector.frameResourceManager.mainFrame.markDOMContentReadyEvent(computedTimestamp);
+
+ let eventMarker = new WebInspector.TimelineMarker(computedTimestamp, WebInspector.TimelineMarker.Type.DOMContentEvent);
+ this._activeRecording.addEventMarker(eventMarker);
+ }
+
+ pageLoadEventFired(timestamp)
+ {
+ // Called from WebInspector.PageObserver.
+
+ console.assert(this._activeRecording);
+ console.assert(isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp));
+
+ let computedTimestamp = this.activeRecording.computeElapsedTime(timestamp);
+
+ WebInspector.frameResourceManager.mainFrame.markLoadEvent(computedTimestamp);
+
+ let eventMarker = new WebInspector.TimelineMarker(computedTimestamp, WebInspector.TimelineMarker.Type.LoadEvent);
+ this._activeRecording.addEventMarker(eventMarker);
+
+ this._stopAutoRecordingSoon();
+ }
+
+ memoryTrackingStart(timestamp)
+ {
+ // Called from WebInspector.MemoryObserver.
+
+ this.capturingStarted(timestamp);
+ }
+
+ memoryTrackingUpdate(event)
+ {
+ // Called from WebInspector.MemoryObserver.
+
+ if (!this._isCapturing)
+ return;
+
+ this._addRecord(new WebInspector.MemoryTimelineRecord(event.timestamp, event.categories));
+ }
+
+ memoryTrackingComplete()
+ {
+ // Called from WebInspector.MemoryObserver.
+ }
+
+ // Private
+
+ _processRecord(recordPayload, parentRecordPayload)
+ {
+ var startTime = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+ var endTime = this.activeRecording.computeElapsedTime(recordPayload.endTime);
+ var callFrames = this._callFramesFromPayload(recordPayload.stackTrace);
+
+ var significantCallFrame = null;
+ if (callFrames) {
+ for (var i = 0; i < callFrames.length; ++i) {
+ if (callFrames[i].nativeCode)
+ continue;
+ significantCallFrame = callFrames[i];
+ break;
+ }
+ }
+
+ var sourceCodeLocation = significantCallFrame && significantCallFrame.sourceCodeLocation;
+
+ switch (recordPayload.type) {
+ case TimelineAgent.EventType.ScheduleStyleRecalculation:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles, startTime, startTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.RecalculateStyles:
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles, startTime, endTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.InvalidateLayout:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout, startTime, startTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.Layout:
+ var layoutRecordType = sourceCodeLocation ? WebInspector.LayoutTimelineRecord.EventType.ForcedLayout : WebInspector.LayoutTimelineRecord.EventType.Layout;
+ var quad = new WebInspector.Quad(recordPayload.data.root);
+ return new WebInspector.LayoutTimelineRecord(layoutRecordType, startTime, endTime, callFrames, sourceCodeLocation, quad);
+
+ case TimelineAgent.EventType.Paint:
+ var quad = new WebInspector.Quad(recordPayload.data.clip);
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Paint, startTime, endTime, callFrames, sourceCodeLocation, quad);
+
+ case TimelineAgent.EventType.Composite:
+ return new WebInspector.LayoutTimelineRecord(WebInspector.LayoutTimelineRecord.EventType.Composite, startTime, endTime, callFrames, sourceCodeLocation);
+
+ case TimelineAgent.EventType.RenderingFrame:
+ if (!recordPayload.children || !recordPayload.children.length)
+ return null;
+
+ return new WebInspector.RenderingFrameTimelineRecord(startTime, endTime);
+
+ case TimelineAgent.EventType.EvaluateScript:
+ if (!sourceCodeLocation) {
+ var mainFrame = WebInspector.frameResourceManager.mainFrame;
+ var scriptResource = mainFrame.url === recordPayload.data.url ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.url, true);
+ if (scriptResource) {
+ // The lineNumber is 1-based, but we expect 0-based.
+ var lineNumber = recordPayload.data.lineNumber - 1;
+
+ // FIXME: No column number is provided.
+ sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
+ }
+ }
+
+ var profileData = recordPayload.data.profile;
+
+ var record;
+ switch (parentRecordPayload && parentRecordPayload.type) {
+ case TimelineAgent.EventType.TimerFire:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+ break;
+ default:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, null, profileData);
+ break;
+ }
+
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
+ return record;
+
+ case TimelineAgent.EventType.ConsoleProfile:
+ var profileData = recordPayload.data.profile;
+ console.assert(profileData);
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded, startTime, endTime, callFrames, sourceCodeLocation, recordPayload.data.title, profileData);
+
+ case TimelineAgent.EventType.TimerFire:
+ case TimelineAgent.EventType.EventDispatch:
+ case TimelineAgent.EventType.FireAnimationFrame:
+ // These are handled when the parent of FunctionCall or EvaluateScript.
+ break;
+
+ case TimelineAgent.EventType.FunctionCall:
+ // FunctionCall always happens as a child of another record, and since the FunctionCall record
+ // has useful info we just make the timeline record here (combining the data from both records).
+ if (!parentRecordPayload) {
+ console.warn("Unexpectedly received a FunctionCall timeline record without a parent record");
+ break;
+ }
+
+ if (!sourceCodeLocation) {
+ var mainFrame = WebInspector.frameResourceManager.mainFrame;
+ var scriptResource = mainFrame.url === recordPayload.data.scriptName ? mainFrame.mainResource : mainFrame.resourceForURL(recordPayload.data.scriptName, true);
+ if (scriptResource) {
+ // The lineNumber is 1-based, but we expect 0-based.
+ var lineNumber = recordPayload.data.scriptLine - 1;
+
+ // FIXME: No column number is provided.
+ sourceCodeLocation = scriptResource.createSourceCodeLocation(lineNumber, 0);
+ }
+ }
+
+ var profileData = recordPayload.data.profile;
+
+ var record;
+ switch (parentRecordPayload.type) {
+ case TimelineAgent.EventType.TimerFire:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.timerId, profileData);
+ break;
+ case TimelineAgent.EventType.EventDispatch:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.EventDispatched, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.type, profileData);
+ break;
+ case TimelineAgent.EventType.FireAnimationFrame:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+ case TimelineAgent.EventType.FunctionCall:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+ case TimelineAgent.EventType.RenderingFrame:
+ record = new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated, startTime, endTime, callFrames, sourceCodeLocation, parentRecordPayload.data.id, profileData);
+ break;
+
+ default:
+ console.assert(false, "Missed FunctionCall embedded inside of: " + parentRecordPayload.type);
+ break;
+ }
+
+ if (record) {
+ this._webTimelineScriptRecordsExpectingScriptProfilerEvents.push(record);
+ return record;
+ }
+ break;
+
+ case TimelineAgent.EventType.ProbeSample:
+ // Pass the startTime as the endTime since this record type has no duration.
+ sourceCodeLocation = WebInspector.probeManager.probeForIdentifier(recordPayload.data.probeId).breakpoint.sourceCodeLocation;
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.probeId);
+
+ case TimelineAgent.EventType.TimerInstall:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ let timerDetails = {timerId: recordPayload.data.timerId, timeout: recordPayload.data.timeout, repeating: !recordPayload.data.singleShot};
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerInstalled, startTime, startTime, callFrames, sourceCodeLocation, timerDetails);
+
+ case TimelineAgent.EventType.TimerRemove:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.TimerRemoved, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.timerId);
+
+ case TimelineAgent.EventType.RequestAnimationFrame:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
+
+ case TimelineAgent.EventType.CancelAnimationFrame:
+ console.assert(isNaN(endTime));
+
+ // Pass the startTime as the endTime since this record type has no duration.
+ return new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled, startTime, startTime, callFrames, sourceCodeLocation, recordPayload.data.id);
+
+ default:
+ console.error("Missing handling of Timeline Event Type: " + recordPayload.type);
+ }
+
+ return null;
+ }
+
+ _processEvent(recordPayload, parentRecordPayload)
+ {
+ switch (recordPayload.type) {
+ case TimelineAgent.EventType.TimeStamp:
+ var timestamp = this.activeRecording.computeElapsedTime(recordPayload.startTime);
+ var eventMarker = new WebInspector.TimelineMarker(timestamp, WebInspector.TimelineMarker.Type.TimeStamp, recordPayload.data.message);
+ this._activeRecording.addEventMarker(eventMarker);
+ break;
+
+ case TimelineAgent.EventType.Time:
+ case TimelineAgent.EventType.TimeEnd:
+ // FIXME: <https://webkit.org/b/150690> Web Inspector: Show console.time/timeEnd ranges in Timeline
+ // FIXME: Make use of "message" payload properties.
+ break;
+
+ default:
+ return this._processRecord(recordPayload, parentRecordPayload);
+ }
+
+ return null;
+ }
+
+ _loadNewRecording()
+ {
+ if (this._activeRecording && this._activeRecording.isEmpty())
+ return;
+
+ // FIXME: <https://webkit.org/b/153672> Web Inspector: Timelines UI redesign: Provide a way to configure which instruments to use
+ // FIXME: Move the list of instruments for a new recording to a Setting when new Instruments are supported.
+ let instruments = WebInspector.TimelineManager.defaultInstruments();
+
+ var identifier = this._nextRecordingIdentifier++;
+ var newRecording = new WebInspector.TimelineRecording(identifier, WebInspector.UIString("Timeline Recording %d").format(identifier), instruments);
+
+ this._recordings.push(newRecording);
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingCreated, {recording: newRecording});
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ var oldRecording = this._activeRecording;
+ if (oldRecording)
+ oldRecording.unloaded();
+
+ this._activeRecording = newRecording;
+
+ // COMPATIBILITY (iOS 8): When using Legacy timestamps, a navigation will have computed
+ // the main resource's will send request timestamp in terms of the last page's base timestamp.
+ // Now that we have navigated, we should reset the legacy base timestamp and the
+ // will send request timestamp for the new main resource. This way, all new timeline
+ // records will be computed relative to the new navigation.
+ if (this._autoCapturingMainResource && WebInspector.TimelineRecording.isLegacy) {
+ console.assert(this._autoCapturingMainResource.originalRequestWillBeSentTimestamp);
+ this._activeRecording.setLegacyBaseTimestamp(this._autoCapturingMainResource.originalRequestWillBeSentTimestamp);
+ this._autoCapturingMainResource._requestSentTimestamp = 0;
+ }
+
+ this.dispatchEventToListeners(WebInspector.TimelineManager.Event.RecordingLoaded, {oldRecording});
+ }
+
+ _callFramesFromPayload(payload)
+ {
+ if (!payload)
+ return null;
+
+ return payload.map(WebInspector.CallFrame.fromPayload);
+ }
+
+ _addRecord(record)
+ {
+ this._activeRecording.addRecord(record);
+
+ // Only worry about dead time after the load event.
+ if (WebInspector.frameResourceManager.mainFrame && isNaN(WebInspector.frameResourceManager.mainFrame.loadEventTimestamp))
+ this._resetAutoRecordingDeadTimeTimeout();
+ }
+
+ _startAutoCapturing(event)
+ {
+ if (!this._autoCaptureOnPageLoad)
+ return false;
+
+ if (!event.target.isMainFrame() || (this._isCapturing && !this._autoCapturingMainResource))
+ return false;
+
+ var mainResource = event.target.provisionalMainResource || event.target.mainResource;
+ if (mainResource === this._autoCapturingMainResource)
+ return false;
+
+ var oldMainResource = event.target.mainResource || null;
+ this._isCapturingPageReload = oldMainResource !== null && oldMainResource.url === mainResource.url;
+
+ if (this._isCapturing)
+ this.stopCapturing();
+
+ this._autoCapturingMainResource = mainResource;
+
+ this._loadNewRecording();
+
+ this.startCapturing();
+
+ this._addRecord(new WebInspector.ResourceTimelineRecord(mainResource));
+
+ if (this._stopCapturingTimeout)
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDuration);
+
+ return true;
+ }
+
+ _stopAutoRecordingSoon()
+ {
+ // Only auto stop when auto capturing.
+ if (!this._isCapturing || !this._autoCapturingMainResource)
+ return;
+
+ if (this._stopCapturingTimeout)
+ clearTimeout(this._stopCapturingTimeout);
+ this._stopCapturingTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent);
+ }
+
+ _resetAutoRecordingDeadTimeTimeout()
+ {
+ // Only monitor dead time when auto capturing.
+ if (!this._isCapturing || !this._autoCapturingMainResource)
+ return;
+
+ if (this._deadTimeTimeout)
+ clearTimeout(this._deadTimeTimeout);
+ this._deadTimeTimeout = setTimeout(this._boundStopCapturing, WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly);
+ }
+
+ _mainResourceDidChange(event)
+ {
+ if (event.target.isMainFrame())
+ this._persistentNetworkTimeline.reset();
+
+ var mainResource = event.target.mainResource;
+ var record = new WebInspector.ResourceTimelineRecord(mainResource);
+ if (!isNaN(record.startTime))
+ this._persistentNetworkTimeline.addRecord(record);
+
+ // Ignore resource events when there isn't a main frame yet. Those events are triggered by
+ // loading the cached resources when the inspector opens, and they do not have timing information.
+ if (!WebInspector.frameResourceManager.mainFrame)
+ return;
+
+ if (this._startAutoCapturing(event))
+ return;
+
+ if (!this._isCapturing)
+ return;
+
+ if (mainResource === this._autoCapturingMainResource)
+ return;
+
+ this._addRecord(record);
+ }
+
+ _resourceWasAdded(event)
+ {
+ var record = new WebInspector.ResourceTimelineRecord(event.data.resource);
+ if (!isNaN(record.startTime))
+ this._persistentNetworkTimeline.addRecord(record);
+
+ // Ignore resource events when there isn't a main frame yet. Those events are triggered by
+ // loading the cached resources when the inspector opens, and they do not have timing information.
+ if (!WebInspector.frameResourceManager.mainFrame)
+ return;
+
+ if (!this._isCapturing)
+ return;
+
+ this._addRecord(record);
+ }
+
+ _garbageCollected(event)
+ {
+ if (!this._isCapturing)
+ return;
+
+ let collection = event.data.collection;
+ this._addRecord(new WebInspector.ScriptTimelineRecord(WebInspector.ScriptTimelineRecord.EventType.GarbageCollected, collection.startTime, collection.endTime, null, null, collection));
+ }
+
+ _scriptProfilerTypeToScriptTimelineRecordType(type)
+ {
+ switch (type) {
+ case ScriptProfilerAgent.EventType.API:
+ return WebInspector.ScriptTimelineRecord.EventType.APIScriptEvaluated;
+ case ScriptProfilerAgent.EventType.Microtask:
+ return WebInspector.ScriptTimelineRecord.EventType.MicrotaskDispatched;
+ case ScriptProfilerAgent.EventType.Other:
+ return WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated;
+ }
+ }
+
+ scriptProfilerTrackingStarted(timestamp)
+ {
+ this._scriptProfilerRecords = [];
+
+ this.capturingStarted(timestamp);
+ }
+
+ scriptProfilerTrackingUpdated(event)
+ {
+ let {startTime, endTime, type} = event;
+ let scriptRecordType = this._scriptProfilerTypeToScriptTimelineRecordType(type);
+ let record = new WebInspector.ScriptTimelineRecord(scriptRecordType, startTime, endTime, null, null, null, null);
+ record.__scriptProfilerType = type;
+ this._scriptProfilerRecords.push(record);
+
+ // "Other" events, generated by Web content, will have wrapping Timeline records
+ // and need to be merged. Non-Other events, generated purely by the JavaScript
+ // engine or outside of the page via APIs, will not have wrapping Timeline
+ // records, so these records can just be added right now.
+ if (type !== ScriptProfilerAgent.EventType.Other)
+ this._addRecord(record);
+ }
+
+ scriptProfilerTrackingCompleted(samples)
+ {
+ console.assert(!this._webTimelineScriptRecordsExpectingScriptProfilerEvents || this._scriptProfilerRecords.length >= this._webTimelineScriptRecordsExpectingScriptProfilerEvents.length);
+
+ if (samples) {
+ if (!this._callingContextTree)
+ this._callingContextTree = new WebInspector.CallingContextTree;
+
+ // Associate the stackTraces with the ScriptProfiler created records.
+ let stackTraces = samples.stackTraces;
+ for (let i = 0; i < stackTraces.length; i++)
+ this._callingContextTree.updateTreeWithStackTrace(stackTraces[i]);
+
+ for (let i = 0; i < this._scriptProfilerRecords.length; ++i) {
+ let record = this._scriptProfilerRecords[i];
+ record.profilePayload = this._callingContextTree.toCPUProfilePayload(record.startTime, record.endTime);
+ }
+ }
+
+ // Associate the ScriptProfiler created records with Web Timeline records.
+ // Filter out the already added ScriptProfiler events which should not have been wrapped.
+ if (WebInspector.debuggableType !== WebInspector.DebuggableType.JavaScript) {
+ this._scriptProfilerRecords = this._scriptProfilerRecords.filter((x) => x.__scriptProfilerType === ScriptProfilerAgent.EventType.Other);
+ this._mergeScriptProfileRecords();
+ }
+
+ this._scriptProfilerRecords = null;
+
+ let timeline = this.activeRecording.timelineForRecordType(WebInspector.TimelineRecord.Type.Script);
+ timeline.refresh();
+ }
+
+ _mergeScriptProfileRecords()
+ {
+ let nextRecord = function(list) { return list.shift() || null; }
+ let nextWebTimelineRecord = nextRecord.bind(null, this._webTimelineScriptRecordsExpectingScriptProfilerEvents);
+ let nextScriptProfilerRecord = nextRecord.bind(null, this._scriptProfilerRecords);
+ let recordEnclosesRecord = function(record1, record2) {
+ return record1.startTime <= record2.startTime && record1.endTime >= record2.endTime;
+ }
+
+ let webRecord = nextWebTimelineRecord();
+ let profilerRecord = nextScriptProfilerRecord();
+
+ while (webRecord && profilerRecord) {
+ // Skip web records with parent web records. For example an EvaluateScript with an EvaluateScript parent.
+ if (webRecord.parent instanceof WebInspector.ScriptTimelineRecord) {
+ console.assert(recordEnclosesRecord(webRecord.parent, webRecord), "Timeline Record incorrectly wrapping another Timeline Record");
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Normal case of a Web record wrapping a Script record.
+ if (recordEnclosesRecord(webRecord, profilerRecord)) {
+ webRecord.profilePayload = profilerRecord.profilePayload;
+ profilerRecord = nextScriptProfilerRecord();
+
+ // If there are more script profile records in the same time interval, add them
+ // as individual script evaluated records with profiles. This can happen with
+ // web microtask checkpoints that are technically inside of other web records.
+ // FIXME: <https://webkit.org/b/152903> Web Inspector: Timeline Cleanup: Better Timeline Record for Microtask Checkpoints
+ while (profilerRecord && recordEnclosesRecord(webRecord, profilerRecord)) {
+ this._addRecord(profilerRecord);
+ profilerRecord = nextScriptProfilerRecord();
+ }
+
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Profiler Record is entirely after the Web Record. This would mean an empty web record.
+ if (profilerRecord.startTime > webRecord.endTime) {
+ console.warn("Unexpected case of a Timeline record not containing a ScriptProfiler event and profile data");
+ webRecord = nextWebTimelineRecord();
+ continue;
+ }
+
+ // Non-wrapped profiler record.
+ console.warn("Unexpected case of a ScriptProfiler event not being contained by a Timeline record");
+ this._addRecord(profilerRecord);
+ profilerRecord = nextScriptProfilerRecord();
+ }
+
+ // Skipping the remaining ScriptProfiler events to match the current UI for handling Timeline records.
+ // However, the remaining ScriptProfiler records are valid and could be shown.
+ // FIXME: <https://webkit.org/b/152904> Web Inspector: Timeline UI should keep up with processing all incoming records
+ }
+};
+
+WebInspector.TimelineManager.Event = {
+ RecordingCreated: "timeline-manager-recording-created",
+ RecordingLoaded: "timeline-manager-recording-loaded",
+ CapturingStarted: "timeline-manager-capturing-started",
+ CapturingStopped: "timeline-manager-capturing-stopped"
+};
+
+WebInspector.TimelineManager.MaximumAutoRecordDuration = 90000; // 90 seconds
+WebInspector.TimelineManager.MaximumAutoRecordDurationAfterLoadEvent = 10000; // 10 seconds
+WebInspector.TimelineManager.DeadTimeRequiredToStopAutoRecordingEarly = 2000; // 2 seconds