1/*
2 * Copyright (C) 2012 Intel Inc. All rights reserved.
3 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "PerformanceUserTiming.h"
29
30#include "Document.h"
31#include "PerformanceTiming.h"
32#include <wtf/NeverDestroyed.h>
33
34namespace WebCore {
35
36using NavigationTimingFunction = unsigned long long (PerformanceTiming::*)() const;
37
38static NavigationTimingFunction restrictedMarkFunction(const String& markName)
39{
40 ASSERT(isMainThread());
41
42 static const auto map = makeNeverDestroyed([] {
43 static const std::pair<ASCIILiteral, NavigationTimingFunction> pairs[] = {
44 { "connectEnd"_s, &PerformanceTiming::connectEnd },
45 { "connectStart"_s, &PerformanceTiming::connectStart },
46 { "domComplete"_s, &PerformanceTiming::domComplete },
47 { "domContentLoadedEventEnd"_s, &PerformanceTiming::domContentLoadedEventEnd },
48 { "domContentLoadedEventStart"_s, &PerformanceTiming::domContentLoadedEventStart },
49 { "domInteractive"_s, &PerformanceTiming::domInteractive },
50 { "domLoading"_s, &PerformanceTiming::domLoading },
51 { "domainLookupEnd"_s, &PerformanceTiming::domainLookupEnd },
52 { "domainLookupStart"_s, &PerformanceTiming::domainLookupStart },
53 { "fetchStart"_s, &PerformanceTiming::fetchStart },
54 { "loadEventEnd"_s, &PerformanceTiming::loadEventEnd },
55 { "loadEventStart"_s, &PerformanceTiming::loadEventStart },
56 { "navigationStart"_s, &PerformanceTiming::navigationStart },
57 { "redirectEnd"_s, &PerformanceTiming::redirectEnd },
58 { "redirectStart"_s, &PerformanceTiming::redirectStart },
59 { "requestStart"_s, &PerformanceTiming::requestStart },
60 { "responseEnd"_s, &PerformanceTiming::responseEnd },
61 { "responseStart"_s, &PerformanceTiming::responseStart },
62 { "secureConnectionStart"_s, &PerformanceTiming::secureConnectionStart },
63 { "unloadEventEnd"_s, &PerformanceTiming::unloadEventEnd },
64 { "unloadEventStart"_s, &PerformanceTiming::unloadEventStart },
65 };
66 HashMap<String, NavigationTimingFunction> map;
67 for (auto& pair : pairs)
68 map.add(pair.first, pair.second);
69 return map;
70 }());
71
72 return map.get().get(markName);
73}
74
75UserTiming::UserTiming(Performance& performance)
76 : m_performance(performance)
77{
78}
79
80static void clearPerformanceEntries(PerformanceEntryMap& map, const String& name)
81{
82 if (name.isNull())
83 map.clear();
84 else
85 map.remove(name);
86}
87
88ExceptionOr<Ref<PerformanceMark>> UserTiming::mark(const String& markName)
89{
90 if (is<Document>(m_performance.scriptExecutionContext()) && restrictedMarkFunction(markName))
91 return Exception { SyntaxError };
92
93 auto& performanceEntryList = m_marksMap.ensure(markName, [] { return Vector<RefPtr<PerformanceEntry>>(); }).iterator->value;
94 auto entry = PerformanceMark::create(markName, m_performance.now());
95 performanceEntryList.append(entry.copyRef());
96 return entry;
97}
98
99void UserTiming::clearMarks(const String& markName)
100{
101 clearPerformanceEntries(m_marksMap, markName);
102}
103
104ExceptionOr<double> UserTiming::findExistingMarkStartTime(const String& markName)
105{
106 auto iterator = m_marksMap.find(markName);
107 if (iterator != m_marksMap.end())
108 return iterator->value.last()->startTime();
109
110 auto* timing = m_performance.timing();
111 if (!timing)
112 return Exception { SyntaxError, makeString("No mark named '", markName, "' exists") };
113
114 if (auto function = restrictedMarkFunction(markName)) {
115 double value = ((*timing).*(function))();
116 if (!value)
117 return Exception { InvalidAccessError };
118 return value - timing->navigationStart();
119 }
120
121 return Exception { SyntaxError };
122}
123
124ExceptionOr<Ref<PerformanceMeasure>> UserTiming::measure(const String& measureName, const String& startMark, const String& endMark)
125{
126 double startTime = 0.0;
127 double endTime = 0.0;
128
129 if (startMark.isNull())
130 endTime = m_performance.now();
131 else if (endMark.isNull()) {
132 endTime = m_performance.now();
133 auto startMarkResult = findExistingMarkStartTime(startMark);
134 if (startMarkResult.hasException())
135 return startMarkResult.releaseException();
136 startTime = startMarkResult.releaseReturnValue();
137 } else {
138 auto endMarkResult = findExistingMarkStartTime(endMark);
139 if (endMarkResult.hasException())
140 return endMarkResult.releaseException();
141 auto startMarkResult = findExistingMarkStartTime(startMark);
142 if (startMarkResult.hasException())
143 return startMarkResult.releaseException();
144 startTime = startMarkResult.releaseReturnValue();
145 endTime = endMarkResult.releaseReturnValue();
146 }
147
148 auto& performanceEntryList = m_measuresMap.ensure(measureName, [] { return Vector<RefPtr<PerformanceEntry>>(); }).iterator->value;
149 auto entry = PerformanceMeasure::create(measureName, startTime, endTime);
150 performanceEntryList.append(entry.copyRef());
151 return entry;
152}
153
154void UserTiming::clearMeasures(const String& measureName)
155{
156 clearPerformanceEntries(m_measuresMap, measureName);
157}
158
159static Vector<RefPtr<PerformanceEntry>> convertToEntrySequence(const PerformanceEntryMap& map)
160{
161 Vector<RefPtr<PerformanceEntry>> entries;
162 for (auto& entry : map.values())
163 entries.appendVector(entry);
164 return entries;
165}
166
167Vector<RefPtr<PerformanceEntry>> UserTiming::getMarks() const
168{
169 return convertToEntrySequence(m_marksMap);
170}
171
172Vector<RefPtr<PerformanceEntry>> UserTiming::getMarks(const String& name) const
173{
174 return m_marksMap.get(name);
175}
176
177Vector<RefPtr<PerformanceEntry>> UserTiming::getMeasures() const
178{
179 return convertToEntrySequence(m_measuresMap);
180}
181
182Vector<RefPtr<PerformanceEntry>> UserTiming::getMeasures(const String& name) const
183{
184 return m_measuresMap.get(name);
185}
186
187} // namespace WebCore
188