# HG changeset patch # User J. Ryan Stinnett # Date 1520879078 18000 # Node ID 49cce2c9e2ab21606a9abcdc0ab0af991806957f # Parent 1a28ad82c8085121af3b788997d062197da9b116 Bug 1443081 - Apply spacing via `eslint --fix` for DevTools - part 19. r=jdescottes MozReview-Commit-ID: 2RVNt140Zte diff --git a/devtools/server/performance/memory.js b/devtools/server/performance/memory.js --- a/devtools/server/performance/memory.js +++ b/devtools/server/performance/memory.js @@ -44,17 +44,17 @@ function Memory(parent, frameCache = new this._onGarbageCollection = this._onGarbageCollection.bind(this); this._emitAllocations = this._emitAllocations.bind(this); this._onWindowReady = this._onWindowReady.bind(this); EventEmitter.on(this.parent, "window-ready", this._onWindowReady); } Memory.prototype = { - destroy: function () { + destroy: function() { EventEmitter.off(this.parent, "window-ready", this._onWindowReady); this._mgr = null; if (this.state === "attached") { this.detach(); } }, @@ -67,85 +67,85 @@ Memory.prototype = { /** * Attach to this MemoryBridge. * * This attaches the MemoryBridge's Debugger instance so that you can start * recording allocations or take a census of the heap. In addition, the * MemoryBridge will start emitting GC events. */ - attach: expectState("detached", function () { + attach: expectState("detached", function() { this.dbg.addDebuggees(); this.dbg.memory.onGarbageCollection = this._onGarbageCollection.bind(this); this.state = "attached"; }, "attaching to the debugger"), /** * Detach from this MemoryBridge. */ - detach: expectState("attached", function () { + detach: expectState("attached", function() { this._clearDebuggees(); this.dbg.enabled = false; this._dbg = null; this.state = "detached"; }, "detaching from the debugger"), /** * Gets the current MemoryBridge attach/detach state. */ - getState: function () { + getState: function() { return this.state; }, - _clearDebuggees: function () { + _clearDebuggees: function() { if (this._dbg) { if (this.isRecordingAllocations()) { this.dbg.memory.drainAllocationsLog(); } this._clearFrames(); this.dbg.removeAllDebuggees(); } }, - _clearFrames: function () { + _clearFrames: function() { if (this.isRecordingAllocations()) { this._frameCache.clearFrames(); } }, /** * Handler for the parent actor's "window-ready" event. */ - _onWindowReady: function ({ isTopLevel }) { + _onWindowReady: function({ isTopLevel }) { if (this.state == "attached") { this._clearDebuggees(); if (isTopLevel && this.isRecordingAllocations()) { this._frameCache.initFrames(); } this.dbg.addDebuggees(); } }, /** * Returns a boolean indicating whether or not allocation * sites are being tracked. */ - isRecordingAllocations: function () { + isRecordingAllocations: function() { return this.dbg.memory.trackingAllocationSites; }, /** * Save a heap snapshot scoped to the current debuggees' portion of the heap * graph. * * @param {Object|null} boundaries * * @returns {String} The snapshot id. */ - saveHeapSnapshot: expectState("attached", function (boundaries = null) { + saveHeapSnapshot: expectState("attached", function(boundaries = null) { // If we are observing the whole process, then scope the snapshot // accordingly. Otherwise, use the debugger's debuggees. if (!boundaries) { if (this.parent instanceof ChromeActor || this.parent instanceof ChildProcessActor) { boundaries = { runtime: true }; } else { boundaries = { debugger: this.dbg }; @@ -153,17 +153,17 @@ Memory.prototype = { } return ChromeUtils.saveHeapSnapshotGetId(boundaries); }, "saveHeapSnapshot"), /** * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for * more information. */ - takeCensus: expectState("attached", function () { + takeCensus: expectState("attached", function() { return this.dbg.memory.takeCensus(); }, "taking census"), /** * Start recording allocation sites. * * @param {number} options.probability * The probability we sample any given allocation when recording @@ -172,17 +172,17 @@ Memory.prototype = { * The maximum number of allocation events to keep in the * log. If new allocs occur while at capacity, oldest * allocations are lost. Must fit in a 32 bit signed integer. * @param {number} options.drainAllocationsTimeout * A number in milliseconds of how often, at least, an `allocation` * event gets emitted (and drained), and also emits and drains on every * GC event, resetting the timer. */ - startRecordingAllocations: expectState("attached", function (options = {}) { + startRecordingAllocations: expectState("attached", function(options = {}) { if (this.isRecordingAllocations()) { return this._getCurrentTime(); } this._frameCache.initFrames(); this.dbg.memory.allocationSamplingProbability = options.probability != null ? options.probability @@ -205,17 +205,17 @@ Memory.prototype = { this.dbg.memory.trackingAllocationSites = true; return this._getCurrentTime(); }, "starting recording allocations"), /** * Stop recording allocation sites. */ - stopRecordingAllocations: expectState("attached", function () { + stopRecordingAllocations: expectState("attached", function() { if (!this.isRecordingAllocations()) { return this._getCurrentTime(); } this.dbg.memory.trackingAllocationSites = false; this._clearFrames(); if (this._poller) { this._poller.disarm(); @@ -224,17 +224,17 @@ Memory.prototype = { return this._getCurrentTime(); }, "stopping recording allocations"), /** * Return settings used in `startRecordingAllocations` for `probability` * and `maxLogLength`. Currently only uses in tests. */ - getAllocationsSettings: expectState("attached", function () { + getAllocationsSettings: expectState("attached", function() { return { maxLogLength: this.dbg.memory.maxAllocationsLogLength, probability: this.dbg.memory.allocationSamplingProbability }; }, "getting allocations settings"), /** * Get a list of the most recent allocations since the last time we got @@ -287,17 +287,17 @@ Memory.prototype = { * * In the future, we might want to split out a frame's "source" and * "functionDisplayName" properties out the same way we have split * frames out with the "frames" array. While this would further * compress the size of the response packet, it would increase CPU * usage to build the packet, and it should, of course, be guided by * profiling and done only when necessary. */ - getAllocations: expectState("attached", function () { + getAllocations: expectState("attached", function() { if (this.dbg.memory.allocationsLogOverflowed) { // Since the last time we drained the allocations log, there have been // more allocations than the log's capacity, and we lost some data. There // isn't anything actionable we can do about this, but put a message in // the browser console so we at least know that it occurred. reportException("MemoryBridge.prototype.getAllocations", "Warning: allocations log overflowed and lost some data."); } @@ -328,38 +328,38 @@ Memory.prototype = { } return this._frameCache.updateFramePacket(packet); }, "getting allocations"), /* * Force a browser-wide GC. */ - forceGarbageCollection: function () { + forceGarbageCollection: function() { for (let i = 0; i < 3; i++) { Cu.forceGC(); } }, /** * Force an XPCOM cycle collection. For more information on XPCOM cycle * collection, see * https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does */ - forceCycleCollection: function () { + forceCycleCollection: function() { Cu.forceCC(); }, /** * A method that returns a detailed breakdown of the memory consumption of the * associated window. * * @returns object */ - measure: function () { + measure: function() { let result = {}; let jsObjectsSize = {}; let jsStringsSize = {}; let jsOtherSize = {}; let domSize = {}; let styleSize = {}; let otherSize = {}; @@ -382,47 +382,47 @@ Memory.prototype = { result.nonJSMilliseconds = nonJSMilliseconds.value.toFixed(1); } catch (e) { reportException("MemoryBridge.prototype.measure", e); } return result; }, - residentUnique: function () { + residentUnique: function() { return this._mgr.residentUnique; }, /** * Handler for GC events on the Debugger.Memory instance. */ - _onGarbageCollection: function (data) { + _onGarbageCollection: function(data) { this.emit("garbage-collection", data); // If `drainAllocationsTimeout` set, fire an allocations event with the drained log, // which will restart the timer. if (this._poller) { this._poller.disarm(); this._emitAllocations(); } }, /** * Called on `drainAllocationsTimeoutTimer` interval if and only if set * during `startRecordingAllocations`, or on a garbage collection event if * drainAllocationsTimeout was set. * Drains allocation log and emits as an event and restarts the timer. */ - _emitAllocations: function () { + _emitAllocations: function() { this.emit("allocations", this.getAllocations()); this._poller.arm(); }, /** * Accesses the docshell to return the current process time. */ - _getCurrentTime: function () { + _getCurrentTime: function() { return (this.parent.isRootActor ? this.parent.docShell : this.parent.originalDocShell).now(); }, }; exports.Memory = Memory; diff --git a/devtools/server/performance/profiler.js b/devtools/server/performance/profiler.js --- a/devtools/server/performance/profiler.js +++ b/devtools/server/performance/profiler.js @@ -29,17 +29,17 @@ var DEFAULT_PROFILER_OPTIONS = { interval: 1, features: ["js"], threadFilters: ["GeckoMain"] }; /** * Main interface for interacting with nsIProfiler */ -const ProfilerManager = (function () { +const ProfilerManager = (function() { let consumers = new Set(); return { // How often the "profiler-status" is emitted _profilerStatusInterval: BUFFER_STATUS_INTERVAL_DEFAULT, // How many subscribers there @@ -52,30 +52,30 @@ const ProfilerManager = (function () { * The nsIProfiler is target agnostic and interacts with the whole platform. * Therefore, special care needs to be given to make sure different profiler * consumers (i.e. "toolboxes") don't interfere with each other. Register * the profiler actor instances here. * * @param Profiler instance * A profiler actor class. */ - addInstance: function (instance) { + addInstance: function(instance) { consumers.add(instance); // Lazily register events this.registerEventListeners(); }, /** * Remove the profiler actor instances here. * * @param Profiler instance * A profiler actor class. */ - removeInstance: function (instance) { + removeInstance: function(instance) { consumers.delete(instance); if (this.length < 0) { let msg = "Somehow the number of started profilers is now negative."; DevToolsUtils.reportException("Profiler", msg); } if (this.length === 0) { @@ -90,17 +90,17 @@ const ProfilerManager = (function () { * * @param {number} entries [optional] * @param {number} interval [optional] * @param {Array} features [optional] * @param {Array} threadFilters [description] * * @return {object} */ - start: function (options = {}) { + start: function(options = {}) { let config = this._profilerStartOptions = { entries: options.entries || DEFAULT_PROFILER_OPTIONS.entries, interval: options.interval || DEFAULT_PROFILER_OPTIONS.interval, features: options.features || DEFAULT_PROFILER_OPTIONS.features, threadFilters: options.threadFilters || DEFAULT_PROFILER_OPTIONS.threadFilters, }; // The start time should be before any samples we might be @@ -128,17 +128,17 @@ const ProfilerManager = (function () { let { position, totalSize, generation } = this.getBufferInfo(); return { started: true, position, totalSize, generation, currentTime }; }, /** * Attempts to stop the nsIProfiler module. */ - stop: function () { + stop: function() { // Actually stop the profiler only if the last client has stopped profiling. // Since this is used as a root actor, and the profiler module interacts // with the whole platform, we need to avoid a case in which the profiler // is stopped when there might be other clients still profiling. // Also check for `started` to only stop the profiler when the actor // actually started it. This is to prevent stopping the profiler initiated // by some other code, like Talos. if (this.length <= 1 && this.started) { @@ -178,70 +178,70 @@ const ProfilerManager = (function () { * Since the circular buffer will only grow as long as the profiler lives, * the buffer can contain unwanted samples. Pass in a `startTime` to only * retrieve samples that took place after the `startTime`, with 0 being * when the profiler just started. * @param boolean stringify * Whether or not the returned profile object should be a string or not to * save JSON parse/stringify cycle if emitting over RDP. */ - getProfile: function (options) { + getProfile: function(options) { let startTime = options.startTime || 0; let profile = options.stringify ? Services.profiler.GetProfile(startTime) : Services.profiler.getProfileData(startTime); return { profile: profile, currentTime: Services.profiler.getElapsedTime() }; }, /** * Returns an array of feature strings, describing the profiler features * that are available on this platform. Can be called while the profiler * is stopped. * * @return {object} */ - getFeatures: function () { + getFeatures: function() { return { features: Services.profiler.GetFeatures([]) }; }, /** * Returns an object with the values of the current status of the * circular buffer in the profiler, returning `position`, `totalSize`, * and the current `generation` of the buffer. * * @return {object} */ - getBufferInfo: function () { + getBufferInfo: function() { let position = {}, totalSize = {}, generation = {}; Services.profiler.GetBufferInfo(position, totalSize, generation); return { position: position.value, totalSize: totalSize.value, generation: generation.value }; }, /** * Returns the configuration used that was originally passed in to start up the * profiler. Used for tests, and does not account for others using nsIProfiler. * * @param {object} */ - getStartOptions: function () { + getStartOptions: function() { return this._profilerStartOptions || {}; }, /** * Verifies whether or not the nsIProfiler module has started. * If already active, the current time is also returned. * * @return {object} */ - isActive: function () { + isActive: function() { let isActive = Services.profiler.IsActive(); let elapsedTime = isActive ? Services.profiler.getElapsedTime() : undefined; let { position, totalSize, generation } = this.getBufferInfo(); return { isActive, currentTime: elapsedTime, position, totalSize, @@ -270,17 +270,17 @@ const ProfilerManager = (function () { }, /** * Callback for all observed notifications. * @param object subject * @param string topic * @param object data */ - observe: sanitizeHandler(function (subject, topic, data) { + observe: sanitizeHandler(function(subject, topic, data) { let details; // An optional label may be specified when calling `console.profile`. // If that's the case, stringify it and send it over with the response. let { action, arguments: args } = subject || {}; let profileLabel = args && args.length > 0 ? `${args[0]}` : void 0; // If the event was generated from `console.profile` or `console.profileEnd` @@ -318,93 +318,93 @@ const ProfilerManager = (function () { * - "console-api-profiler" * - "profiler-started" * - "profiler-stopped" * - "profiler-status" * * The ProfilerManager listens to all events, and individual * consumers filter which events they are interested in. */ - registerEventListeners: function () { + registerEventListeners: function() { if (!this._eventsRegistered) { PROFILER_SYSTEM_EVENTS.forEach(eventName => Services.obs.addObserver(this, eventName)); this._eventsRegistered = true; } }, /** * Unregisters handlers for all system events. */ - unregisterEventListeners: function () { + unregisterEventListeners: function() { if (this._eventsRegistered) { PROFILER_SYSTEM_EVENTS.forEach(eventName => Services.obs.removeObserver(this, eventName)); this._eventsRegistered = false; } }, /** * Takes an event name and additional data and emits them * through each profiler instance that is subscribed to the event. * * @param {string} eventName * @param {object} data */ - emitEvent: function (eventName, data) { + emitEvent: function(eventName, data) { let subscribers = Array.from(consumers).filter(c => { return c.subscribedEvents.has(eventName); }); for (let subscriber of subscribers) { subscriber.emit(eventName, data); } }, /** * Updates the frequency that the "profiler-status" event is emitted * during recording. * * @param {number} interval */ - setProfilerStatusInterval: function (interval) { + setProfilerStatusInterval: function(interval) { this._profilerStatusInterval = interval; if (this._poller) { this._poller._delayMs = interval; } }, - subscribeToProfilerStatusEvents: function () { + subscribeToProfilerStatusEvents: function() { this._profilerStatusSubscribers++; this._updateProfilerStatusPolling(); }, - unsubscribeToProfilerStatusEvents: function () { + unsubscribeToProfilerStatusEvents: function() { this._profilerStatusSubscribers--; this._updateProfilerStatusPolling(); }, /** * Will enable or disable "profiler-status" events depending on * if there are subscribers and if the profiler is current recording. */ - _updateProfilerStatusPolling: function () { + _updateProfilerStatusPolling: function() { if (this._profilerStatusSubscribers > 0 && Services.profiler.IsActive()) { if (!this._poller) { this._poller = new DeferredTask(this._emitProfilerStatus.bind(this), this._profilerStatusInterval, 0); } this._poller.arm(); } else if (this._poller) { // No subscribers; turn off if it exists. this._poller.disarm(); } }, - _emitProfilerStatus: function () { + _emitProfilerStatus: function() { this.emitEvent("profiler-status", this.isActive()); this._poller.arm(); } }; })(); /** * The profiler actor provides remote access to the built-in nsIProfiler module. @@ -560,17 +560,17 @@ function cycleBreaker(key, value) { * This spends some CPU cycles, but it's simple. * * @TODO Also wraps it in a `makeInfallible` -- is this still necessary? * * @param {function} handler * @return {function} */ function sanitizeHandler(handler, identifier) { - return DevToolsUtils.makeInfallible(function (subject, topic, data) { + return DevToolsUtils.makeInfallible(function(subject, topic, data) { subject = (subject && !Cu.isXrayWrapper(subject) && subject.wrappedJSObject) || subject; subject = JSON.parse(JSON.stringify(subject, cycleBreaker)); data = (data && !Cu.isXrayWrapper(data) && data.wrappedJSObject) || data; data = JSON.parse(JSON.stringify(data, cycleBreaker)); // Pass in clean data to the underlying handler return handler.call(this, subject, topic, data); diff --git a/devtools/server/performance/recorder.js b/devtools/server/performance/recorder.js --- a/devtools/server/performance/recorder.js +++ b/devtools/server/performance/recorder.js @@ -62,17 +62,17 @@ PerformanceRecorder.prototype = { * If in the process of opening, or already open, nothing happens. * * @param {Object} options.systemClient * Metadata about the client's system to attach to the recording models. * * @return object * A promise that is resolved once the connection is established. */ - connect: function (options) { + connect: function(options) { if (this._connected) { return; } // Sets `this._profiler`, `this._timeline` and `this._memory`. // Only initialize the timeline and memory fronts if the respective actors // are available. Older Gecko versions don't have existing implementations, // in which case all the methods we need can be easily mocked. @@ -82,69 +82,69 @@ PerformanceRecorder.prototype = { this._systemClient = options.systemClient; this._connected = true; }, /** * Destroys this connection. */ - destroy: function () { + destroy: function() { this._unregisterListeners(); this._disconnectComponents(); this._connected = null; this._profiler = null; this._timeline = null; this._memory = null; this._target = null; this._client = null; }, /** * Initializes fronts and connects to the underlying actors using the facades * found in ./actors.js. */ - _connectComponents: function () { + _connectComponents: function() { this._profiler = new Profiler(this.tabActor); this._memory = new Memory(this.tabActor); this._timeline = new Timeline(this.tabActor); this._profiler.registerEventNotifications({ events: PROFILER_EVENTS }); }, /** * Registers listeners on events from the underlying * actors, so the connection can handle them. */ - _registerListeners: function () { + _registerListeners: function() { this._timeline.on("*", this._onTimelineData); this._memory.on("*", this._onTimelineData); this._profiler.on("*", this._onProfilerEvent); }, /** * Unregisters listeners on events on the underlying actors. */ - _unregisterListeners: function () { + _unregisterListeners: function() { this._timeline.off("*", this._onTimelineData); this._memory.off("*", this._onTimelineData); this._profiler.off("*", this._onProfilerEvent); }, /** * Closes the connections to non-profiler actors. */ - _disconnectComponents: function () { + _disconnectComponents: function() { this._profiler.unregisterEventNotifications({ events: PROFILER_EVENTS }); this._profiler.destroy(); this._timeline.destroy(); this._memory.destroy(); }, - _onProfilerEvent: function (topic, data) { + _onProfilerEvent: function(topic, data) { if (topic === "console-api-profiler") { if (data.subject.action === "profile") { this._onConsoleProfileStart(data.details); } else if (data.subject.action === "profileEnd") { this._onConsoleProfileEnd(data.details); } } else if (topic === "profiler-stopped") { this._onProfilerUnexpectedlyStopped(); @@ -221,29 +221,29 @@ PerformanceRecorder.prototype = { } await this.stopRecording(model); }, /** * TODO handle bug 1144438 */ - _onProfilerUnexpectedlyStopped: function () { + _onProfilerUnexpectedlyStopped: function() { Cu.reportError("Profiler unexpectedly stopped.", arguments); }, /** * Called whenever there is timeline data of any of the following types: * - markers * - frames * - memory * - ticks * - allocations */ - _onTimelineData: function (eventName, ...data) { + _onTimelineData: function(eventName, ...data) { let eventData = Object.create(null); switch (eventName) { case "markers": { eventData = { markers: data[0], endTime: data[1] }; break; } case "ticks": { @@ -272,17 +272,17 @@ PerformanceRecorder.prototype = { this.emit("timeline-data", eventName, eventData, activeRecordings); } }, /** * Checks whether or not recording is currently supported. At the moment, * this is only influenced by private browsing mode and the profiler. */ - canCurrentlyRecord: function () { + canCurrentlyRecord: function() { let success = true; let reasons = []; if (!Profiler.canProfile()) { success = false; reasons.push("profiler-unavailable"); } @@ -307,17 +307,17 @@ PerformanceRecorder.prototype = { * @param string options.label * @param boolean options.realtimeMarkers * @return object * A promise that is resolved once recording has started. */ async startRecording(options) { let profilerStart, timelineStart, memoryStart; - profilerStart = (async function () { + profilerStart = (async function() { let data = await this._profiler.isActive(); if (data.isActive) { return data; } let startData = await this._profiler.start( mapRecordingOptions("profiler", options) ); @@ -433,43 +433,43 @@ PerformanceRecorder.prototype = { }, /** * Checks all currently stored recording handles and returns a boolean * if there is a session currently being recorded. * * @return Boolean */ - isRecording: function () { + isRecording: function() { return this._recordings.some(h => h.isRecording()); }, /** * Returns all current recordings. */ - getRecordings: function () { + getRecordings: function() { return this._recordings; }, /** * Sets how often the "profiler-status" event should be emitted. * Used in tests. */ - setProfilerStatusInterval: function (n) { + setProfilerStatusInterval: function(n) { this._profiler.setProfilerStatusInterval(n); }, /** * Returns the configurations set on underlying components, used in tests. * Returns an object with `probability`, `maxLogLength` for allocations, and * `features`, `threadFilters`, `entries` and `interval` for profiler. * * @return {object} */ - getConfiguration: function () { + getConfiguration: function() { let allocationSettings = Object.create(null); if (this._memory.getState() === "attached") { allocationSettings = this._memory.getAllocationsSettings(); } return Object.assign({}, allocationSettings, this._profiler.getStartOptions()); }, diff --git a/devtools/server/performance/timeline.js b/devtools/server/performance/timeline.js --- a/devtools/server/performance/timeline.js +++ b/devtools/server/performance/timeline.js @@ -52,17 +52,17 @@ function Timeline(tabActor) { this._onGarbageCollection = this._onGarbageCollection.bind(this); this.tabActor.on("window-ready", this._onWindowReady); } Timeline.prototype = { /** * Destroys this actor, stopping recording first. */ - destroy: function () { + destroy: function() { this.stop(); this.tabActor.off("window-ready", this._onWindowReady); this.tabActor = null; }, /** * Get the list of docShells in the currently attached tabActor. Note that we @@ -99,17 +99,17 @@ Timeline.prototype = { return docShells; }, /** * At regular intervals, pop the markers from the docshell, and forward * markers, memory, tick and frames events, if any. */ - _pullTimelineData: function () { + _pullTimelineData: function() { let docShells = this.docShells; if (!this._isRecording || !docShells.length) { return; } let endTime = docShells[0].now(); let markers = []; @@ -173,17 +173,17 @@ Timeline.prototype = { this._dataPullTimeout = setTimeout(() => { this._pullTimelineData(); }, DEFAULT_TIMELINE_DATA_PULL_TIMEOUT); }, /** * Are we recording profile markers currently? */ - isRecording: function () { + isRecording: function() { return this._isRecording; }, /** * Start recording profile markers. * * @option {boolean} withMarkers * Boolean indicating whether or not timeline markers are emitted @@ -308,17 +308,17 @@ Timeline.prototype = { return endTime; }, /** * When a new window becomes available in the tabActor, start recording its * markers if we were recording. */ - _onWindowReady: function ({ window }) { + _onWindowReady: function({ window }) { if (this._isRecording) { let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShell); docShell.recordProfileTimelineMarkers = true; } }, @@ -326,17 +326,17 @@ Timeline.prototype = { * Fired when the Memory component emits a `garbage-collection` event. Used to * take the data and make it look like the rest of our markers. * * A GC "marker" here represents a full GC cycle, which may contain several incremental * events within its `collection` array. The marker contains a `reason` field, * indicating why there was a GC, and may contain a `nonincrementalReason` when * SpiderMonkey could not incrementally collect garbage. */ - _onGarbageCollection: function ({ + _onGarbageCollection: function({ collections, gcCycleNumber, reason, nonincrementalReason }) { let docShells = this.docShells; if (!this._isRecording || !docShells.length) { return; } let endTime = docShells[0].now();