1'use strict';
2// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming
3
4const { InternalPerformanceEntry } = require('internal/perf/performance_entry');
5const { SymbolToStringTag } = primordials;
6const assert = require('internal/assert');
7const { enqueue, bufferResourceTiming } = require('internal/perf/observe');
8const { Symbol, ObjectSetPrototypeOf } = primordials;
9
10const kCacheMode = Symbol('kCacheMode');
11const kRequestedUrl = Symbol('kRequestedUrl');
12const kTimingInfo = Symbol('kTimingInfo');
13const kInitiatorType = Symbol('kInitiatorType');
14
15const {
16  codes: {
17    ERR_ILLEGAL_CONSTRUCTOR,
18  },
19} = require('internal/errors');
20
21class InternalPerformanceResourceTiming extends InternalPerformanceEntry {
22  constructor(requestedUrl, initiatorType, timingInfo, cacheMode = '') {
23    super(requestedUrl, 'resource');
24    this[kInitiatorType] = initiatorType;
25    this[kRequestedUrl] = requestedUrl;
26    // https://fetch.spec.whatwg.org/#fetch-timing-info
27    // This class is using timingInfo assuming it's already validated.
28    // The spec doesn't say to validate it in the class construction.
29    this[kTimingInfo] = timingInfo;
30    this[kCacheMode] = cacheMode;
31  }
32
33  get [SymbolToStringTag]() {
34    return 'PerformanceResourceTiming';
35  }
36
37  get name() {
38    return this[kRequestedUrl];
39  }
40
41  get startTime() {
42    return this[kTimingInfo].startTime;
43  }
44
45  get duration() {
46    return this[kTimingInfo].endTime - this[kTimingInfo].startTime;
47  }
48
49  get initiatorType() {
50    return this[kInitiatorType];
51  }
52
53  get workerStart() {
54    return this[kTimingInfo].finalServiceWorkerStartTime;
55  }
56
57  get redirectStart() {
58    return this[kTimingInfo].redirectStartTime;
59  }
60
61  get redirectEnd() {
62    return this[kTimingInfo].redirectEndTime;
63  }
64
65  get fetchStart() {
66    return this[kTimingInfo].postRedirectStartTime;
67  }
68
69  get domainLookupStart() {
70    return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupStartTime;
71  }
72
73  get domainLookupEnd() {
74    return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupEndTime;
75  }
76
77  get connectStart() {
78    return this[kTimingInfo].finalConnectionTimingInfo?.connectionStartTime;
79  }
80
81  get connectEnd() {
82    return this[kTimingInfo].finalConnectionTimingInfo?.connectionEndTime;
83  }
84
85  get secureConnectionStart() {
86    return this[kTimingInfo]
87      .finalConnectionTimingInfo?.secureConnectionStartTime;
88  }
89
90  get nextHopProtocol() {
91    return this[kTimingInfo]
92      .finalConnectionTimingInfo?.ALPNNegotiatedProtocol;
93  }
94
95  get requestStart() {
96    return this[kTimingInfo].finalNetworkRequestStartTime;
97  }
98
99  get responseStart() {
100    return this[kTimingInfo].finalNetworkResponseStartTime;
101  }
102
103  get responseEnd() {
104    return this[kTimingInfo].endTime;
105  }
106
107  get encodedBodySize() {
108    return this[kTimingInfo].encodedBodySize;
109  }
110
111  get decodedBodySize() {
112    return this[kTimingInfo].decodedBodySize;
113  }
114
115  get transferSize() {
116    if (this[kCacheMode] === 'local') return 0;
117    if (this[kCacheMode] === 'validated') return 300;
118
119    return this[kTimingInfo].encodedBodySize + 300;
120  }
121
122  toJSON() {
123    return {
124      name: this.name,
125      entryType: this.entryType,
126      startTime: this.startTime,
127      duration: this.duration,
128      initiatorType: this[kInitiatorType],
129      nextHopProtocol: this.nextHopProtocol,
130      workerStart: this.workerStart,
131      redirectStart: this.redirectStart,
132      redirectEnd: this.redirectEnd,
133      fetchStart: this.fetchStart,
134      domainLookupStart: this.domainLookupStart,
135      domainLookupEnd: this.domainLookupEnd,
136      connectStart: this.connectStart,
137      connectEnd: this.connectEnd,
138      secureConnectionStart: this.secureConnectionStart,
139      requestStart: this.requestStart,
140      responseStart: this.responseStart,
141      responseEnd: this.responseEnd,
142      transferSize: this.transferSize,
143      encodedBodySize: this.encodedBodySize,
144      decodedBodySize: this.decodedBodySize,
145    };
146  }
147}
148
149class PerformanceResourceTiming extends InternalPerformanceResourceTiming {
150  constructor() {
151    throw new ERR_ILLEGAL_CONSTRUCTOR();
152  }
153}
154
155// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing
156function markResourceTiming(
157  timingInfo,
158  requestedUrl,
159  initiatorType,
160  global,
161  cacheMode,
162) {
163  // https://w3c.github.io/resource-timing/#dfn-setup-the-resource-timing-entry
164  assert(
165    cacheMode === '' || cacheMode === 'local',
166    'cache must be an empty string or \'local\'',
167  );
168  const resource = new InternalPerformanceResourceTiming(
169    requestedUrl,
170    initiatorType,
171    timingInfo,
172    cacheMode,
173  );
174
175  ObjectSetPrototypeOf(resource, PerformanceResourceTiming.prototype);
176  enqueue(resource);
177  bufferResourceTiming(resource);
178  return resource;
179}
180
181module.exports = {
182  PerformanceResourceTiming,
183  markResourceTiming,
184};
185