2 * Copyright (C) 2012 Intel Inc. All rights reserved.
3 * Copyright (C) 2017 Apple Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "PerformanceUserTiming.h"
31 #include "MessagePort.h"
32 #include "PerformanceMarkOptions.h"
33 #include "PerformanceMeasureOptions.h"
34 #include "PerformanceTiming.h"
35 #include "SerializedScriptValue.h"
36 #include <JavaScriptCore/JSCJSValueInlines.h>
37 #include <wtf/NeverDestroyed.h>
41 using NavigationTimingFunction = unsigned long long (PerformanceTiming::*)() const;
42 static const HashMap<String, NavigationTimingFunction>& restrictedMarkNamesToNavigationTimingFunctionMap()
44 ASSERT(isMainThread());
46 static auto map = makeNeverDestroyed<HashMap<String, NavigationTimingFunction>>({
47 { "connectEnd"_s, &PerformanceTiming::connectEnd },
48 { "connectStart"_s, &PerformanceTiming::connectStart },
49 { "domComplete"_s, &PerformanceTiming::domComplete },
50 { "domContentLoadedEventEnd"_s, &PerformanceTiming::domContentLoadedEventEnd },
51 { "domContentLoadedEventStart"_s, &PerformanceTiming::domContentLoadedEventStart },
52 { "domInteractive"_s, &PerformanceTiming::domInteractive },
53 { "domLoading"_s, &PerformanceTiming::domLoading },
54 { "domainLookupEnd"_s, &PerformanceTiming::domainLookupEnd },
55 { "domainLookupStart"_s, &PerformanceTiming::domainLookupStart },
56 { "fetchStart"_s, &PerformanceTiming::fetchStart },
57 { "loadEventEnd"_s, &PerformanceTiming::loadEventEnd },
58 { "loadEventStart"_s, &PerformanceTiming::loadEventStart },
59 { "navigationStart"_s, &PerformanceTiming::navigationStart },
60 { "redirectEnd"_s, &PerformanceTiming::redirectEnd },
61 { "redirectStart"_s, &PerformanceTiming::redirectStart },
62 { "requestStart"_s, &PerformanceTiming::requestStart },
63 { "responseEnd"_s, &PerformanceTiming::responseEnd },
64 { "responseStart"_s, &PerformanceTiming::responseStart },
65 { "secureConnectionStart"_s, &PerformanceTiming::secureConnectionStart },
66 { "unloadEventEnd"_s, &PerformanceTiming::unloadEventEnd },
67 { "unloadEventStart"_s, &PerformanceTiming::unloadEventStart },
73 static NavigationTimingFunction restrictedMarkFunction(const String& markName)
75 ASSERT(isMainThread());
76 return restrictedMarkNamesToNavigationTimingFunctionMap().get(markName);
79 static bool isRestrictedMarkNameNonMainThread(const String& markName)
81 ASSERT(!isMainThread());
84 callOnMainThreadAndWait([&isRestricted, markName = markName.isolatedCopy()] {
85 isRestricted = restrictedMarkNamesToNavigationTimingFunctionMap().contains(markName);
90 bool PerformanceUserTiming::isRestrictedMarkName(const String& markName)
92 ASSERT(isMainThread());
93 return restrictedMarkNamesToNavigationTimingFunctionMap().contains(markName);
96 PerformanceUserTiming::PerformanceUserTiming(Performance& performance)
97 : m_performance(performance)
101 static void clearPerformanceEntries(PerformanceEntryMap& map, const String& name)
109 static void addPerformanceEntry(PerformanceEntryMap& map, const String& name, PerformanceEntry& entry)
111 auto& performanceEntryList = map.ensure(name, [] { return Vector<RefPtr<PerformanceEntry>>(); }).iterator->value;
112 performanceEntryList.append(&entry);
115 ExceptionOr<Ref<PerformanceMark>> PerformanceUserTiming::mark(JSC::JSGlobalObject& globalObject, const String& markName, Optional<PerformanceMarkOptions>&& markOptions)
117 auto mark = PerformanceMark::create(globalObject, *m_performance.scriptExecutionContext(), markName, WTFMove(markOptions));
118 if (mark.hasException())
119 return mark.releaseException();
121 addPerformanceEntry(m_marksMap, markName, mark.returnValue().get());
122 return mark.releaseReturnValue();
125 void PerformanceUserTiming::clearMarks(const String& markName)
127 clearPerformanceEntries(m_marksMap, markName);
130 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(const Variant<String, double>& mark) const
132 return WTF::switchOn(mark, [&](auto& value) {
133 return convertMarkToTimestamp(value);
137 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(const String& mark) const
139 if (!is<Document>(m_performance.scriptExecutionContext())) {
140 if (isRestrictedMarkNameNonMainThread(mark))
141 return Exception { TypeError };
143 if (auto function = restrictedMarkFunction(mark)) {
144 if (function == &PerformanceTiming::navigationStart)
147 // PerformanceTiming should always be non-null for the Document ScriptExecutionContext.
148 ASSERT(m_performance.timing());
149 auto timing = m_performance.timing();
150 auto startTime = timing->navigationStart();
151 auto endTime = ((*timing).*(function))();
153 return Exception { InvalidAccessError };
154 return endTime - startTime;
158 auto iterator = m_marksMap.find(mark);
159 if (iterator != m_marksMap.end())
160 return iterator->value.last()->startTime();
162 return Exception { SyntaxError, makeString("No mark named '", mark, "' exists") };
165 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(double mark) const
168 return Exception { TypeError };
172 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(const String& measureName, const String& startMark, const String& endMark)
175 if (!endMark.isNull()) {
176 auto end = convertMarkToTimestamp(endMark);
177 if (end.hasException())
178 return end.releaseException();
179 endTime = end.returnValue();
181 endTime = m_performance.now();
184 if (!startMark.isNull()) {
185 auto start = convertMarkToTimestamp(startMark);
186 if (start.hasException())
187 return start.releaseException();
188 startTime = start.returnValue();
192 auto measure = PerformanceMeasure::create(measureName, startTime, endTime, SerializedScriptValue::nullValue());
193 if (measure.hasException())
194 return measure.releaseException();
196 addPerformanceEntry(m_measuresMap, measureName, measure.returnValue().get());
197 return measure.releaseReturnValue();
200 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(JSC::JSGlobalObject& globalObject, const String& measureName, const PerformanceMeasureOptions& measureOptions)
203 if (measureOptions.end) {
204 auto end = convertMarkToTimestamp(*measureOptions.end);
205 if (end.hasException())
206 return end.releaseException();
207 endTime = end.returnValue();
208 } else if (measureOptions.start && measureOptions.duration) {
209 auto start = convertMarkToTimestamp(*measureOptions.start);
210 if (start.hasException())
211 return start.releaseException();
212 auto duration = convertMarkToTimestamp(*measureOptions.duration);
213 if (duration.hasException())
214 return start.releaseException();
215 endTime = start.returnValue() + duration.returnValue();
217 endTime = m_performance.now();
220 if (measureOptions.start) {
221 auto start = convertMarkToTimestamp(*measureOptions.start);
222 if (start.hasException())
223 return start.releaseException();
224 startTime = start.returnValue();
225 } else if (measureOptions.duration && measureOptions.end) {
226 auto duration = convertMarkToTimestamp(*measureOptions.duration);
227 if (duration.hasException())
228 return duration.releaseException();
229 auto end = convertMarkToTimestamp(*measureOptions.end);
230 if (end.hasException())
231 return end.releaseException();
232 startTime = end.returnValue() - duration.returnValue();
237 JSC::JSValue detail = measureOptions.detail;
238 if (detail.isUndefined())
239 detail = JSC::jsNull();
241 Vector<RefPtr<MessagePort>> ignoredMessagePorts;
242 auto serializedDetail = SerializedScriptValue::create(globalObject, detail, { }, ignoredMessagePorts);
243 if (serializedDetail.hasException())
244 return serializedDetail.releaseException();
246 auto measure = PerformanceMeasure::create(measureName, startTime, endTime, serializedDetail.releaseReturnValue());
247 if (measure.hasException())
248 return measure.releaseException();
250 addPerformanceEntry(m_measuresMap, measureName, measure.returnValue().get());
251 return measure.releaseReturnValue();
254 static bool isNonEmptyDictionary(const PerformanceMeasureOptions& measureOptions)
256 return !measureOptions.detail.isUndefined() || measureOptions.start || measureOptions.duration || measureOptions.end;
259 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(JSC::JSGlobalObject& globalObject, const String& measureName, Optional<StartOrMeasureOptions>&& startOrMeasureOptions, const String& endMark)
261 if (startOrMeasureOptions) {
262 return WTF::switchOn(*startOrMeasureOptions,
263 [&] (const PerformanceMeasureOptions& measureOptions) -> ExceptionOr<Ref<PerformanceMeasure>> {
264 if (isNonEmptyDictionary(measureOptions)) {
265 if (!endMark.isNull())
266 return Exception { TypeError };
267 if (!measureOptions.start && !measureOptions.end)
268 return Exception { TypeError };
269 if (measureOptions.start && measureOptions.duration && measureOptions.end)
270 return Exception { TypeError };
273 return measure(globalObject, measureName, measureOptions);
275 [&] (const String& startMark) {
276 return measure(measureName, startMark, endMark);
281 return measure(measureName, { }, endMark);
284 void PerformanceUserTiming::clearMeasures(const String& measureName)
286 clearPerformanceEntries(m_measuresMap, measureName);
289 static Vector<RefPtr<PerformanceEntry>> convertToEntrySequence(const PerformanceEntryMap& map)
291 Vector<RefPtr<PerformanceEntry>> entries;
292 for (auto& entry : map.values())
293 entries.appendVector(entry);
297 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMarks() const
299 return convertToEntrySequence(m_marksMap);
302 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMarks(const String& name) const
304 return m_marksMap.get(name);
307 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMeasures() const
309 return convertToEntrySequence(m_measuresMap);
312 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMeasures(const String& name) const
314 return m_measuresMap.get(name);
317 } // namespace WebCore