diff options
author | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2017-05-30 12:48:17 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2017-05-30 12:48:17 +0200 |
commit | 881da28418d380042aa95a97f0cbd42560a64f7c (patch) | |
tree | a794dff3274695e99c651902dde93d934ea7a5af /Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js | |
parent | 7e104c57a70fdf551bb3d22a5d637cdcbc69dbea (diff) | |
parent | 0fcedcd17cc00d3dd44c718b3cb36c1033319671 (diff) | |
download | qtwebkit-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.js | 808 |
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 |