de35fee6b305c79daac6ecc790379707a3a9718d
[WebKit-https.git] / Source / WebCore / page / PerformanceUserTiming.cpp
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 "MessagePort.h"
32 #include "PerformanceMarkOptions.h"
33 #include "PerformanceMeasureOptions.h"
34 #include "PerformanceTiming.h"
35 #include "SerializedScriptValue.h"
36 #include <wtf/NeverDestroyed.h>
37
38 namespace WebCore {
39
40 using NavigationTimingFunction = unsigned long long (PerformanceTiming::*)() const;
41 static const HashMap<String, NavigationTimingFunction>& restrictedMarkNamesToNavigationTimingFunctionMap()
42 {
43     ASSERT(isMainThread());
44
45     static auto map = makeNeverDestroyed<HashMap<String, NavigationTimingFunction>>({
46         { "connectEnd"_s, &PerformanceTiming::connectEnd },
47         { "connectStart"_s, &PerformanceTiming::connectStart },
48         { "domComplete"_s, &PerformanceTiming::domComplete },
49         { "domContentLoadedEventEnd"_s, &PerformanceTiming::domContentLoadedEventEnd },
50         { "domContentLoadedEventStart"_s, &PerformanceTiming::domContentLoadedEventStart },
51         { "domInteractive"_s, &PerformanceTiming::domInteractive },
52         { "domLoading"_s, &PerformanceTiming::domLoading },
53         { "domainLookupEnd"_s, &PerformanceTiming::domainLookupEnd },
54         { "domainLookupStart"_s, &PerformanceTiming::domainLookupStart },
55         { "fetchStart"_s, &PerformanceTiming::fetchStart },
56         { "loadEventEnd"_s, &PerformanceTiming::loadEventEnd },
57         { "loadEventStart"_s, &PerformanceTiming::loadEventStart },
58         { "navigationStart"_s, &PerformanceTiming::navigationStart },
59         { "redirectEnd"_s, &PerformanceTiming::redirectEnd },
60         { "redirectStart"_s, &PerformanceTiming::redirectStart },
61         { "requestStart"_s, &PerformanceTiming::requestStart },
62         { "responseEnd"_s, &PerformanceTiming::responseEnd },
63         { "responseStart"_s, &PerformanceTiming::responseStart },
64         { "secureConnectionStart"_s, &PerformanceTiming::secureConnectionStart },
65         { "unloadEventEnd"_s, &PerformanceTiming::unloadEventEnd },
66         { "unloadEventStart"_s, &PerformanceTiming::unloadEventStart },
67     });
68     
69     return map;
70 }
71
72 static NavigationTimingFunction restrictedMarkFunction(const String& markName)
73 {
74     ASSERT(isMainThread());
75     return restrictedMarkNamesToNavigationTimingFunctionMap().get(markName);
76 }
77
78 static bool isRestrictedMarkNameNonMainThread(const String& markName)
79 {
80     ASSERT(!isMainThread());
81
82     bool isRestricted;
83     callOnMainThreadAndWait([&isRestricted, markName = markName.isolatedCopy()] {
84         isRestricted = restrictedMarkNamesToNavigationTimingFunctionMap().contains(markName);
85     });
86     return isRestricted;
87 }
88
89 bool PerformanceUserTiming::isRestrictedMarkName(const String& markName)
90 {
91     ASSERT(isMainThread());
92     return restrictedMarkNamesToNavigationTimingFunctionMap().contains(markName);
93 }
94
95 PerformanceUserTiming::PerformanceUserTiming(Performance& performance)
96     : m_performance(performance)
97 {
98 }
99
100 static void clearPerformanceEntries(PerformanceEntryMap& map, const String& name)
101 {
102     if (name.isNull())
103         map.clear();
104     else
105         map.remove(name);
106 }
107
108 static void addPerformanceEntry(PerformanceEntryMap& map, const String& name, PerformanceEntry& entry)
109 {
110     auto& performanceEntryList = map.ensure(name, [] { return Vector<RefPtr<PerformanceEntry>>(); }).iterator->value;
111     performanceEntryList.append(&entry);
112 }
113
114 ExceptionOr<Ref<PerformanceMark>> PerformanceUserTiming::mark(JSC::JSGlobalObject& globalObject, const String& markName, Optional<PerformanceMarkOptions>&& markOptions)
115 {
116     auto mark = PerformanceMark::create(globalObject, *m_performance.scriptExecutionContext(), markName, WTFMove(markOptions));
117     if (mark.hasException())
118         return mark.releaseException();
119
120     addPerformanceEntry(m_marksMap, markName, mark.returnValue().get());
121     return mark.releaseReturnValue();
122 }
123
124 void PerformanceUserTiming::clearMarks(const String& markName)
125 {
126     clearPerformanceEntries(m_marksMap, markName);
127 }
128
129 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(const Variant<String, double>& mark) const
130 {
131     return WTF::switchOn(mark, [&](auto& value) {
132         return convertMarkToTimestamp(value);
133     });
134 }
135
136 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(const String& mark) const
137 {
138     if (!is<Document>(m_performance.scriptExecutionContext())) {
139         if (isRestrictedMarkNameNonMainThread(mark))
140             return Exception { TypeError };
141     } else {
142         if (auto function = restrictedMarkFunction(mark)) {
143             if (function == &PerformanceTiming::navigationStart)
144                 return 0.0;
145
146             // PerformanceTiming should always be non-null for the Document ScriptExecutionContext.
147             ASSERT(m_performance.timing());
148             auto timing = m_performance.timing();
149             auto startTime = timing->navigationStart();
150             auto endTime = ((*timing).*(function))();
151             if (!endTime)
152                 return Exception { InvalidAccessError };
153             return endTime - startTime;
154         }
155     }
156
157     auto iterator = m_marksMap.find(mark);
158     if (iterator != m_marksMap.end())
159         return iterator->value.last()->startTime();
160
161     return Exception { SyntaxError, makeString("No mark named '", mark, "' exists") };
162 }
163
164 ExceptionOr<double> PerformanceUserTiming::convertMarkToTimestamp(double mark) const
165 {
166     if (mark < 0)
167         return Exception { TypeError };
168     return mark;
169 }
170
171 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(const String& measureName, const String& startMark, const String& endMark)
172 {
173     double endTime;
174     if (!endMark.isNull()) {
175         auto end = convertMarkToTimestamp(endMark);
176         if (end.hasException())
177             return end.releaseException();
178         endTime = end.returnValue();
179     } else
180         endTime = m_performance.now();
181
182     double startTime;
183     if (!startMark.isNull()) {
184         auto start = convertMarkToTimestamp(startMark);
185         if (start.hasException())
186             return start.releaseException();
187         startTime = start.returnValue();
188     } else
189         startTime = 0.0;
190         
191     auto measure = PerformanceMeasure::create(measureName, startTime, endTime, SerializedScriptValue::nullValue());
192     if (measure.hasException())
193         return measure.releaseException();
194
195     addPerformanceEntry(m_measuresMap, measureName, measure.returnValue().get());
196     return measure.releaseReturnValue();
197 }
198
199 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(JSC::JSGlobalObject& globalObject, const String& measureName, const PerformanceMeasureOptions& measureOptions)
200 {
201     double endTime;
202     if (measureOptions.end) {
203         auto end = convertMarkToTimestamp(*measureOptions.end);
204         if (end.hasException())
205             return end.releaseException();
206         endTime = end.returnValue();
207     } else if (measureOptions.start && measureOptions.duration) {
208         auto start = convertMarkToTimestamp(*measureOptions.start);
209         if (start.hasException())
210             return start.releaseException();
211         auto duration = convertMarkToTimestamp(*measureOptions.duration);
212         if (duration.hasException())
213             return start.releaseException();
214         endTime = start.returnValue() + duration.returnValue();
215     } else
216         endTime = m_performance.now();
217
218     double startTime;
219     if (measureOptions.start) {
220         auto start = convertMarkToTimestamp(*measureOptions.start);
221         if (start.hasException())
222             return start.releaseException();
223         startTime = start.returnValue();
224     } else if (measureOptions.duration && measureOptions.end) {
225         auto duration = convertMarkToTimestamp(*measureOptions.duration);
226         if (duration.hasException())
227             return duration.releaseException();
228         auto end = convertMarkToTimestamp(*measureOptions.end);
229         if (end.hasException())
230             return end.releaseException();
231         startTime = end.returnValue() - duration.returnValue();
232     } else
233         startTime = 0;
234
235
236     JSC::JSValue detail = measureOptions.detail;
237     if (detail.isUndefined())
238         detail = JSC::jsNull();
239
240     Vector<RefPtr<MessagePort>> ignoredMessagePorts;
241     auto serializedDetail = SerializedScriptValue::create(globalObject, detail, { }, ignoredMessagePorts);
242     if (serializedDetail.hasException())
243         return serializedDetail.releaseException();
244
245     auto measure = PerformanceMeasure::create(measureName, startTime, endTime, serializedDetail.releaseReturnValue());
246     if (measure.hasException())
247         return measure.releaseException();
248
249     addPerformanceEntry(m_measuresMap, measureName, measure.returnValue().get());
250     return measure.releaseReturnValue();
251 }
252
253 static bool isNonEmptyDictionary(const PerformanceMeasureOptions& measureOptions)
254 {
255     return !measureOptions.detail.isUndefined() || measureOptions.start || measureOptions.duration || measureOptions.end;
256 }
257
258 ExceptionOr<Ref<PerformanceMeasure>> PerformanceUserTiming::measure(JSC::JSGlobalObject& globalObject, const String& measureName, Optional<StartOrMeasureOptions>&& startOrMeasureOptions, const String& endMark)
259 {
260     if (startOrMeasureOptions) {
261         return WTF::switchOn(*startOrMeasureOptions,
262             [&] (const PerformanceMeasureOptions& measureOptions) -> ExceptionOr<Ref<PerformanceMeasure>> {
263                 if (isNonEmptyDictionary(measureOptions)) {
264                     if (!endMark.isNull())
265                         return Exception { TypeError };
266                     if (!measureOptions.start && !measureOptions.end)
267                         return Exception { TypeError };
268                     if (measureOptions.start && measureOptions.duration && measureOptions.end)
269                         return Exception { TypeError };
270                 }
271
272                 return measure(globalObject, measureName, measureOptions);
273             },
274             [&] (const String& startMark) {
275                 return measure(measureName, startMark, endMark);
276             }
277         );
278     }
279
280     return measure(measureName, { }, endMark);
281 }
282
283 void PerformanceUserTiming::clearMeasures(const String& measureName)
284 {
285     clearPerformanceEntries(m_measuresMap, measureName);
286 }
287
288 static Vector<RefPtr<PerformanceEntry>> convertToEntrySequence(const PerformanceEntryMap& map)
289 {
290     Vector<RefPtr<PerformanceEntry>> entries;
291     for (auto& entry : map.values())
292         entries.appendVector(entry);
293     return entries;
294 }
295
296 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMarks() const
297 {
298     return convertToEntrySequence(m_marksMap);
299 }
300
301 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMarks(const String& name) const
302 {
303     return m_marksMap.get(name);
304 }
305
306 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMeasures() const
307 {
308     return convertToEntrySequence(m_measuresMap);
309 }
310
311 Vector<RefPtr<PerformanceEntry>> PerformanceUserTiming::getMeasures(const String& name) const
312 {
313     return m_measuresMap.get(name);
314 }
315
316 } // namespace WebCore