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
66namespace WebCore {
67
68using namespace Inspector;
69
70#if PLATFORM(COCOA)
71static 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
86InspectorTimelineAgent::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
93InspectorTimelineAgent::~InspectorTimelineAgent() = default;
94
95void InspectorTimelineAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
96{
97 m_instrumentingAgents.setPersistentInspectorTimelineAgent(this);
98}
99
100void 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
111void InspectorTimelineAgent::start(ErrorString&, const int* maxCallStackDepth)
112{
113 m_enabledFromFrontend = true;
114
115 internalStart(maxCallStackDepth);
116}
117
118void InspectorTimelineAgent::stop(ErrorString&)
119{
120 internalStop();
121
122 m_enabledFromFrontend = false;
123}
124
125void InspectorTimelineAgent::setAutoCaptureEnabled(ErrorString&, bool enabled)
126{
127 m_autoCaptureEnabled = enabled;
128}
129
130void 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
154void 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
210void 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
238double InspectorTimelineAgent::timestamp()
239{
240 return m_environment.executionStopwatch()->elapsedTime().seconds();
241}
242
243void 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
267void 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
294void 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
299void InspectorTimelineAgent::didCallFunction(Frame*)
300{
301 didCompleteCurrentRecord(TimelineRecordType::FunctionCall);
302}
303
304void InspectorTimelineAgent::willDispatchEvent(const Event& event, Frame* frame)
305{
306 pushCurrentRecord(TimelineRecordFactory::createEventDispatchData(event), TimelineRecordType::EventDispatch, false, frame);
307}
308
309void 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
318void InspectorTimelineAgent::didInvalidateLayout(Frame& frame)
319{
320 appendRecord(JSON::Object::create(), TimelineRecordType::InvalidateLayout, true, &frame);
321}
322
323void InspectorTimelineAgent::willLayout(Frame& frame)
324{
325 pushCurrentRecord(JSON::Object::create(), TimelineRecordType::Layout, true, &frame);
326}
327
328void 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
343void InspectorTimelineAgent::didScheduleStyleRecalculation(Frame* frame)
344{
345 appendRecord(JSON::Object::create(), TimelineRecordType::ScheduleStyleRecalculation, true, frame);
346}
347
348void InspectorTimelineAgent::willRecalculateStyle(Frame* frame)
349{
350 pushCurrentRecord(JSON::Object::create(), TimelineRecordType::RecalculateStyles, true, frame);
351}
352
353void InspectorTimelineAgent::didRecalculateStyle()
354{
355 didCompleteCurrentRecord(TimelineRecordType::RecalculateStyles);
356}
357
358void InspectorTimelineAgent::willComposite(Frame& frame)
359{
360 ASSERT(!m_startedComposite);
361 pushCurrentRecord(JSON::Object::create(), TimelineRecordType::Composite, true, &frame);
362 m_startedComposite = true;
363}
364
365void InspectorTimelineAgent::didComposite()
366{
367 ASSERT(m_startedComposite);
368 didCompleteCurrentRecord(TimelineRecordType::Composite);
369 m_startedComposite = false;
370}
371
372void InspectorTimelineAgent::willPaint(Frame& frame)
373{
374 pushCurrentRecord(JSON::Object::create(), TimelineRecordType::Paint, true, &frame);
375}
376
377void 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
387void InspectorTimelineAgent::didInstallTimer(int timerId, Seconds timeout, bool singleShot, Frame* frame)
388{
389 appendRecord(TimelineRecordFactory::createTimerInstallData(timerId, timeout, singleShot), TimelineRecordType::TimerInstall, true, frame);
390}
391
392void InspectorTimelineAgent::didRemoveTimer(int timerId, Frame* frame)
393{
394 appendRecord(TimelineRecordFactory::createGenericTimerData(timerId), TimelineRecordType::TimerRemove, true, frame);
395}
396
397void InspectorTimelineAgent::willFireTimer(int timerId, Frame* frame)
398{
399 pushCurrentRecord(TimelineRecordFactory::createGenericTimerData(timerId), TimelineRecordType::TimerFire, false, frame);
400}
401
402void InspectorTimelineAgent::didFireTimer()
403{
404 didCompleteCurrentRecord(TimelineRecordType::TimerFire);
405}
406
407void 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
412void InspectorTimelineAgent::didEvaluateScript(Frame&)
413{
414 didCompleteCurrentRecord(TimelineRecordType::EvaluateScript);
415}
416
417void InspectorTimelineAgent::didTimeStamp(Frame& frame, const String& message)
418{
419 appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeStamp, true, &frame);
420}
421
422void InspectorTimelineAgent::time(Frame& frame, const String& message)
423{
424 appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::Time, true, &frame);
425}
426
427void InspectorTimelineAgent::timeEnd(Frame& frame, const String& message)
428{
429 appendRecord(TimelineRecordFactory::createTimeStampData(message), TimelineRecordType::TimeEnd, true, &frame);
430}
431
432void 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
457void 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
466void 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
485void 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
503void 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
530void 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
542void 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
554void 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
569void 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
584void InspectorTimelineAgent::toggleTimelineInstrument(InstrumentState state)
585{
586 if (state == InstrumentState::Start)
587 internalStart();
588 else
589 internalStop();
590}
591
592void InspectorTimelineAgent::didCommitLoad()
593{
594 clearRecordStack();
595}
596
597void InspectorTimelineAgent::didRequestAnimationFrame(int callbackId, Frame* frame)
598{
599 appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::RequestAnimationFrame, true, frame);
600}
601
602void InspectorTimelineAgent::didCancelAnimationFrame(int callbackId, Frame* frame)
603{
604 appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::CancelAnimationFrame, true, frame);
605}
606
607void InspectorTimelineAgent::willFireAnimationFrame(int callbackId, Frame* frame)
608{
609 pushCurrentRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::FireAnimationFrame, false, frame);
610}
611
612void InspectorTimelineAgent::didFireAnimationFrame()
613{
614 didCompleteCurrentRecord(TimelineRecordType::FireAnimationFrame);
615}
616
617void InspectorTimelineAgent::willFireObserverCallback(const String& callbackType, Frame* frame)
618{
619 pushCurrentRecord(TimelineRecordFactory::createObserverCallbackData(callbackType), TimelineRecordType::ObserverCallback, false, frame);
620}
621
622void InspectorTimelineAgent::didFireObserverCallback()
623{
624 didCompleteCurrentRecord(TimelineRecordType::ObserverCallback);
625}
626
627// ScriptDebugListener
628
629void 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
634static 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
692void 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
710void 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
722void 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
730void 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
747void 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
755void 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
762InspectorTimelineAgent::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
769void InspectorTimelineAgent::pushCurrentRecord(RefPtr<JSON::Object>&& data, TimelineRecordType type, bool captureCallStack, Frame* frame)
770{
771 pushCurrentRecord(createRecordEntry(WTFMove(data), type, captureCallStack, frame));
772}
773
774void InspectorTimelineAgent::clearRecordStack()
775{
776 m_recordStack.clear();
777 m_id++;
778}
779
780void 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
791