• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Google 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 COMPUTER, 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 COMPUTER, 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 "platform/network/ResourceResponse.h"
29 
30 #include "wtf/CurrentTime.h"
31 #include "wtf/StdLibExtras.h"
32 
33 namespace blink {
34 
ResourceResponse()35 ResourceResponse::ResourceResponse()
36     : m_expectedContentLength(0)
37     , m_httpStatusCode(0)
38     , m_lastModifiedDate(0)
39     , m_wasCached(false)
40     , m_connectionID(0)
41     , m_connectionReused(false)
42     , m_isNull(true)
43     , m_haveParsedAgeHeader(false)
44     , m_haveParsedDateHeader(false)
45     , m_haveParsedExpiresHeader(false)
46     , m_haveParsedLastModifiedHeader(false)
47     , m_age(0.0)
48     , m_date(0.0)
49     , m_expires(0.0)
50     , m_lastModified(0.0)
51     , m_httpVersion(Unknown)
52     , m_appCacheID(0)
53     , m_isMultipartPayload(false)
54     , m_wasFetchedViaSPDY(false)
55     , m_wasNpnNegotiated(false)
56     , m_wasAlternateProtocolAvailable(false)
57     , m_wasFetchedViaProxy(false)
58     , m_wasFetchedViaServiceWorker(false)
59     , m_responseTime(0)
60     , m_remotePort(0)
61 {
62 }
63 
ResourceResponse(const KURL & url,const AtomicString & mimeType,long long expectedLength,const AtomicString & textEncodingName,const String & filename)64 ResourceResponse::ResourceResponse(const KURL& url, const AtomicString& mimeType, long long expectedLength, const AtomicString& textEncodingName, const String& filename)
65     : m_url(url)
66     , m_mimeType(mimeType)
67     , m_expectedContentLength(expectedLength)
68     , m_textEncodingName(textEncodingName)
69     , m_suggestedFilename(filename)
70     , m_httpStatusCode(0)
71     , m_lastModifiedDate(0)
72     , m_wasCached(false)
73     , m_connectionID(0)
74     , m_connectionReused(false)
75     , m_isNull(false)
76     , m_haveParsedAgeHeader(false)
77     , m_haveParsedDateHeader(false)
78     , m_haveParsedExpiresHeader(false)
79     , m_haveParsedLastModifiedHeader(false)
80     , m_age(0.0)
81     , m_date(0.0)
82     , m_expires(0.0)
83     , m_lastModified(0.0)
84     , m_httpVersion(Unknown)
85     , m_appCacheID(0)
86     , m_isMultipartPayload(false)
87     , m_wasFetchedViaSPDY(false)
88     , m_wasNpnNegotiated(false)
89     , m_wasAlternateProtocolAvailable(false)
90     , m_wasFetchedViaProxy(false)
91     , m_wasFetchedViaServiceWorker(false)
92     , m_responseTime(0)
93     , m_remotePort(0)
94 {
95 }
96 
adopt(PassOwnPtr<CrossThreadResourceResponseData> data)97 PassOwnPtr<ResourceResponse> ResourceResponse::adopt(PassOwnPtr<CrossThreadResourceResponseData> data)
98 {
99     OwnPtr<ResourceResponse> response = adoptPtr(new ResourceResponse);
100     response->setURL(data->m_url);
101     response->setMimeType(AtomicString(data->m_mimeType));
102     response->setExpectedContentLength(data->m_expectedContentLength);
103     response->setTextEncodingName(AtomicString(data->m_textEncodingName));
104     response->setSuggestedFilename(data->m_suggestedFilename);
105 
106     response->setHTTPStatusCode(data->m_httpStatusCode);
107     response->setHTTPStatusText(AtomicString(data->m_httpStatusText));
108 
109     response->m_httpHeaderFields.adopt(data->m_httpHeaders.release());
110     response->setLastModifiedDate(data->m_lastModifiedDate);
111     response->setResourceLoadTiming(data->m_resourceLoadTiming.release());
112     response->m_securityInfo = data->m_securityInfo;
113     response->m_httpVersion = data->m_httpVersion;
114     response->m_appCacheID = data->m_appCacheID;
115     response->m_appCacheManifestURL = data->m_appCacheManifestURL.copy();
116     response->m_isMultipartPayload = data->m_isMultipartPayload;
117     response->m_wasFetchedViaSPDY = data->m_wasFetchedViaSPDY;
118     response->m_wasNpnNegotiated = data->m_wasNpnNegotiated;
119     response->m_wasAlternateProtocolAvailable = data->m_wasAlternateProtocolAvailable;
120     response->m_wasFetchedViaProxy = data->m_wasFetchedViaProxy;
121     response->m_wasFetchedViaServiceWorker = data->m_wasFetchedViaServiceWorker;
122     response->m_responseTime = data->m_responseTime;
123     response->m_remoteIPAddress = AtomicString(data->m_remoteIPAddress);
124     response->m_remotePort = data->m_remotePort;
125     response->m_downloadedFilePath = data->m_downloadedFilePath;
126     response->m_downloadedFileHandle = data->m_downloadedFileHandle;
127 
128     // Bug https://bugs.webkit.org/show_bug.cgi?id=60397 this doesn't support
129     // whatever values may be present in the opaque m_extraData structure.
130 
131     return response.release();
132 }
133 
copyData() const134 PassOwnPtr<CrossThreadResourceResponseData> ResourceResponse::copyData() const
135 {
136     OwnPtr<CrossThreadResourceResponseData> data = adoptPtr(new CrossThreadResourceResponseData);
137     data->m_url = url().copy();
138     data->m_mimeType = mimeType().string().isolatedCopy();
139     data->m_expectedContentLength = expectedContentLength();
140     data->m_textEncodingName = textEncodingName().string().isolatedCopy();
141     data->m_suggestedFilename = suggestedFilename().isolatedCopy();
142     data->m_httpStatusCode = httpStatusCode();
143     data->m_httpStatusText = httpStatusText().string().isolatedCopy();
144     data->m_httpHeaders = httpHeaderFields().copyData();
145     data->m_lastModifiedDate = lastModifiedDate();
146     if (m_resourceLoadTiming)
147         data->m_resourceLoadTiming = m_resourceLoadTiming->deepCopy();
148     data->m_securityInfo = CString(m_securityInfo.data(), m_securityInfo.length());
149     data->m_httpVersion = m_httpVersion;
150     data->m_appCacheID = m_appCacheID;
151     data->m_appCacheManifestURL = m_appCacheManifestURL.copy();
152     data->m_isMultipartPayload = m_isMultipartPayload;
153     data->m_wasFetchedViaSPDY = m_wasFetchedViaSPDY;
154     data->m_wasNpnNegotiated = m_wasNpnNegotiated;
155     data->m_wasAlternateProtocolAvailable = m_wasAlternateProtocolAvailable;
156     data->m_wasFetchedViaProxy = m_wasFetchedViaProxy;
157     data->m_wasFetchedViaServiceWorker = m_wasFetchedViaServiceWorker;
158     data->m_responseTime = m_responseTime;
159     data->m_remoteIPAddress = m_remoteIPAddress.string().isolatedCopy();
160     data->m_remotePort = m_remotePort;
161     data->m_downloadedFilePath = m_downloadedFilePath.isolatedCopy();
162     data->m_downloadedFileHandle = m_downloadedFileHandle;
163 
164     // Bug https://bugs.webkit.org/show_bug.cgi?id=60397 this doesn't support
165     // whatever values may be present in the opaque m_extraData structure.
166 
167     return data.release();
168 }
169 
isHTTP() const170 bool ResourceResponse::isHTTP() const
171 {
172     return m_url.protocolIsInHTTPFamily();
173 }
174 
url() const175 const KURL& ResourceResponse::url() const
176 {
177     return m_url;
178 }
179 
setURL(const KURL & url)180 void ResourceResponse::setURL(const KURL& url)
181 {
182     m_isNull = false;
183 
184     m_url = url;
185 }
186 
mimeType() const187 const AtomicString& ResourceResponse::mimeType() const
188 {
189     return m_mimeType;
190 }
191 
setMimeType(const AtomicString & mimeType)192 void ResourceResponse::setMimeType(const AtomicString& mimeType)
193 {
194     m_isNull = false;
195 
196     // FIXME: MIME type is determined by HTTP Content-Type header. We should update the header, so that it doesn't disagree with m_mimeType.
197     m_mimeType = mimeType;
198 }
199 
expectedContentLength() const200 long long ResourceResponse::expectedContentLength() const
201 {
202     return m_expectedContentLength;
203 }
204 
setExpectedContentLength(long long expectedContentLength)205 void ResourceResponse::setExpectedContentLength(long long expectedContentLength)
206 {
207     m_isNull = false;
208 
209     // FIXME: Content length is determined by HTTP Content-Length header. We should update the header, so that it doesn't disagree with m_expectedContentLength.
210     m_expectedContentLength = expectedContentLength;
211 }
212 
textEncodingName() const213 const AtomicString& ResourceResponse::textEncodingName() const
214 {
215     return m_textEncodingName;
216 }
217 
setTextEncodingName(const AtomicString & encodingName)218 void ResourceResponse::setTextEncodingName(const AtomicString& encodingName)
219 {
220     m_isNull = false;
221 
222     // FIXME: Text encoding is determined by HTTP Content-Type header. We should update the header, so that it doesn't disagree with m_textEncodingName.
223     m_textEncodingName = encodingName;
224 }
225 
226 // FIXME should compute this on the fly
suggestedFilename() const227 const String& ResourceResponse::suggestedFilename() const
228 {
229     return m_suggestedFilename;
230 }
231 
setSuggestedFilename(const String & suggestedName)232 void ResourceResponse::setSuggestedFilename(const String& suggestedName)
233 {
234     m_isNull = false;
235 
236     // FIXME: Suggested file name is calculated based on other headers. There should not be a setter for it.
237     m_suggestedFilename = suggestedName;
238 }
239 
httpStatusCode() const240 int ResourceResponse::httpStatusCode() const
241 {
242     return m_httpStatusCode;
243 }
244 
setHTTPStatusCode(int statusCode)245 void ResourceResponse::setHTTPStatusCode(int statusCode)
246 {
247     m_httpStatusCode = statusCode;
248 }
249 
httpStatusText() const250 const AtomicString& ResourceResponse::httpStatusText() const
251 {
252     return m_httpStatusText;
253 }
254 
setHTTPStatusText(const AtomicString & statusText)255 void ResourceResponse::setHTTPStatusText(const AtomicString& statusText)
256 {
257     m_httpStatusText = statusText;
258 }
259 
httpHeaderField(const AtomicString & name) const260 const AtomicString& ResourceResponse::httpHeaderField(const AtomicString& name) const
261 {
262     return m_httpHeaderFields.get(name);
263 }
264 
httpHeaderField(const char * name) const265 const AtomicString& ResourceResponse::httpHeaderField(const char* name) const
266 {
267     return m_httpHeaderFields.get(name);
268 }
269 
cacheControlHeaderString()270 static const AtomicString& cacheControlHeaderString()
271 {
272     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlHeader, ("cache-control", AtomicString::ConstructFromLiteral));
273     return cacheControlHeader;
274 }
275 
pragmaHeaderString()276 static const AtomicString& pragmaHeaderString()
277 {
278     DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma", AtomicString::ConstructFromLiteral));
279     return pragmaHeader;
280 }
281 
updateHeaderParsedState(const AtomicString & name)282 void ResourceResponse::updateHeaderParsedState(const AtomicString& name)
283 {
284     DEFINE_STATIC_LOCAL(const AtomicString, ageHeader, ("age", AtomicString::ConstructFromLiteral));
285     DEFINE_STATIC_LOCAL(const AtomicString, dateHeader, ("date", AtomicString::ConstructFromLiteral));
286     DEFINE_STATIC_LOCAL(const AtomicString, expiresHeader, ("expires", AtomicString::ConstructFromLiteral));
287     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
288 
289     if (equalIgnoringCase(name, ageHeader))
290         m_haveParsedAgeHeader = false;
291     else if (equalIgnoringCase(name, cacheControlHeaderString()) || equalIgnoringCase(name, pragmaHeaderString()))
292         m_cacheControlHeader = CacheControlHeader();
293     else if (equalIgnoringCase(name, dateHeader))
294         m_haveParsedDateHeader = false;
295     else if (equalIgnoringCase(name, expiresHeader))
296         m_haveParsedExpiresHeader = false;
297     else if (equalIgnoringCase(name, lastModifiedHeader))
298         m_haveParsedLastModifiedHeader = false;
299 }
300 
setHTTPHeaderField(const AtomicString & name,const AtomicString & value)301 void ResourceResponse::setHTTPHeaderField(const AtomicString& name, const AtomicString& value)
302 {
303     updateHeaderParsedState(name);
304 
305     m_httpHeaderFields.set(name, value);
306 }
307 
addHTTPHeaderField(const AtomicString & name,const AtomicString & value)308 void ResourceResponse::addHTTPHeaderField(const AtomicString& name, const AtomicString& value)
309 {
310     updateHeaderParsedState(name);
311 
312     HTTPHeaderMap::AddResult result = m_httpHeaderFields.add(name, value);
313     if (!result.isNewEntry)
314         result.storedValue->value = result.storedValue->value + ", " + value;
315 }
316 
clearHTTPHeaderField(const AtomicString & name)317 void ResourceResponse::clearHTTPHeaderField(const AtomicString& name)
318 {
319     m_httpHeaderFields.remove(name);
320 }
321 
httpHeaderFields() const322 const HTTPHeaderMap& ResourceResponse::httpHeaderFields() const
323 {
324     return m_httpHeaderFields;
325 }
326 
cacheControlContainsNoCache()327 bool ResourceResponse::cacheControlContainsNoCache()
328 {
329     if (!m_cacheControlHeader.parsed)
330         m_cacheControlHeader = parseCacheControlDirectives(m_httpHeaderFields.get(cacheControlHeaderString()), m_httpHeaderFields.get(pragmaHeaderString()));
331     return m_cacheControlHeader.containsNoCache;
332 }
333 
cacheControlContainsNoStore()334 bool ResourceResponse::cacheControlContainsNoStore()
335 {
336     if (!m_cacheControlHeader.parsed)
337         m_cacheControlHeader = parseCacheControlDirectives(m_httpHeaderFields.get(cacheControlHeaderString()), m_httpHeaderFields.get(pragmaHeaderString()));
338     return m_cacheControlHeader.containsNoStore;
339 }
340 
cacheControlContainsMustRevalidate()341 bool ResourceResponse::cacheControlContainsMustRevalidate()
342 {
343     if (!m_cacheControlHeader.parsed)
344         m_cacheControlHeader = parseCacheControlDirectives(m_httpHeaderFields.get(cacheControlHeaderString()), m_httpHeaderFields.get(pragmaHeaderString()));
345     return m_cacheControlHeader.containsMustRevalidate;
346 }
347 
hasCacheValidatorFields() const348 bool ResourceResponse::hasCacheValidatorFields() const
349 {
350     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
351     DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag", AtomicString::ConstructFromLiteral));
352     return !m_httpHeaderFields.get(lastModifiedHeader).isEmpty() || !m_httpHeaderFields.get(eTagHeader).isEmpty();
353 }
354 
cacheControlMaxAge()355 double ResourceResponse::cacheControlMaxAge()
356 {
357     if (!m_cacheControlHeader.parsed)
358         m_cacheControlHeader = parseCacheControlDirectives(m_httpHeaderFields.get(cacheControlHeaderString()), m_httpHeaderFields.get(pragmaHeaderString()));
359     return m_cacheControlHeader.maxAge;
360 }
361 
parseDateValueInHeader(const HTTPHeaderMap & headers,const AtomicString & headerName)362 static double parseDateValueInHeader(const HTTPHeaderMap& headers, const AtomicString& headerName)
363 {
364     const AtomicString& headerValue = headers.get(headerName);
365     if (headerValue.isEmpty())
366         return std::numeric_limits<double>::quiet_NaN();
367     // This handles all date formats required by RFC2616:
368     // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
369     // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
370     // Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
371     double dateInMilliseconds = parseDate(headerValue);
372     if (!std::isfinite(dateInMilliseconds))
373         return std::numeric_limits<double>::quiet_NaN();
374     return dateInMilliseconds / 1000;
375 }
376 
date() const377 double ResourceResponse::date() const
378 {
379     if (!m_haveParsedDateHeader) {
380         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("date", AtomicString::ConstructFromLiteral));
381         m_date = parseDateValueInHeader(m_httpHeaderFields, headerName);
382         m_haveParsedDateHeader = true;
383     }
384     return m_date;
385 }
386 
age() const387 double ResourceResponse::age() const
388 {
389     if (!m_haveParsedAgeHeader) {
390         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("age", AtomicString::ConstructFromLiteral));
391         const AtomicString& headerValue = m_httpHeaderFields.get(headerName);
392         bool ok;
393         m_age = headerValue.toDouble(&ok);
394         if (!ok)
395             m_age = std::numeric_limits<double>::quiet_NaN();
396         m_haveParsedAgeHeader = true;
397     }
398     return m_age;
399 }
400 
expires() const401 double ResourceResponse::expires() const
402 {
403     if (!m_haveParsedExpiresHeader) {
404         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("expires", AtomicString::ConstructFromLiteral));
405         m_expires = parseDateValueInHeader(m_httpHeaderFields, headerName);
406         m_haveParsedExpiresHeader = true;
407     }
408     return m_expires;
409 }
410 
lastModified() const411 double ResourceResponse::lastModified() const
412 {
413     if (!m_haveParsedLastModifiedHeader) {
414         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("last-modified", AtomicString::ConstructFromLiteral));
415         m_lastModified = parseDateValueInHeader(m_httpHeaderFields, headerName);
416         m_haveParsedLastModifiedHeader = true;
417     }
418     return m_lastModified;
419 }
420 
isAttachment() const421 bool ResourceResponse::isAttachment() const
422 {
423     DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("content-disposition", AtomicString::ConstructFromLiteral));
424     String value = m_httpHeaderFields.get(headerName);
425     size_t loc = value.find(';');
426     if (loc != kNotFound)
427         value = value.left(loc);
428     value = value.stripWhiteSpace();
429     DEFINE_STATIC_LOCAL(const AtomicString, attachmentString, ("attachment", AtomicString::ConstructFromLiteral));
430     return equalIgnoringCase(value, attachmentString);
431 }
432 
setLastModifiedDate(time_t lastModifiedDate)433 void ResourceResponse::setLastModifiedDate(time_t lastModifiedDate)
434 {
435     m_lastModifiedDate = lastModifiedDate;
436 }
437 
lastModifiedDate() const438 time_t ResourceResponse::lastModifiedDate() const
439 {
440     return m_lastModifiedDate;
441 }
442 
wasCached() const443 bool ResourceResponse::wasCached() const
444 {
445     return m_wasCached;
446 }
447 
setWasCached(bool value)448 void ResourceResponse::setWasCached(bool value)
449 {
450     m_wasCached = value;
451 }
452 
connectionReused() const453 bool ResourceResponse::connectionReused() const
454 {
455     return m_connectionReused;
456 }
457 
setConnectionReused(bool connectionReused)458 void ResourceResponse::setConnectionReused(bool connectionReused)
459 {
460     m_connectionReused = connectionReused;
461 }
462 
connectionID() const463 unsigned ResourceResponse::connectionID() const
464 {
465     return m_connectionID;
466 }
467 
setConnectionID(unsigned connectionID)468 void ResourceResponse::setConnectionID(unsigned connectionID)
469 {
470     m_connectionID = connectionID;
471 }
472 
resourceLoadTiming() const473 ResourceLoadTiming* ResourceResponse::resourceLoadTiming() const
474 {
475     return m_resourceLoadTiming.get();
476 }
477 
setResourceLoadTiming(PassRefPtr<ResourceLoadTiming> resourceLoadTiming)478 void ResourceResponse::setResourceLoadTiming(PassRefPtr<ResourceLoadTiming> resourceLoadTiming)
479 {
480     m_resourceLoadTiming = resourceLoadTiming;
481 }
482 
resourceLoadInfo() const483 PassRefPtr<ResourceLoadInfo> ResourceResponse::resourceLoadInfo() const
484 {
485     return m_resourceLoadInfo.get();
486 }
487 
setResourceLoadInfo(PassRefPtr<ResourceLoadInfo> loadInfo)488 void ResourceResponse::setResourceLoadInfo(PassRefPtr<ResourceLoadInfo> loadInfo)
489 {
490     m_resourceLoadInfo = loadInfo;
491 }
492 
setDownloadedFilePath(const String & downloadedFilePath)493 void ResourceResponse::setDownloadedFilePath(const String& downloadedFilePath)
494 {
495     m_downloadedFilePath = downloadedFilePath;
496     if (m_downloadedFilePath.isEmpty()) {
497         m_downloadedFileHandle.clear();
498         return;
499     }
500     OwnPtr<BlobData> blobData = BlobData::create();
501     blobData->appendFile(m_downloadedFilePath);
502     blobData->detachFromCurrentThread();
503     m_downloadedFileHandle = BlobDataHandle::create(blobData.release(), -1);
504 }
505 
compare(const ResourceResponse & a,const ResourceResponse & b)506 bool ResourceResponse::compare(const ResourceResponse& a, const ResourceResponse& b)
507 {
508     if (a.isNull() != b.isNull())
509         return false;
510     if (a.url() != b.url())
511         return false;
512     if (a.mimeType() != b.mimeType())
513         return false;
514     if (a.expectedContentLength() != b.expectedContentLength())
515         return false;
516     if (a.textEncodingName() != b.textEncodingName())
517         return false;
518     if (a.suggestedFilename() != b.suggestedFilename())
519         return false;
520     if (a.httpStatusCode() != b.httpStatusCode())
521         return false;
522     if (a.httpStatusText() != b.httpStatusText())
523         return false;
524     if (a.httpHeaderFields() != b.httpHeaderFields())
525         return false;
526     if (a.resourceLoadTiming() && b.resourceLoadTiming() && *a.resourceLoadTiming() == *b.resourceLoadTiming())
527         return true;
528     if (a.resourceLoadTiming() != b.resourceLoadTiming())
529         return false;
530     return true;
531 }
532 
533 }
534