Web Inspector: Timelines: can't reliably stop/start a recording
[WebKit-https.git] / Source / WebCore / inspector / agents / 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 "DOMWindow.h"
37 #include "Event.h"
38 #include "Frame.h"
39 #include "InspectorCPUProfilerAgent.h"
40 #include "InspectorMemoryAgent.h"
41 #include "InspectorPageAgent.h"
42 #include "InstrumentingAgents.h"
43 #include "JSDOMWindow.h"
44 #include "PageHeapAgent.h"
45 #include "PageScriptDebugServer.h"
46 #include "RenderView.h"
47 #include "ScriptState.h"
48 #include "TimelineRecordFactory.h"
49 #include "WebConsoleAgent.h"
50 #include <JavaScriptCore/ConsoleMessage.h>
51 #include <JavaScriptCore/InspectorDebuggerAgent.h>
52 #include <JavaScriptCore/InspectorScriptProfilerAgent.h>
53 #include <JavaScriptCore/ScriptBreakpoint.h>
54 #include <wtf/Stopwatch.h>
55
56 #if PLATFORM(IOS_FAMILY)
57 #include "RuntimeApplicationChecks.h"
58 #include "WebCoreThreadInternal.h"
59 #endif
60
61 #if PLATFORM(COCOA)
62 #include "RunLoopObserver.h"
63 #endif
64
65
66 namespace WebCore {
67
68 using namespace Inspector;
69
70 #if PLATFORM(COCOA)
71 static CFRunLoopRef currentRunLoop()
72 {
73 #if PLATFORM(IOS_FAMILY)
74     // A race condition during WebView deallocation can lead to a crash if the layer sync run loop
75     // observer is added to the main run loop <rdar://problem/9798550>. However, for responsiveness,
76     // we still allow this, see <rdar://problem/7403328>. Since the race condition and subsequent
77     // crash are especially troublesome for iBooks, we never allow the observer to be added to the
78     // main run loop in iBooks.
79     if (IOSApplication::isIBooks())
80         return WebThreadRunLoop();
81 #endif
82     return CFRunLoopGetCurrent();
83 }
84 #endif
85
86 InspectorTimelineAgent::InspectorTimelineAgent(WebAgentContext& context)
87     : InspectorAgentBase("Timeline"_s, context)
88     , m_frontendDispatcher(std::make_unique<Inspector::TimelineFrontendDispatcher>(context.frontendRouter))
89     , m_backendDispatcher(Inspector::TimelineBackendDispatcher::create(context.backendDispatcher, this))
90 {
91 }
92
93 InspectorTimelineAgent::~InspectorTimelineAgent() = default;
94
95 void InspectorTimelineAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
96 {
97     m_instrumentingAgents.setPersistentInspectorTimelineAgent(this);
98 }
99
100 void InspectorTimelineAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
101 {
102     m_instrumentingAgents.setPersistentInspectorTimelineAgent(nullptr);
103
104     ErrorString unused;
105     stop(unused);
106
107     m_autoCaptureEnabled = false;
108     m_instruments.clear();
109 }
110
111 void InspectorTimelineAgent::start(ErrorString&, const int* maxCallStackDepth)
112 {
113     m_enabledFromFrontend = true;
114
115     internalStart(maxCallStackDepth);
116 }
117
118 void InspectorTimelineAgent::stop(ErrorString&)
119 {
120     internalStop();
121
122     m_enabledFromFrontend = false;
123 }
124
125 void InspectorTimelineAgent::setAutoCaptureEnabled(ErrorString&, bool enabled)
126 {
127     m_autoCaptureEnabled = enabled;
128 }
129
130 void InspectorTimelineAgent::setInstruments(ErrorString& errorString, const JSON::Array& instruments)
131 {
132     Vector<Protocol::Timeline::Instrument> newInstruments;
133     newInstruments.reserveCapacity(instruments.length());
134
135     for (const auto& instrumentValue : instruments) {
136         String enumValueString;
137         if (!instrumentValue->asString(enumValueString)) {
138             errorString = "Unexpected type in instruments list, should be string"_s;
139             return;
140         }
141
142         Optional<Protocol::Timeline::Instrument> instrumentType = Protocol::InspectorHelpers::parseEnumValueFromString<Protocol::Timeline::Instrument>(enumValueString);
143         if (!instrumentType) {
144             errorString = makeString("Unexpected enum value: ", enumValueString);
145             return;
146         }
147
148         newInstruments.uncheckedAppend(*instrumentType);
149     }
150
151     m_instruments.swap(newInstruments);
152 }
153
154 void InspectorTimelineAgent::internalStart(const int* maxCallStackDepth)
155 {
156     if (m_enabled)
157         return;
158
159     if (maxCallStackDepth && *maxCallStackDepth > 0)
160         m_maxCallStackDepth = *maxCallStackDepth;
161     else
162         m_maxCallStackDepth = 5;
163
164     m_instrumentingAgents.setInspectorTimelineAgent(this);
165
166     m_environment.scriptDebugServer().addListener(this);
167
168     m_enabled = true;
169
170     // FIXME: Abstract away platform-specific code once https://bugs.webkit.org/show_bug.cgi?id=142748 is fixed.
171
172 #if PLATFORM(COCOA)
173     m_frameStartObserver = std::make_unique<RunLoopObserver>(static_cast<CFIndex>(RunLoopObserver::WellKnownRunLoopOrders::InspectorFrameBegin), [this]() {
174         if (!m_enabled || m_environment.scriptDebugServer().isPaused())
175             return;
176
177         if (!m_runLoopNestingLevel)
178             pushCurrentRecord(JSON::Object::create(), TimelineRecordType::RenderingFrame, false, nullptr);
179         m_runLoopNestingLevel++;
180     });
181
182     m_frameStopObserver = std::make_unique<RunLoopObserver>(static_cast<CFIndex>(RunLoopObserver::WellKnownRunLoopOrders::InspectorFrameEnd), [this]() {
183         if (!m_enabled || m_environment.scriptDebugServer().isPaused())
184             return;
185
186         ASSERT(m_runLoopNestingLevel > 0);
187         m_runLoopNestingLevel--;
188         if (m_runLoopNestingLevel)
189             return;
190
191         if (m_startedComposite)
192             didComposite();
193
194         didCompleteCurrentRecord(TimelineRecordType::RenderingFrame);
195     });
196
197     m_frameStartObserver->schedule(currentRunLoop(), kCFRunLoopEntry | kCFRunLoopAfterWaiting);
198     m_frameStopObserver->schedule(currentRunLoop(), kCFRunLoopExit | kCFRunLoopBeforeWaiting);
199
200     // Create a runloop record and increment the runloop nesting level, to capture the current turn of the main runloop
201     // (which is the outer runloop if recording started while paused in the debugger).
202     pushCurrentRecord(JSON::Object::create(), TimelineRecordType::RenderingFrame, false, nullptr);
203
204     m_runLoopNestingLevel = 1;
205 #endif
206
207     m_frontendDispatcher->recordingStarted(timestamp());
208 }
209
210 void InspectorTimelineAgent::internalStop()
211 {
212     if (!m_enabled)
213         return;
214
215     m_instrumentingAgents.setInspectorTimelineAgent(nullptr);
216
217     m_environment.scriptDebugServer().removeListener(this, true);
218
219 #if PLATFORM(COCOA)
220     m_frameStartObserver = nullptr;
221     m_frameStopObserver = nullptr;
222     m_runLoopNestingLevel = 0;
223
224     // Complete all pending records to prevent discarding events that are currently in progress.
225     while (!m_recordStack.isEmpty())
226         didCompleteCurrentRecord(m_recordStack.last().type);
227 #endif
228
229     clearRecordStack();
230
231     m_enabled = false;
232     m_startedComposite = false;
233     m_autoCapturePhase = AutoCapturePhase::None;
234
235     m_frontendDispatcher->recordingStopped(timestamp());
236 }
237
238 double InspectorTimelineAgent::timestamp()
239 {
240     return m_environment.executionStopwatch()->elapsedTime().seconds();
241 }
242
243 void InspectorTimelineAgent::startFromConsole(JSC::ExecState* exec, const String& title)
244 {
245     // Allow duplicate unnamed profiles. Disallow duplicate named profiles.
246     if (!title.isEmpty()) {
247         for (const TimelineRecordEntry& record : m_pendingConsoleProfileRecords) {
248             String recordTitle;
249             record.data->getString("title"_s, recordTitle);
250             if (recordTitle == title) {
251                 if (WebConsoleAgent* consoleAgent = m_instrumentingAgents.webConsoleAgent()) {
252                     // FIXME: Send an enum to the frontend for localization?
253                     String warning = title.isEmpty() ? "Unnamed Profile already exists"_s : makeString("Profile \"", title, "\" already exists");
254                     consoleAgent->addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Profile, MessageLevel::Warning, warning));
255                 }
256                 return;
257             }
258         }
259     }
260
261     if (!m_enabled && m_pendingConsoleProfileRecords.isEmpty())
262         startProgrammaticCapture();
263
264     m_pendingConsoleProfileRecords.append(createRecordEntry(TimelineRecordFactory::createConsoleProfileData(title), TimelineRecordType::ConsoleProfile, true, frameFromExecState(exec)));
265 }
266
267 void InspectorTimelineAgent::stopFromConsole(JSC::ExecState*, const String& title)
268 {
269     // Stop profiles in reverse order. If the title is empty, then stop the last profile.
270     // Otherwise, match the title of the profile to stop.
271     for (int i = m_pendingConsoleProfileRecords.size() - 1; i >= 0; --i) {
272         const TimelineRecordEntry& record = m_pendingConsoleProfileRecords[i];
273
274         String recordTitle;
275         record.data->getString("title"_s, recordTitle);
276         if (title.isEmpty() || recordTitle == title) {
277             didCompleteRecordEntry(record);
278             m_pendingConsoleProfileRecords.remove(i);
279
280             if (!m_enabledFromFrontend && m_pendingConsoleProfileRecords.isEmpty())
281                 stopProgrammaticCapture();
282
283             return;
284         }
285     }
286
287     if (WebConsoleAgent* consoleAgent = m_instrumentingAgents.webConsoleAgent()) {
288         // FIXME: Send an enum to the frontend for localization?
289         String warning = title.isEmpty() ? "No profiles exist"_s : makeString("Profile \"", title, "\" does not exist");
290         consoleAgent->addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::ProfileEnd, MessageLevel::Warning, warning));
291     }
292 }
293
294 void InspectorTimelineAgent::willCallFunction(const String& scriptName, int scriptLine, int scriptColumn, Frame* frame)
295 {
296     pushCurrentRecord(TimelineRecordFactory::createFunctionCallData(scriptName, scriptLine, scriptColumn), TimelineRecordType::FunctionCall, true, frame);
297 }
298
299 void InspectorTimelineAgent::didCallFunction(Frame*)
300 {
301     didCompleteCurrentRecord(TimelineRecordType::FunctionCall);
302 }
303
304 void InspectorTimelineAgent::willDispatchEvent(const Event& event, Frame* frame)
305 {
306     pushCurrentRecord(TimelineRecordFactory::createEventDispatchData(event), TimelineRecordType::EventDispatch, false, frame);
307 }
308
309 void InspectorTimelineAgent::didDispatchEvent(bool defaultPrevented)
310 {
311     auto& entry = m_recordStack.last();
312     ASSERT(entry.type == TimelineRecordType::EventDispatch);
313     entry.data->setBoolean("defaultPrevented"_s, defaultPrevented);
314
315     didCompleteCurrentRecord(TimelineRecordType::EventDispatch);
316 }
317
318 void InspectorTimelineAgent::didInvalidateLayout(Frame& frame)
319 {
320     appendRecord(JSON::Object::create(), TimelineRecordType::InvalidateLayout, true, &frame);
321 }
322
323 void InspectorTimelineAgent::willLayout(Frame& frame)
324 {
325     pushCurrentRecord(JSON::Object::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(JSON::Object::create(), TimelineRecordType::ScheduleStyleRecalculation, true, frame);
346 }
347
348 void InspectorTimelineAgent::willRecalculateStyle(Frame* frame)
349 {
350     pushCurrentRecord(JSON::Object::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(JSON::Object::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(JSON::Object::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, Seconds 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, int columnNumber, Frame& frame)
408 {
409     pushCurrentRecord(TimelineRecordFactory::createEvaluateScriptData(url, lineNumber, columnNumber), TimelineRecordType::EvaluateScript, true, &frame);
410 }
411
412 void InspectorTimelineAgent::didEvaluateScript(Frame&)
413 {
414     didCompleteCurrentRecord(TimelineRecordType::EvaluateScript);
415 }
416
417 void InspectorTimelineAgent::didTimeStamp(Frame& frame, const String& message)
418 {
419     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeStamp, true, &frame);
420 }
421
422 void InspectorTimelineAgent::time(Frame& frame, const String& message)
423 {
424     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::Time, true, &frame);
425 }
426
427 void InspectorTimelineAgent::timeEnd(Frame& frame, const String& message)
428 {
429     appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeEnd, true, &frame);
430 }
431
432 void InspectorTimelineAgent::mainFrameStartedLoading()
433 {
434     if (m_enabled)
435         return;
436
437     if (!m_autoCaptureEnabled)
438         return;
439
440     if (m_instruments.isEmpty())
441         return;
442
443     m_autoCapturePhase = AutoCapturePhase::BeforeLoad;
444
445     // Pre-emptively disable breakpoints. The frontend must re-enable them.
446     if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) {
447         ErrorString unused;
448         debuggerAgent->setBreakpointsActive(unused, false);
449     }
450
451     // Inform the frontend we started an auto capture. The frontend must stop capture.
452     m_frontendDispatcher->autoCaptureStarted();
453
454     toggleInstruments(InstrumentState::Start);
455 }
456
457 void InspectorTimelineAgent::mainFrameNavigated()
458 {
459     if (m_autoCapturePhase == AutoCapturePhase::BeforeLoad) {
460         m_autoCapturePhase = AutoCapturePhase::FirstNavigation;
461         toggleInstruments(InstrumentState::Start);
462         m_autoCapturePhase = AutoCapturePhase::AfterFirstNavigation;
463     }
464 }
465
466 void InspectorTimelineAgent::startProgrammaticCapture()
467 {
468     ASSERT(!m_enabled);
469
470     // Disable breakpoints during programmatic capture.
471     if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) {
472         m_programmaticCaptureRestoreBreakpointActiveValue = debuggerAgent->breakpointsActive();
473         if (m_programmaticCaptureRestoreBreakpointActiveValue) {
474             ErrorString unused;
475             debuggerAgent->setBreakpointsActive(unused, false);
476         }
477     } else
478         m_programmaticCaptureRestoreBreakpointActiveValue = false;
479
480     toggleScriptProfilerInstrument(InstrumentState::Start); // Ensure JavaScript samping data.
481     toggleTimelineInstrument(InstrumentState::Start); // Ensure Console Profile event records.
482     toggleInstruments(InstrumentState::Start); // Any other instruments the frontend wants us to record.
483 }
484
485 void InspectorTimelineAgent::stopProgrammaticCapture()
486 {
487     ASSERT(m_enabled);
488     ASSERT(!m_enabledFromFrontend);
489
490     toggleInstruments(InstrumentState::Stop);
491     toggleTimelineInstrument(InstrumentState::Stop);
492     toggleScriptProfilerInstrument(InstrumentState::Stop);
493
494     // Re-enable breakpoints if they were enabled.
495     if (m_programmaticCaptureRestoreBreakpointActiveValue) {
496         if (InspectorDebuggerAgent* debuggerAgent = m_instrumentingAgents.inspectorDebuggerAgent()) {
497             ErrorString unused;
498             debuggerAgent->setBreakpointsActive(unused, true);
499         }
500     }
501 }
502
503 void InspectorTimelineAgent::toggleInstruments(InstrumentState state)
504 {
505     for (auto instrumentType : m_instruments) {
506         switch (instrumentType) {
507         case Inspector::Protocol::Timeline::Instrument::ScriptProfiler: {
508             toggleScriptProfilerInstrument(state);
509             break;
510         }
511         case Inspector::Protocol::Timeline::Instrument::Heap: {
512             toggleHeapInstrument(state);
513             break;
514         }
515         case Inspector::Protocol::Timeline::Instrument::CPU: {
516             toggleCPUInstrument(state);
517             break;
518         }
519         case Inspector::Protocol::Timeline::Instrument::Memory: {
520             toggleMemoryInstrument(state);
521             break;
522         }
523         case Inspector::Protocol::Timeline::Instrument::Timeline:
524             toggleTimelineInstrument(state);
525             break;
526         }
527     }
528 }
529
530 void InspectorTimelineAgent::toggleScriptProfilerInstrument(InstrumentState state)
531 {
532     if (auto* scriptProfilerAgent = m_instrumentingAgents.inspectorScriptProfilerAgent()) {
533         ErrorString unused;
534         if (state == InstrumentState::Start) {
535             const bool includeSamples = true;
536             scriptProfilerAgent->startTracking(unused, &includeSamples);
537         } else
538             scriptProfilerAgent->stopTracking(unused);
539     }
540 }
541
542 void InspectorTimelineAgent::toggleHeapInstrument(InstrumentState state)
543 {
544     if (auto* heapAgent = m_instrumentingAgents.pageHeapAgent()) {
545         ErrorString unused;
546         if (state == InstrumentState::Start) {
547             if (m_autoCapturePhase == AutoCapturePhase::None || m_autoCapturePhase == AutoCapturePhase::FirstNavigation)
548                 heapAgent->startTracking(unused);
549         } else
550             heapAgent->stopTracking(unused);
551     }
552 }
553
554 void InspectorTimelineAgent::toggleCPUInstrument(InstrumentState state)
555 {
556 #if ENABLE(RESOURCE_USAGE)
557     if (InspectorCPUProfilerAgent* cpuProfilerAgent = m_instrumentingAgents.inspectorCPUProfilerAgent()) {
558         ErrorString unused;
559         if (state == InstrumentState::Start)
560             cpuProfilerAgent->startTracking(unused);
561         else
562             cpuProfilerAgent->stopTracking(unused);
563     }
564 #else
565     UNUSED_PARAM(state);
566 #endif
567 }
568
569 void InspectorTimelineAgent::toggleMemoryInstrument(InstrumentState state)
570 {
571 #if ENABLE(RESOURCE_USAGE)
572     if (InspectorMemoryAgent* memoryAgent = m_instrumentingAgents.inspectorMemoryAgent()) {
573         ErrorString unused;
574         if (state == InstrumentState::Start)
575             memoryAgent->startTracking(unused);
576         else
577             memoryAgent->stopTracking(unused);
578     }
579 #else
580     UNUSED_PARAM(state);
581 #endif
582 }
583
584 void InspectorTimelineAgent::toggleTimelineInstrument(InstrumentState state)
585 {
586     if (state == InstrumentState::Start)
587         internalStart();
588     else
589         internalStop();
590 }
591
592 void InspectorTimelineAgent::didCommitLoad()
593 {
594     clearRecordStack();
595 }
596
597 void InspectorTimelineAgent::didRequestAnimationFrame(int callbackId, Frame* frame)
598 {
599     appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::RequestAnimationFrame, true, frame);
600 }
601
602 void InspectorTimelineAgent::didCancelAnimationFrame(int callbackId, Frame* frame)
603 {
604     appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::CancelAnimationFrame, true, frame);
605 }
606
607 void InspectorTimelineAgent::willFireAnimationFrame(int callbackId, Frame* frame)
608 {
609     pushCurrentRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::FireAnimationFrame, false, frame);
610 }
611
612 void InspectorTimelineAgent::didFireAnimationFrame()
613 {
614     didCompleteCurrentRecord(TimelineRecordType::FireAnimationFrame);
615 }
616
617 void InspectorTimelineAgent::willFireObserverCallback(const String& callbackType, Frame* frame)
618 {
619     pushCurrentRecord(TimelineRecordFactory::createObserverCallbackData(callbackType), TimelineRecordType::ObserverCallback, false, frame);
620 }
621
622 void InspectorTimelineAgent::didFireObserverCallback()
623 {
624     didCompleteCurrentRecord(TimelineRecordType::ObserverCallback);
625 }
626
627 // ScriptDebugListener
628
629 void InspectorTimelineAgent::breakpointActionProbe(JSC::ExecState& state, const Inspector::ScriptBreakpointAction& action, unsigned /*batchId*/, unsigned sampleId, JSC::JSValue)
630 {
631     appendRecord(TimelineRecordFactory::createProbeSampleData(action, sampleId), TimelineRecordType::ProbeSample, false, frameFromExecState(&state));
632 }
633
634 static Inspector::Protocol::Timeline::EventType toProtocol(TimelineRecordType type)
635 {
636     switch (type) {
637     case TimelineRecordType::EventDispatch:
638         return Inspector::Protocol::Timeline::EventType::EventDispatch;
639     case TimelineRecordType::ScheduleStyleRecalculation:
640         return Inspector::Protocol::Timeline::EventType::ScheduleStyleRecalculation;
641     case TimelineRecordType::RecalculateStyles:
642         return Inspector::Protocol::Timeline::EventType::RecalculateStyles;
643     case TimelineRecordType::InvalidateLayout:
644         return Inspector::Protocol::Timeline::EventType::InvalidateLayout;
645     case TimelineRecordType::Layout:
646         return Inspector::Protocol::Timeline::EventType::Layout;
647     case TimelineRecordType::Paint:
648         return Inspector::Protocol::Timeline::EventType::Paint;
649     case TimelineRecordType::Composite:
650         return Inspector::Protocol::Timeline::EventType::Composite;
651     case TimelineRecordType::RenderingFrame:
652         return Inspector::Protocol::Timeline::EventType::RenderingFrame;
653
654     case TimelineRecordType::TimerInstall:
655         return Inspector::Protocol::Timeline::EventType::TimerInstall;
656     case TimelineRecordType::TimerRemove:
657         return Inspector::Protocol::Timeline::EventType::TimerRemove;
658     case TimelineRecordType::TimerFire:
659         return Inspector::Protocol::Timeline::EventType::TimerFire;
660
661     case TimelineRecordType::EvaluateScript:
662         return Inspector::Protocol::Timeline::EventType::EvaluateScript;
663
664     case TimelineRecordType::TimeStamp:
665         return Inspector::Protocol::Timeline::EventType::TimeStamp;
666     case TimelineRecordType::Time:
667         return Inspector::Protocol::Timeline::EventType::Time;
668     case TimelineRecordType::TimeEnd:
669         return Inspector::Protocol::Timeline::EventType::TimeEnd;
670
671     case TimelineRecordType::FunctionCall:
672         return Inspector::Protocol::Timeline::EventType::FunctionCall;
673     case TimelineRecordType::ProbeSample:
674         return Inspector::Protocol::Timeline::EventType::ProbeSample;
675     case TimelineRecordType::ConsoleProfile:
676         return Inspector::Protocol::Timeline::EventType::ConsoleProfile;
677
678     case TimelineRecordType::RequestAnimationFrame:
679         return Inspector::Protocol::Timeline::EventType::RequestAnimationFrame;
680     case TimelineRecordType::CancelAnimationFrame:
681         return Inspector::Protocol::Timeline::EventType::CancelAnimationFrame;
682     case TimelineRecordType::FireAnimationFrame:
683         return Inspector::Protocol::Timeline::EventType::FireAnimationFrame;
684
685     case TimelineRecordType::ObserverCallback:
686         return Inspector::Protocol::Timeline::EventType::ObserverCallback;
687     }
688
689     return Inspector::Protocol::Timeline::EventType::TimeStamp;
690 }
691
692 void InspectorTimelineAgent::addRecordToTimeline(RefPtr<JSON::Object>&& record, TimelineRecordType type)
693 {
694     ASSERT_ARG(record, record);
695     record->setString("type", Inspector::Protocol::InspectorHelpers::getEnumConstantValue(toProtocol(type)));
696
697     if (m_recordStack.isEmpty()) {
698         auto recordObject = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(record));
699         sendEvent(WTFMove(recordObject));
700     } else {
701         const TimelineRecordEntry& parent = m_recordStack.last();
702         // Nested paint records are an implementation detail and add no information not already contained in the parent.
703         if (type == TimelineRecordType::Paint && parent.type == type)
704             return;
705
706         parent.children->pushObject(WTFMove(record));
707     }
708 }
709
710 void InspectorTimelineAgent::setFrameIdentifier(JSON::Object* record, Frame* frame)
711 {
712     if (!frame)
713         return;
714
715     auto* pageAgent = m_instrumentingAgents.inspectorPageAgent();
716     if (!pageAgent)
717         return;
718
719     record->setString("frameId"_s, pageAgent->frameId(frame));
720 }
721
722 void InspectorTimelineAgent::didCompleteRecordEntry(const TimelineRecordEntry& entry)
723 {
724     entry.record->setObject("data"_s, entry.data);
725     entry.record->setArray("children"_s, entry.children);
726     entry.record->setDouble("endTime"_s, timestamp());
727     addRecordToTimeline(entry.record.copyRef(), entry.type);
728 }
729
730 void InspectorTimelineAgent::didCompleteCurrentRecord(TimelineRecordType type)
731 {
732     // An empty stack could merely mean that the timeline agent was turned on in the middle of
733     // an event.  Don't treat as an error.
734     if (!m_recordStack.isEmpty()) {
735         TimelineRecordEntry entry = m_recordStack.last();
736         m_recordStack.removeLast();
737         ASSERT_UNUSED(type, entry.type == type);
738
739         // Don't send RenderingFrame records that have no children to reduce noise.
740         if (entry.type == TimelineRecordType::RenderingFrame && !entry.children->length())
741             return;
742
743         didCompleteRecordEntry(entry);
744     }
745 }
746
747 void InspectorTimelineAgent::appendRecord(RefPtr<JSON::Object>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
748 {
749     Ref<JSON::Object> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0);
750     record->setObject("data", WTFMove(data));
751     setFrameIdentifier(&record.get(), frame);
752     addRecordToTimeline(WTFMove(record), type);
753 }
754
755 void InspectorTimelineAgent::sendEvent(RefPtr<JSON::Object>&& event)
756 {
757     // FIXME: runtimeCast is a hack. We do it because we can't build TimelineEvent directly now.
758     auto recordChecked = BindingTraits<Inspector::Protocol::Timeline::TimelineEvent>::runtimeCast(WTFMove(event));
759     m_frontendDispatcher->eventRecorded(WTFMove(recordChecked));
760 }
761
762 InspectorTimelineAgent::TimelineRecordEntry InspectorTimelineAgent::createRecordEntry(RefPtr<JSON::Object>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
763 {
764     Ref<JSON::Object> record = TimelineRecordFactory::createGenericRecord(timestamp(), captureCallStack ? m_maxCallStackDepth : 0);
765     setFrameIdentifier(&record.get(), frame);
766     return TimelineRecordEntry(WTFMove(record), WTFMove(data), JSON::Array::create(), type);
767 }
768
769 void InspectorTimelineAgent::pushCurrentRecord(RefPtr<JSON::Object>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
770 {
771     pushCurrentRecord(createRecordEntry(WTFMove(data), type, captureCallStack, frame));
772 }
773
774 void InspectorTimelineAgent::clearRecordStack()
775 {
776     m_recordStack.clear();
777     m_id++;
778 }
779
780 void InspectorTimelineAgent::localToPageQuad(const RenderObject& renderer, const LayoutRect& rect, FloatQuad* quad)
781 {
782     const FrameView& frameView = renderer.view().frameView();
783     FloatQuad absolute = renderer.localToAbsoluteQuad(FloatQuad(rect));
784     quad->setP1(frameView.contentsToRootView(roundedIntPoint(absolute.p1())));
785     quad->setP2(frameView.contentsToRootView(roundedIntPoint(absolute.p2())));
786     quad->setP3(frameView.contentsToRootView(roundedIntPoint(absolute.p3())));
787     quad->setP4(frameView.contentsToRootView(roundedIntPoint(absolute.p4())));
788 }
789
790 } // namespace WebCore