Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / inspector / InspectorTimelineAgent.cpp
1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * Copyright (C) 2014 University of Washington.
4 * Copyright (C) 2015 Apple Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *     * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *     * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 *     * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "config.h"
34 #include "InspectorTimelineAgent.h"
35
36 #include "Event.h"
37 #include "Frame.h"
38 #include "InspectorPageAgent.h"
39 #include "InstrumentingAgents.h"
40 #include "JSDOMWindow.h"
41 #include "PageScriptDebugServer.h"
42 #include "RenderView.h"
43 #include "ScriptState.h"
44 #include "TimelineRecordFactory.h"
45 #include <inspector/ScriptBreakpoint.h>
46 #include <profiler/LegacyProfiler.h>
47 #include <wtf/Stopwatch.h>
48
49 #if PLATFORM(IOS)
50 #include "RuntimeApplicationChecksIOS.h"
51 #include <WebCore/WebCoreThread.h>
52 #endif
53
54 #if PLATFORM(COCOA)
55 #include <WebCore/RunLoopObserver.h>
56 #endif
57
58 using namespace Inspector;
59
60 namespace WebCore {
61
62 #if PLATFORM(COCOA)
63 static const CFIndex frameStopRunLoopOrder = (CFIndex)RunLoopObserver::WellKnownRunLoopOrders::CoreAnimationCommit + 1;
64
65 static CFRunLoopRef currentRunLoop()
66 {
67 #if PLATFORM(IOS)
68     // A race condition during WebView deallocation can lead to a crash if the layer sync run loop
69     // observer is added to the main run loop <rdar://problem/9798550>. However, for responsiveness,
70     // we still allow this, see <rdar://problem/7403328>. Since the race condition and subsequent
71     // crash are especially troublesome for iBooks, we never allow the observer to be added to the
72     // main run loop in iBooks.
73     if (applicationIsIBooksOnIOS())
74         return WebThreadRunLoop();
75 #endif
76     return CFRunLoopGetCurrent();
77 }
78 #endif
79
80 InspectorTimelineAgent::~InspectorTimelineAgent()
81 {
82 }
83
84 void InspectorTimelineAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
85 {
86     m_instrumentingAgents.setPersistentInspectorTimelineAgent(this);
87
88     // Recompile to include profiling information.
89     // FIXME: This doesn't seem like the most appropriate place.
90     m_environment.scriptDebugServer().recompileAllJSFunctions();
91 }
92
93 void InspectorTimelineAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason reason)
94 {
95     m_instrumentingAgents.setPersistentInspectorTimelineAgent(nullptr);
96
97     if (reason != Inspector::DisconnectReason::InspectedTargetDestroyed)
98         m_environment.scriptDebugServer().recompileAllJSFunctions();
99
100     ErrorString unused;
101     stop(unused);
102 }
103
104 void InspectorTimelineAgent::start(ErrorString&, const int* maxCallStackDepth)
105 {
106     m_enabledFromFrontend = true;
107
108     internalStart(maxCallStackDepth);
109 }
110
111 void InspectorTimelineAgent::stop(ErrorString&)
112 {
113     internalStop();
114
115     m_enabledFromFrontend = false;
116 }
117
118 void InspectorTimelineAgent::internalStart(const int* maxCallStackDepth)
119 {
120     if (m_enabled)
121         return;
122
123     if (maxCallStackDepth && *maxCallStackDepth > 0)
124         m_maxCallStackDepth = *maxCallStackDepth;
125     else
126         m_maxCallStackDepth = 5;
127
128     m_instrumentingAgents.setInspectorTimelineAgent(this);
129
130     m_environment.scriptDebugServer().addListener(this);
131
132     m_enabled = true;
133
134     // FIXME: Abstract away platform-specific code once https://bugs.webkit.org/show_bug.cgi?id=142748 is fixed.
135
136 #if PLATFORM(COCOA)
137     m_frameStartObserver = RunLoopObserver::create(0, [this]() {
138         if (!m_enabled || m_environment.scriptDebugServer().isPaused())
139             return;
140
141         if (!m_runLoopNestingLevel)
142             pushCurrentRecord(InspectorObject::create(), TimelineRecordType::RenderingFrame, false, nullptr);
143         m_runLoopNestingLevel++;
144     });
145
146     m_frameStopObserver = RunLoopObserver::create(frameStopRunLoopOrder, [this]() {
147         if (!m_enabled || m_environment.scriptDebugServer().isPaused())
148             return;
149
150         ASSERT(m_runLoopNestingLevel > 0);
151         m_runLoopNestingLevel--;
152         if (m_runLoopNestingLevel)
153             return;
154
155         if (m_startedComposite)
156             didComposite();
157
158         didCompleteCurrentRecord(TimelineRecordType::RenderingFrame);
159     });
160
161     m_frameStartObserver->schedule(currentRunLoop(), kCFRunLoopEntry | kCFRunLoopAfterWaiting);
162     m_frameStopObserver->schedule(currentRunLoop(), kCFRunLoopExit | kCFRunLoopBeforeWaiting);
163
164     // Create a runloop record and increment the runloop nesting level, to capture the current turn of the main runloop
165     // (which is the outer runloop if recording started while paused in the debugger).
166     pushCurrentRecord(InspectorObject::create(), TimelineRecordType::RenderingFrame, false, nullptr);
167
168     m_runLoopNestingLevel = 1;
169 #endif
170
171     m_frontendDispatcher->recordingStarted(timestamp());
172 }
173
174 void InspectorTimelineAgent::internalStop()
175 {
176     if (!m_enabled)
177         return;
178
179     m_instrumentingAgents.setInspectorTimelineAgent(nullptr);
180
181     m_environment.scriptDebugServer().removeListener(this, true);
182
183 #if PLATFORM(COCOA)
184     m_frameStartObserver = nullptr;
185     m_frameStopObserver = nullptr;
186     m_runLoopNestingLevel = 0;
187
188     // Complete all pending records to prevent discarding events that are currently in progress.
189     while (!m_recordStack.isEmpty())
190         didCompleteCurrentRecord(m_recordStack.last().type);
191 #endif
192
193     clearRecordStack();
194
195     m_enabled = false;
196     m_startedComposite = false;
197
198     m_frontendDispatcher->recordingStopped(timestamp());
199 }
200
201 double InspectorTimelineAgent::timestamp()
202 {
203     return m_environment.executionStopwatch()->elapsedTime();
204 }
205
206 static inline void startProfiling(JSC::ExecState* exec, const String& title, RefPtr<Stopwatch>&& stopwatch)
207 {
208     JSC::LegacyProfiler::profiler()->startProfiling(exec, title, WTFMove(stopwatch));
209 }
210
211 static inline RefPtr<JSC::Profile> stopProfiling(JSC::ExecState* exec, const String& title)
212 {
213     return JSC::LegacyProfiler::profiler()->stopProfiling(exec, title);
214 }
215
216 static inline void startProfiling(Frame* frame, const String& title, RefPtr<Stopwatch>&& stopwatch)
217 {
218     startProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title, WTFMove(stopwatch));
219 }
220
221 static inline PassRefPtr<JSC::Profile> stopProfiling(Frame* frame, const String& title)
222 {
223     return stopProfiling(toJSDOMWindow(frame, debuggerWorld())->globalExec(), title);
224 }
225
226 void InspectorTimelineAgent::startFromConsole(JSC::ExecState* exec, const String &title)
227 {
228     // Only allow recording of a profile if it is anonymous (empty title) or does not match
229     // the title of an already recording profile.
230     if (!title.isEmpty()) {
231         for (const TimelineRecordEntry& record : m_pendingConsoleProfileRecords) {
232             String recordTitle;
233             record.data->getString(ASCIILiteral("title"), recordTitle);
234             if (recordTitle == title)
235                 return;
236         }
237     }
238
239     if (!m_enabled && m_pendingConsoleProfileRecords.isEmpty())
240         internalStart();
241
242     startProfiling(exec, title, m_environment.executionStopwatch());
243
244     m_pendingConsoleProfileRecords.append(createRecordEntry(TimelineRecordFactory::createConsoleProfileData(title), TimelineRecordType::ConsoleProfile, true, frameFromExecState(exec)));
245 }
246
247 RefPtr<JSC::Profile> InspectorTimelineAgent::stopFromConsole(JSC::ExecState* exec, const String& title)
248 {
249     // Stop profiles in reverse order. If the title is empty, then stop the last profile.
250     // Otherwise, match the title of the profile to stop.
251     for (ptrdiff_t i = m_pendingConsoleProfileRecords.size() - 1; i >= 0; --i) {
252         const TimelineRecordEntry& record = m_pendingConsoleProfileRecords[i];
253
254         String recordTitle;
255         record.data->getString(ASCIILiteral("title"), recordTitle);
256
257         if (title.isEmpty() || recordTitle == title) {
258             RefPtr<JSC::Profile> profile = stopProfiling(exec, title);
259             if (profile)
260                 TimelineRecordFactory::appendProfile(record.data.get(), profile.copyRef());
261
262             didCompleteRecordEntry(record);
263
264             m_pendingConsoleProfileRecords.remove(i);
265
266             if (!m_enabledFromFrontend && m_pendingConsoleProfileRecords.isEmpty())
267                 internalStop();
268
269             return profile;
270         }
271     }
272
273     return nullptr;
274 }
275
276 void InspectorTimelineAgent::willCallFunction(const String& scriptName, int scriptLine, Frame* frame)
277 {
278     pushCurrentRecord(TimelineRecordFactory::createFunctionCallData(scriptName, scriptLine), TimelineRecordType::FunctionCall, true, frame);
279
280     if (frame && !m_callStackDepth)
281         startProfiling(frame, ASCIILiteral("Timeline FunctionCall"), m_environment.executionStopwatch());
282
283     ++m_callStackDepth;
284 }
285
286 void InspectorTimelineAgent::didCallFunction(Frame* frame)
287 {
288     if (frame && m_callStackDepth) {
289         --m_callStackDepth;
290         ASSERT(m_callStackDepth >= 0);
291
292         if (!m_callStackDepth) {
293             if (m_recordStack.isEmpty())
294                 return;
295
296             TimelineRecordEntry& entry = m_recordStack.last();
297             ASSERT(entry.type == TimelineRecordType::FunctionCall);
298
299             RefPtr<JSC::Profile> profile = stopProfiling(frame, ASCIILiteral("Timeline FunctionCall"));
300             if (profile)
301                 TimelineRecordFactory::appendProfile(entry.data.get(), profile.release());
302         }
303     }
304
305     didCompleteCurrentRecord(TimelineRecordType::FunctionCall);
306 }
307
308 void InspectorTimelineAgent::willDispatchEvent(const Event& event, Frame* frame)
309 {
310     pushCurrentRecord(TimelineRecordFactory::createEventDispatchData(event), TimelineRecordType::EventDispatch, false, frame);
311 }
312
313 void InspectorTimelineAgent::didDispatchEvent()
314 {
315     didCompleteCurrentRecord(TimelineRecordType::EventDispatch);
316 }
317
318 void InspectorTimelineAgent::didInvalidateLayout(Frame& frame)
319 {
320     appendRecord(InspectorObject::create(), TimelineRecordType::InvalidateLayout, true, &frame);
321 }
322
323 void InspectorTimelineAgent::willLayout(Frame& frame)
324 {
325     pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Layout, true, &frame);
326 }
327
328 void InspectorTimelineAgent::didLayout(RenderObject* root)
329 {
330     if (m_recordStack.isEmpty())
331         return;
332     TimelineRecordEntry& entry = m_recordStack.last();
333     ASSERT(entry.type == TimelineRecordType::Layout);
334     Vector<FloatQuad> quads;
335     root->absoluteQuads(quads);
336     if (quads.size() >= 1)
337         TimelineRecordFactory::appendLayoutRoot(entry.data.get(), quads[0]);
338     else
339         ASSERT_NOT_REACHED();
340     didCompleteCurrentRecord(TimelineRecordType::Layout);
341 }
342
343 void InspectorTimelineAgent::didScheduleStyleRecalculation(Frame* frame)
344 {
345     appendRecord(InspectorObject::create(), TimelineRecordType::ScheduleStyleRecalculation, true, frame);
346 }
347
348 void InspectorTimelineAgent::willRecalculateStyle(Frame* frame)
349 {
350     pushCurrentRecord(InspectorObject::create(), TimelineRecordType::RecalculateStyles, true, frame);
351 }
352
353 void InspectorTimelineAgent::didRecalculateStyle()
354 {
355     didCompleteCurrentRecord(TimelineRecordType::RecalculateStyles);
356 }
357
358 void InspectorTimelineAgent::willComposite(Frame& frame)
359 {
360     ASSERT(!m_startedComposite);
361     pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Composite, true, &frame);
362     m_startedComposite = true;
363 }
364
365 void InspectorTimelineAgent::didComposite()
366 {
367     ASSERT(m_startedComposite);
368     didCompleteCurrentRecord(TimelineRecordType::Composite);
369     m_startedComposite = false;
370 }
371
372 void InspectorTimelineAgent::willPaint(Frame& frame)
373 {
374     pushCurrentRecord(InspectorObject::create(), TimelineRecordType::Paint, true, &frame);
375 }
376
377 void InspectorTimelineAgent::didPaint(RenderObject* renderer, const LayoutRect& clipRect)
378 {
379     TimelineRecordEntry& entry = m_recordStack.last();
380     ASSERT(entry.type == TimelineRecordType::Paint);
381     FloatQuad quad;
382     localToPageQuad(*renderer, clipRect, &quad);
383     entry.data = TimelineRecordFactory::createPaintData(quad);
384     didCompleteCurrentRecord(TimelineRecordType::Paint);
385 }
386
387 void InspectorTimelineAgent::didInstallTimer(int timerId, int timeout, bool singleShot, Frame* frame)
388 {
389     appendRecord(TimelineRecordFactory::createTimerInstallData(timerId, timeout, singleShot), TimelineRecordType::TimerInstall, true, frame);
390 }
391
392 void InspectorTimelineAgent::didRemoveTimer(int timerId, Frame* frame)
393 {
394     appendRecord(TimelineRecordFactory::createGenericTimerData(timerId), TimelineRecordType::TimerRemove, true, frame);
395 }
396
397 void InspectorTimelineAgent::willFireTimer(int timerId, Frame* frame)
398 {
399     pushCurrentRecord(TimelineRecordFactory::createGenericTimerData(timerId), TimelineRecordType::TimerFire, false, frame);
400 }
401
402 void InspectorTimelineAgent::didFireTimer()
403 {
404     didCompleteCurrentRecord(TimelineRecordType::TimerFire);
405 }
406
407 void InspectorTimelineAgent::willEvaluateScript(const String& url, int lineNumber, Frame& frame)
408 {
409     pushCurrentRecord(TimelineRecordFactory::createEvaluateScriptData(url, lineNumber), TimelineRecordType::EvaluateScript, true, &frame);
410
411     if (!m_callStackDepth) {
412         ++m_callStackDepth;
413         startProfiling(&frame, ASCIILiteral("Timeline EvaluateScript"), m_environment.executionStopwatch());
414     }
415 }
416
417 void InspectorTimelineAgent::didEvaluateScript(Frame& frame)
418 {
419     if (m_callStackDepth) {
420         --m_callStackDepth;
421         ASSERT(m_callStackDepth >= 0);
422
423         if (!m_callStackDepth) {
424             if (m_recordStack.isEmpty())
425                 return;
426
427             TimelineRecordEntry& entry = m_recordStack.last();
428             ASSERT(entry.type == TimelineRecordType::EvaluateScript);
429
430             RefPtr<JSC::Profile> profile = stopProfiling(&frame, ASCIILiteral("Timeline EvaluateScript"));
431             if (profile)
432                 TimelineRecordFactory::appendProfile(entry.data.get(), profile.release());
433         }
434     }
435
436     didCompleteCurrentRecord(TimelineRecordType::EvaluateScript);
437 }
438
439 void InspectorTimelineAgent::didTimeStamp(Frame& frame, const String& message)
440 {
441     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeStamp, true, &frame);
442 }
443
444 void InspectorTimelineAgent::time(Frame& frame, const String& message)
445 {
446     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::Time, true, &frame);
447 }
448
449 void InspectorTimelineAgent::timeEnd(Frame& frame, const String& message)
450 {
451     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeEnd, true, &frame);
452 }
453
454 void InspectorTimelineAgent::didCommitLoad()
455 {
456     clearRecordStack();
457 }
458
459 void InspectorTimelineAgent::didRequestAnimationFrame(int callbackId, Frame* frame)
460 {
461     appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::RequestAnimationFrame, true, frame);
462 }
463
464 void InspectorTimelineAgent::didCancelAnimationFrame(int callbackId, Frame* frame)
465 {
466     appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::CancelAnimationFrame, true, frame);
467 }
468
469 void InspectorTimelineAgent::willFireAnimationFrame(int callbackId, Frame* frame)
470 {
471     pushCurrentRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::FireAnimationFrame, false, frame);
472 }
473
474 void InspectorTimelineAgent::didFireAnimationFrame()
475 {
476     didCompleteCurrentRecord(TimelineRecordType::FireAnimationFrame);
477 }
478
479 // ScriptDebugListener
480
481 void InspectorTimelineAgent::breakpointActionProbe(JSC::ExecState* exec, const Inspector::ScriptBreakpointAction& action, unsigned batchId, unsigned sampleId, const Deprecated::ScriptValue&)
482 {
483     UNUSED_PARAM(batchId);
484     ASSERT(exec);
485
486     appendRecord(TimelineRecordFactory::createProbeSampleData(action, sampleId), TimelineRecordType::ProbeSample, false, frameFromExecState(exec));
487 }
488
489 static Inspector::Protocol::Timeline::EventType toProtocol(TimelineRecordType type)
490 {
491     switch (type) {
492     case TimelineRecordType::EventDispatch:
493         return Inspector::Protocol::Timeline::EventType::EventDispatch;
494     case TimelineRecordType::ScheduleStyleRecalculation:
495         return Inspector::Protocol::Timeline::EventType::ScheduleStyleRecalculation;
496     case TimelineRecordType::RecalculateStyles:
497         return Inspector::Protocol::Timeline::EventType::RecalculateStyles;
498     case TimelineRecordType::InvalidateLayout:
499         return Inspector::Protocol::Timeline::EventType::InvalidateLayout;
500     case TimelineRecordType::Layout:
501         return Inspector::Protocol::Timeline::EventType::Layout;
502     case TimelineRecordType::Paint:
503         return Inspector::Protocol::Timeline::EventType::Paint;
504     case TimelineRecordType::Composite:
505         return Inspector::Protocol::Timeline::EventType::Composite;
506     case TimelineRecordType::RenderingFrame:
507         return Inspector::Protocol::Timeline::EventType::RenderingFrame;
508
509     case TimelineRecordType::TimerInstall:
510         return Inspector::Protocol::Timeline::EventType::TimerInstall;
511     case TimelineRecordType::TimerRemove:
512         return Inspector::Protocol::Timeline::EventType::TimerRemove;
513     case TimelineRecordType::TimerFire:
514         return Inspector::Protocol::Timeline::EventType::TimerFire;
515
516     case TimelineRecordType::EvaluateScript:
517         return Inspector::Protocol::Timeline::EventType::EvaluateScript;
518
519     case TimelineRecordType::TimeStamp:
520         return Inspector::Protocol::Timeline::EventType::TimeStamp;
521     case TimelineRecordType::Time:
522         return Inspector::Protocol::Timeline::EventType::Time;
523     case TimelineRecordType::TimeEnd:
524         return Inspector::Protocol::Timeline::EventType::TimeEnd;
525
526     case TimelineRecordType::FunctionCall:
527         return Inspector::Protocol::Timeline::EventType::FunctionCall;
528     case TimelineRecordType::ProbeSample:
529         return Inspector::Protocol::Timeline::EventType::ProbeSample;
530     case TimelineRecordType::ConsoleProfile:
531         return Inspector::Protocol::Timeline::EventType::ConsoleProfile;
532
533     case TimelineRecordType::RequestAnimationFrame:
534         return Inspector::Protocol::Timeline::EventType::RequestAnimationFrame;
535     case TimelineRecordType::CancelAnimationFrame:
536         return Inspector::Protocol::Timeline::EventType::CancelAnimationFrame;
537     case TimelineRecordType::FireAnimationFrame:
538         return Inspector::Protocol::Timeline::EventType::FireAnimationFrame;
539     }
540
541     return Inspector::Protocol::Timeline::EventType::TimeStamp;
542 }
543
544 void InspectorTimelineAgent::addRecordToTimeline(RefPtr<InspectorObject>&& record, TimelineRecordType type)
545 {
546     ASSERT_ARG(record, record);
547     record->setString("type", Inspector::Protocol::getEnumConstantValue(toProtocol(type)));
548
549     if (m_recordStack.isEmpty()) {
550         auto recordObject = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(record));
551         sendEvent(WTFMove(recordObject));
552     } else {
553         const TimelineRecordEntry& parent = m_recordStack.last();
554         // Nested paint records are an implementation detail and add no information not already contained in the parent.
555         if (type == TimelineRecordType::Paint && parent.type == type)
556             return;
557
558         parent.children->pushObject(WTFMove(record));
559     }
560 }
561
562 void InspectorTimelineAgent::setFrameIdentifier(InspectorObject* record, Frame* frame)
563 {
564     if (!frame || !m_pageAgent)
565         return;
566     String frameId;
567     if (frame && m_pageAgent)
568         frameId = m_pageAgent->frameId(frame);
569     record->setString("frameId", frameId);
570 }
571
572 void InspectorTimelineAgent::didCompleteRecordEntry(const TimelineRecordEntry& entry)
573 {
574     entry.record->setObject(ASCIILiteral("data"), entry.data);
575     entry.record->setArray(ASCIILiteral("children"), entry.children);
576     entry.record->setDouble(ASCIILiteral("endTime"), timestamp());
577     addRecordToTimeline(entry.record.copyRef(), entry.type);
578 }
579
580 void InspectorTimelineAgent::didCompleteCurrentRecord(TimelineRecordType type)
581 {
582     // An empty stack could merely mean that the timeline agent was turned on in the middle of
583     // an event.  Don't treat as an error.
584     if (!m_recordStack.isEmpty()) {
585         TimelineRecordEntry entry = m_recordStack.last();
586         m_recordStack.removeLast();
587         ASSERT_UNUSED(type, entry.type == type);
588
589         // Don't send RenderingFrame records that have no children to reduce noise.
590         if (entry.type == TimelineRecordType::RenderingFrame && !entry.children->length())
591             return;
592
593         didCompleteRecordEntry(entry);
594     }
595 }
596
597 InspectorTimelineAgent::InspectorTimelineAgent(WebAgentContext& context, InspectorPageAgent* pageAgent)
598     : InspectorAgentBase(ASCIILiteral("Timeline"), context)
599     , m_frontendDispatcher(std::make_unique<Inspector::TimelineFrontendDispatcher>(context.frontendRouter))
600     , m_backendDispatcher(Inspector::TimelineBackendDispatcher::create(context.backendDispatcher, this))
601     , m_pageAgent(pageAgent)
602 {
603 }
604
605 void InspectorTimelineAgent::appendRecord(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
606 {
607     Ref<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0);
608     record->setObject("data", WTFMove(data));
609     setFrameIdentifier(&record.get(), frame);
610     addRecordToTimeline(WTFMove(record), type);
611 }
612
613 void InspectorTimelineAgent::sendEvent(RefPtr<InspectorObject>&& event)
614 {
615     // FIXME: runtimeCast is a hack. We do it because we can't build TimelineEvent directly now.
616     auto recordChecked = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(event));
617     m_frontendDispatcher->eventRecorded(WTFMove(recordChecked));
618 }
619
620 InspectorTimelineAgent::TimelineRecordEntry InspectorTimelineAgent::createRecordEntry(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
621 {
622     Ref<InspectorObject> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0);
623     setFrameIdentifier(&record.get(), frame);
624     return TimelineRecordEntry(WTFMove(record), WTFMove(data), InspectorArray::create(), type);
625 }
626
627 void InspectorTimelineAgent::pushCurrentRecord(RefPtr<InspectorObject>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
628 {
629     pushCurrentRecord(createRecordEntry(WTFMove(data), type, captureCallStack, frame));
630 }
631
632 void InspectorTimelineAgent::clearRecordStack()
633 {
634     m_recordStack.clear();
635     m_id++;
636 }
637
638 void InspectorTimelineAgent::localToPageQuad(const RenderObject& renderer, const LayoutRect& rect, FloatQuad* quad)
639 {
640     const FrameView& frameView = renderer.view().frameView();
641     FloatQuad absolute = renderer.localToAbsoluteQuad(FloatQuad(rect));
642     quad->setP1(frameView.contentsToRootView(roundedIntPoint(absolute.p1())));
643     quad->setP2(frameView.contentsToRootView(roundedIntPoint(absolute.p2())));
644     quad->setP3(frameView.contentsToRootView(roundedIntPoint(absolute.p3())));
645     quad->setP4(frameView.contentsToRootView(roundedIntPoint(absolute.p4())));
646 }
647
648 } // namespace WebCore