| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) | |
| 3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org) | |
| 4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org) | |
| 5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) | |
| 6 Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All
rights reserved. | |
| 7 | |
| 8 This library is free software; you can redistribute it and/or | |
| 9 modify it under the terms of the GNU Library General Public | |
| 10 License as published by the Free Software Foundation; either | |
| 11 version 2 of the License, or (at your option) any later version. | |
| 12 | |
| 13 This library is distributed in the hope that it will be useful, | |
| 14 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 16 Library General Public License for more details. | |
| 17 | |
| 18 You should have received a copy of the GNU Library General Public License | |
| 19 along with this library; see the file COPYING.LIB. If not, write to | |
| 20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
| 21 Boston, MA 02110-1301, USA. | |
| 22 */ | |
| 23 | |
| 24 #include "sky/engine/core/fetch/Resource.h" | |
| 25 | |
| 26 #include "gen/sky/core/FetchInitiatorTypeNames.h" | |
| 27 #include "sky/engine/core/fetch/MemoryCache.h" | |
| 28 #include "sky/engine/core/fetch/ResourceClient.h" | |
| 29 #include "sky/engine/core/fetch/ResourceClientWalker.h" | |
| 30 #include "sky/engine/core/fetch/ResourceFetcher.h" | |
| 31 #include "sky/engine/core/fetch/ResourceLoader.h" | |
| 32 #include "sky/engine/core/fetch/ResourcePtr.h" | |
| 33 #include "sky/engine/platform/Logging.h" | |
| 34 #include "sky/engine/platform/SharedBuffer.h" | |
| 35 #include "sky/engine/platform/TraceEvent.h" | |
| 36 #include "sky/engine/platform/weborigin/KURL.h" | |
| 37 #include "sky/engine/public/platform/Platform.h" | |
| 38 #include "sky/engine/wtf/CurrentTime.h" | |
| 39 #include "sky/engine/wtf/MathExtras.h" | |
| 40 #include "sky/engine/wtf/RefCountedLeakCounter.h" | |
| 41 #include "sky/engine/wtf/StdLibExtras.h" | |
| 42 #include "sky/engine/wtf/Vector.h" | |
| 43 #include "sky/engine/wtf/text/CString.h" | |
| 44 | |
| 45 using namespace WTF; | |
| 46 | |
| 47 namespace blink { | |
| 48 | |
| 49 // These response headers are not copied from a revalidated response to the | |
| 50 // cached response headers. For compatibility, this list is based on Chromium's | |
| 51 // net/http/http_response_headers.cc. | |
| 52 const char* const headersToIgnoreAfterRevalidation[] = { | |
| 53 "allow", | |
| 54 "connection", | |
| 55 "etag", | |
| 56 "expires", | |
| 57 "keep-alive", | |
| 58 "last-modified" | |
| 59 "proxy-authenticate", | |
| 60 "proxy-connection", | |
| 61 "trailer", | |
| 62 "transfer-encoding", | |
| 63 "upgrade", | |
| 64 "www-authenticate", | |
| 65 "x-frame-options", | |
| 66 "x-xss-protection", | |
| 67 }; | |
| 68 | |
| 69 // Some header prefixes mean "Don't copy this header from a 304 response.". | |
| 70 // Rather than listing all the relevant headers, we can consolidate them into | |
| 71 // this list, also grabbed from Chromium's net/http/http_response_headers.cc. | |
| 72 const char* const headerPrefixesToIgnoreAfterRevalidation[] = { | |
| 73 "content-", | |
| 74 "x-content-", | |
| 75 "x-webkit-" | |
| 76 }; | |
| 77 | |
| 78 static inline bool shouldUpdateHeaderAfterRevalidation(const AtomicString& heade
r) | |
| 79 { | |
| 80 for (size_t i = 0; i < WTF_ARRAY_LENGTH(headersToIgnoreAfterRevalidation); i
++) { | |
| 81 if (equalIgnoringCase(header, headersToIgnoreAfterRevalidation[i])) | |
| 82 return false; | |
| 83 } | |
| 84 for (size_t i = 0; i < WTF_ARRAY_LENGTH(headerPrefixesToIgnoreAfterRevalidat
ion); i++) { | |
| 85 if (header.startsWith(headerPrefixesToIgnoreAfterRevalidation[i], false)
) | |
| 86 return false; | |
| 87 } | |
| 88 return true; | |
| 89 } | |
| 90 | |
| 91 DEFINE_DEBUG_ONLY_GLOBAL(RefCountedLeakCounter, cachedResourceLeakCounter, ("Res
ource")); | |
| 92 unsigned Resource::s_instanceCount = 0; | |
| 93 | |
| 94 Resource::Resource(const ResourceRequest& request, Type type) | |
| 95 : m_resourceRequest(request) | |
| 96 , m_responseTimestamp(currentTime()) | |
| 97 , m_cancelTimer(this, &Resource::cancelTimerFired) | |
| 98 , m_loadFinishTime(0) | |
| 99 , m_identifier(0) | |
| 100 , m_encodedSize(0) | |
| 101 , m_decodedSize(0) | |
| 102 , m_handleCount(0) | |
| 103 , m_protectorCount(0) | |
| 104 , m_requestedFromNetworkingLayer(false) | |
| 105 , m_loading(false) | |
| 106 , m_switchingClientsToRevalidatedResource(false) | |
| 107 , m_type(type) | |
| 108 , m_status(Pending) | |
| 109 , m_wasPurged(false) | |
| 110 , m_needsSynchronousCacheHit(false) | |
| 111 #ifdef ENABLE_RESOURCE_IS_DELETED_CHECK | |
| 112 , m_deleted(false) | |
| 113 #endif | |
| 114 , m_resourceToRevalidate(nullptr) | |
| 115 , m_proxyResource(nullptr) | |
| 116 { | |
| 117 ASSERT(m_type == unsigned(type)); // m_type is a bitfield, so this tests car
eless updates of the enum. | |
| 118 ++s_instanceCount; | |
| 119 #ifndef NDEBUG | |
| 120 cachedResourceLeakCounter.increment(); | |
| 121 #endif | |
| 122 memoryCache()->registerLiveResource(*this); | |
| 123 | |
| 124 if (!m_resourceRequest.url().hasFragmentIdentifier()) | |
| 125 return; | |
| 126 KURL urlForCache = MemoryCache::removeFragmentIdentifierIfNeeded(m_resourceR
equest.url()); | |
| 127 if (urlForCache.hasFragmentIdentifier()) | |
| 128 return; | |
| 129 m_fragmentIdentifierForRequest = m_resourceRequest.url().fragmentIdentifier(
); | |
| 130 m_resourceRequest.setURL(urlForCache); | |
| 131 } | |
| 132 | |
| 133 Resource::~Resource() | |
| 134 { | |
| 135 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() check
s this. | |
| 136 ASSERT(canDelete()); | |
| 137 RELEASE_ASSERT(!memoryCache()->contains(this)); | |
| 138 RELEASE_ASSERT(!ResourceCallback::callbackHandler()->isScheduled(this)); | |
| 139 ASSERT(url().isNull() || memoryCache()->resourceForURL(KURL(ParsedURLString,
url())) != this); | |
| 140 assertAlive(); | |
| 141 | |
| 142 #ifdef ENABLE_RESOURCE_IS_DELETED_CHECK | |
| 143 m_deleted = true; | |
| 144 #endif | |
| 145 #ifndef NDEBUG | |
| 146 cachedResourceLeakCounter.decrement(); | |
| 147 #endif | |
| 148 --s_instanceCount; | |
| 149 } | |
| 150 | |
| 151 void Resource::dispose() | |
| 152 { | |
| 153 } | |
| 154 | |
| 155 void Resource::failBeforeStarting() | |
| 156 { | |
| 157 WTF_LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1(
).data()); | |
| 158 error(Resource::LoadError); | |
| 159 } | |
| 160 | |
| 161 void Resource::load(ResourceFetcher* fetcher, const ResourceLoaderOptions& optio
ns) | |
| 162 { | |
| 163 if (!fetcher->frame()) { | |
| 164 failBeforeStarting(); | |
| 165 return; | |
| 166 } | |
| 167 | |
| 168 m_options = options; | |
| 169 m_loading = true; | |
| 170 | |
| 171 if (!accept().isEmpty()) | |
| 172 m_resourceRequest.setHTTPAccept(accept()); | |
| 173 | |
| 174 // FIXME: It's unfortunate that the cache layer and below get to know anythi
ng about fragment identifiers. | |
| 175 // We should look into removing the expectation of that knowledge from the p
latform network stacks. | |
| 176 ResourceRequest request(m_resourceRequest); | |
| 177 if (!m_fragmentIdentifierForRequest.isNull()) { | |
| 178 KURL url = request.url(); | |
| 179 url.setFragmentIdentifier(m_fragmentIdentifierForRequest); | |
| 180 request.setURL(url); | |
| 181 m_fragmentIdentifierForRequest = String(); | |
| 182 } | |
| 183 m_status = Pending; | |
| 184 m_loader = ResourceLoader::create(fetcher, this, request, options); | |
| 185 m_loader->start(); | |
| 186 } | |
| 187 | |
| 188 void Resource::checkNotify() | |
| 189 { | |
| 190 if (isLoading()) | |
| 191 return; | |
| 192 | |
| 193 ResourceClientWalker<ResourceClient> w(m_clients); | |
| 194 while (ResourceClient* c = w.next()) | |
| 195 c->notifyFinished(this); | |
| 196 } | |
| 197 | |
| 198 void Resource::appendData(const char* data, int length) | |
| 199 { | |
| 200 TRACE_EVENT0("blink", "Resource::appendData"); | |
| 201 ASSERT(!m_resourceToRevalidate); | |
| 202 ASSERT(!errorOccurred()); | |
| 203 if (m_options.dataBufferingPolicy == DoNotBufferData) | |
| 204 return; | |
| 205 if (m_data) | |
| 206 m_data->append(data, length); | |
| 207 else | |
| 208 m_data = SharedBuffer::createPurgeable(data, length); | |
| 209 setEncodedSize(m_data->size()); | |
| 210 } | |
| 211 | |
| 212 void Resource::setResourceBuffer(PassRefPtr<SharedBuffer> resourceBuffer) | |
| 213 { | |
| 214 ASSERT(!m_resourceToRevalidate); | |
| 215 ASSERT(!errorOccurred()); | |
| 216 ASSERT(m_options.dataBufferingPolicy == BufferData); | |
| 217 m_data = resourceBuffer; | |
| 218 setEncodedSize(m_data->size()); | |
| 219 } | |
| 220 | |
| 221 void Resource::setDataBufferingPolicy(DataBufferingPolicy dataBufferingPolicy) | |
| 222 { | |
| 223 m_options.dataBufferingPolicy = dataBufferingPolicy; | |
| 224 m_data.clear(); | |
| 225 setEncodedSize(0); | |
| 226 } | |
| 227 | |
| 228 void Resource::error(Resource::Status status) | |
| 229 { | |
| 230 if (m_resourceToRevalidate) | |
| 231 revalidationFailed(); | |
| 232 | |
| 233 if (!m_error.isNull()) | |
| 234 memoryCache()->remove(this); | |
| 235 | |
| 236 setStatus(status); | |
| 237 ASSERT(errorOccurred()); | |
| 238 m_data.clear(); | |
| 239 | |
| 240 setLoading(false); | |
| 241 checkNotify(); | |
| 242 } | |
| 243 | |
| 244 void Resource::finishOnePart() | |
| 245 { | |
| 246 setLoading(false); | |
| 247 checkNotify(); | |
| 248 } | |
| 249 | |
| 250 void Resource::finish(double finishTime) | |
| 251 { | |
| 252 ASSERT(!m_resourceToRevalidate); | |
| 253 ASSERT(!errorOccurred()); | |
| 254 m_loadFinishTime = finishTime; | |
| 255 finishOnePart(); | |
| 256 if (!errorOccurred()) | |
| 257 m_status = Cached; | |
| 258 } | |
| 259 | |
| 260 static double currentAge(const ResourceResponse& response, double responseTimest
amp) | |
| 261 { | |
| 262 // RFC2616 13.2.3 | |
| 263 // No compensation for latency as that is not terribly important in practice | |
| 264 double dateValue = response.date(); | |
| 265 double apparentAge = std::isfinite(dateValue) ? std::max(0., responseTimesta
mp - dateValue) : 0; | |
| 266 double ageValue = response.age(); | |
| 267 double correctedReceivedAge = std::isfinite(ageValue) ? std::max(apparentAge
, ageValue) : apparentAge; | |
| 268 double residentTime = currentTime() - responseTimestamp; | |
| 269 return correctedReceivedAge + residentTime; | |
| 270 } | |
| 271 | |
| 272 static double freshnessLifetime(ResourceResponse& response, double responseTimes
tamp) | |
| 273 { | |
| 274 #if !OS(ANDROID) | |
| 275 // On desktop, local files should be reloaded in case they change. | |
| 276 if (response.url().isLocalFile()) | |
| 277 return 0; | |
| 278 #endif | |
| 279 | |
| 280 // Cache other non-http / non-filesystem resources liberally. | |
| 281 if (!response.url().protocolIsInHTTPFamily() | |
| 282 && !response.url().protocolIs("filesystem")) | |
| 283 return std::numeric_limits<double>::max(); | |
| 284 | |
| 285 // RFC2616 13.2.4 | |
| 286 double maxAgeValue = response.cacheControlMaxAge(); | |
| 287 if (std::isfinite(maxAgeValue)) | |
| 288 return maxAgeValue; | |
| 289 double expiresValue = response.expires(); | |
| 290 double dateValue = response.date(); | |
| 291 double creationTime = std::isfinite(dateValue) ? dateValue : responseTimesta
mp; | |
| 292 if (std::isfinite(expiresValue)) | |
| 293 return expiresValue - creationTime; | |
| 294 double lastModifiedValue = response.lastModified(); | |
| 295 if (std::isfinite(lastModifiedValue)) | |
| 296 return (creationTime - lastModifiedValue) * 0.1; | |
| 297 // If no cache headers are present, the specification leaves the decision to
the UA. Other browsers seem to opt for 0. | |
| 298 return 0; | |
| 299 } | |
| 300 | |
| 301 static bool canUseResponse(ResourceResponse& response, double responseTimestamp) | |
| 302 { | |
| 303 if (response.isNull()) | |
| 304 return false; | |
| 305 | |
| 306 // FIXME: Why isn't must-revalidate considered a reason we can't use the res
ponse? | |
| 307 if (response.cacheControlContainsNoCache() || response.cacheControlContainsN
oStore()) | |
| 308 return false; | |
| 309 | |
| 310 if (response.httpStatusCode() == 303) { | |
| 311 // Must not be cached. | |
| 312 return false; | |
| 313 } | |
| 314 | |
| 315 if (response.httpStatusCode() == 302 || response.httpStatusCode() == 307) { | |
| 316 // Default to not cacheable unless explicitly allowed. | |
| 317 bool hasMaxAge = std::isfinite(response.cacheControlMaxAge()); | |
| 318 bool hasExpires = std::isfinite(response.expires()); | |
| 319 // TODO: consider catching Cache-Control "private" and "public" here. | |
| 320 if (!hasMaxAge && !hasExpires) | |
| 321 return false; | |
| 322 } | |
| 323 | |
| 324 return currentAge(response, responseTimestamp) <= freshnessLifetime(response
, responseTimestamp); | |
| 325 } | |
| 326 | |
| 327 void Resource::willSendRequest(ResourceRequest& request, const ResourceResponse&
response) | |
| 328 { | |
| 329 m_requestedFromNetworkingLayer = true; | |
| 330 } | |
| 331 | |
| 332 bool Resource::unlock() | |
| 333 { | |
| 334 if (!m_data) | |
| 335 return false; | |
| 336 | |
| 337 if (!m_data->isLocked()) | |
| 338 return true; | |
| 339 | |
| 340 if (!memoryCache()->contains(this) || hasClients() || m_handleCount > 1 || m
_proxyResource || m_resourceToRevalidate || !m_loadFinishTime || !isSafeToUnlock
()) | |
| 341 return false; | |
| 342 | |
| 343 m_data->unlock(); | |
| 344 return true; | |
| 345 } | |
| 346 | |
| 347 bool Resource::hasRightHandleCountApartFromCache(unsigned targetCount) const | |
| 348 { | |
| 349 return m_handleCount == targetCount + (memoryCache()->contains(this) ? 1 : 0
); | |
| 350 } | |
| 351 | |
| 352 void Resource::responseReceived(const ResourceResponse& response) | |
| 353 { | |
| 354 setResponse(response); | |
| 355 m_responseTimestamp = currentTime(); | |
| 356 String encoding = response.textEncodingName(); | |
| 357 if (!encoding.isNull()) | |
| 358 setEncoding(encoding); | |
| 359 | |
| 360 if (!m_resourceToRevalidate) | |
| 361 return; | |
| 362 if (response.httpStatusCode() == 304) | |
| 363 revalidationSucceeded(response); | |
| 364 else | |
| 365 revalidationFailed(); | |
| 366 } | |
| 367 | |
| 368 bool Resource::canDelete() const | |
| 369 { | |
| 370 return !hasClients() && !m_loader && hasRightHandleCountApartFromCache(0) | |
| 371 && !m_protectorCount && !m_resourceToRevalidate && !m_proxyResource; | |
| 372 } | |
| 373 | |
| 374 bool Resource::hasOneHandle() const | |
| 375 { | |
| 376 return hasRightHandleCountApartFromCache(1); | |
| 377 } | |
| 378 | |
| 379 void Resource::clearLoader() | |
| 380 { | |
| 381 m_loader = nullptr; | |
| 382 } | |
| 383 | |
| 384 void Resource::addClient(ResourceClient* client) | |
| 385 { | |
| 386 if (addClientToSet(client)) | |
| 387 didAddClient(client); | |
| 388 } | |
| 389 | |
| 390 void Resource::didAddClient(ResourceClient* c) | |
| 391 { | |
| 392 if (!isLoading() && !stillNeedsLoad()) | |
| 393 c->notifyFinished(this); | |
| 394 } | |
| 395 | |
| 396 static bool shouldSendCachedDataSynchronouslyForType(Resource::Type type) | |
| 397 { | |
| 398 // Some resources types default to return data synchronously. | |
| 399 // For most of these, it's because there are layout tests that | |
| 400 // expect data to return synchronously in case of cache hit. In | |
| 401 // the case of fonts, there was a performance regression. | |
| 402 // FIXME: Get to the point where we don't need to special-case sync/async | |
| 403 // behavior for different resource types. | |
| 404 if (type == Resource::Image) | |
| 405 return true; | |
| 406 if (type == Resource::Font) | |
| 407 return true; | |
| 408 return false; | |
| 409 } | |
| 410 | |
| 411 bool Resource::addClientToSet(ResourceClient* client) | |
| 412 { | |
| 413 ASSERT(!isPurgeable()); | |
| 414 | |
| 415 if (!hasClients()) | |
| 416 memoryCache()->makeLive(this); | |
| 417 | |
| 418 // If we have existing data to send to the new client and the resource type
supprts it, send it asynchronously. | |
| 419 if (!m_response.isNull() && !m_proxyResource && !shouldSendCachedDataSynchro
nouslyForType(type()) && !m_needsSynchronousCacheHit) { | |
| 420 m_clientsAwaitingCallback.add(client); | |
| 421 ResourceCallback::callbackHandler()->schedule(this); | |
| 422 return false; | |
| 423 } | |
| 424 | |
| 425 m_clients.add(client); | |
| 426 return true; | |
| 427 } | |
| 428 | |
| 429 void Resource::removeClient(ResourceClient* client) | |
| 430 { | |
| 431 if (m_clientsAwaitingCallback.contains(client)) { | |
| 432 ASSERT(!m_clients.contains(client)); | |
| 433 m_clientsAwaitingCallback.remove(client); | |
| 434 } else { | |
| 435 ASSERT(m_clients.contains(client)); | |
| 436 m_clients.remove(client); | |
| 437 didRemoveClient(client); | |
| 438 } | |
| 439 | |
| 440 if (m_clientsAwaitingCallback.isEmpty()) | |
| 441 ResourceCallback::callbackHandler()->cancel(this); | |
| 442 | |
| 443 bool deleted = deleteIfPossible(); | |
| 444 if (!deleted && !hasClients()) { | |
| 445 memoryCache()->makeDead(this); | |
| 446 if (!m_switchingClientsToRevalidatedResource) | |
| 447 allClientsRemoved(); | |
| 448 | |
| 449 // RFC2616 14.9.2: | |
| 450 // "no-store: ... MUST make a best-effort attempt to remove the informat
ion from volatile storage as promptly as possible" | |
| 451 // "... History buffers MAY store such responses as part of their normal
operation." | |
| 452 // We allow non-secure content to be reused in history, but we do not al
low secure content to be reused. | |
| 453 if (hasCacheControlNoStoreHeader() && url().protocolIs("https")) { | |
| 454 memoryCache()->remove(this); | |
| 455 memoryCache()->prune(); | |
| 456 } else { | |
| 457 memoryCache()->prune(this); | |
| 458 } | |
| 459 } | |
| 460 // This object may be dead here. | |
| 461 } | |
| 462 | |
| 463 void Resource::allClientsRemoved() | |
| 464 { | |
| 465 if (!m_loader) | |
| 466 return; | |
| 467 if (m_type == MainResource || m_type == Raw) | |
| 468 cancelTimerFired(&m_cancelTimer); | |
| 469 else if (!m_cancelTimer.isActive()) | |
| 470 m_cancelTimer.startOneShot(0, FROM_HERE); | |
| 471 | |
| 472 unlock(); | |
| 473 } | |
| 474 | |
| 475 void Resource::cancelTimerFired(Timer<Resource>* timer) | |
| 476 { | |
| 477 ASSERT_UNUSED(timer, timer == &m_cancelTimer); | |
| 478 if (hasClients() || !m_loader) | |
| 479 return; | |
| 480 ResourcePtr<Resource> protect(this); | |
| 481 m_loader->cancelIfNotFinishing(); | |
| 482 if (m_status != Cached) | |
| 483 memoryCache()->remove(this); | |
| 484 } | |
| 485 | |
| 486 bool Resource::deleteIfPossible() | |
| 487 { | |
| 488 if (canDelete() && !memoryCache()->contains(this)) { | |
| 489 dispose(); | |
| 490 memoryCache()->unregisterLiveResource(*this); | |
| 491 #if !ENABLE(OILPAN) | |
| 492 delete this; | |
| 493 #endif | |
| 494 return true; | |
| 495 } | |
| 496 return false; | |
| 497 } | |
| 498 | |
| 499 void Resource::setDecodedSize(size_t decodedSize) | |
| 500 { | |
| 501 if (decodedSize == m_decodedSize) | |
| 502 return; | |
| 503 size_t oldSize = size(); | |
| 504 m_decodedSize = decodedSize; | |
| 505 memoryCache()->update(this, oldSize, size()); | |
| 506 memoryCache()->updateDecodedResource(this, UpdateForPropertyChange); | |
| 507 } | |
| 508 | |
| 509 void Resource::setEncodedSize(size_t encodedSize) | |
| 510 { | |
| 511 if (encodedSize == m_encodedSize) | |
| 512 return; | |
| 513 size_t oldSize = size(); | |
| 514 m_encodedSize = encodedSize; | |
| 515 memoryCache()->update(this, oldSize, size()); | |
| 516 } | |
| 517 | |
| 518 void Resource::didAccessDecodedData() | |
| 519 { | |
| 520 memoryCache()->updateDecodedResource(this, UpdateForAccess); | |
| 521 memoryCache()->prune(); | |
| 522 } | |
| 523 | |
| 524 void Resource::finishPendingClients() | |
| 525 { | |
| 526 // We're going to notify clients one by one. It is simple if the client does
nothing. | |
| 527 // However there are a couple other things that can happen. | |
| 528 // | |
| 529 // 1. Clients can be added during the loop. Make sure they are not processed
. | |
| 530 // 2. Clients can be removed during the loop. Make sure they are always avai
lable to be | |
| 531 // removed. Also don't call removed clients or add them back. | |
| 532 | |
| 533 // Handle case (1) by saving a list of clients to notify. A separate list al
so ensure | |
| 534 // a client is either in m_clients or m_clientsAwaitingCallback. | |
| 535 Vector<ResourceClient*> clientsToNotify; | |
| 536 copyToVector(m_clientsAwaitingCallback, clientsToNotify); | |
| 537 | |
| 538 for (size_t i = 0; i < clientsToNotify.size(); ++i) { | |
| 539 ResourceClient* client = clientsToNotify[i]; | |
| 540 | |
| 541 // Handle case (2) to skip removed clients. | |
| 542 if (!m_clientsAwaitingCallback.remove(client)) | |
| 543 continue; | |
| 544 m_clients.add(client); | |
| 545 didAddClient(client); | |
| 546 } | |
| 547 | |
| 548 // It is still possible for the above loop to finish a new client synchronou
sly. | |
| 549 // If there's no client waiting we should deschedule. | |
| 550 bool scheduled = ResourceCallback::callbackHandler()->isScheduled(this); | |
| 551 if (scheduled && m_clientsAwaitingCallback.isEmpty()) | |
| 552 ResourceCallback::callbackHandler()->cancel(this); | |
| 553 | |
| 554 // Prevent the case when there are clients waiting but no callback scheduled
. | |
| 555 ASSERT(m_clientsAwaitingCallback.isEmpty() || scheduled); | |
| 556 } | |
| 557 | |
| 558 void Resource::prune() | |
| 559 { | |
| 560 destroyDecodedDataIfPossible(); | |
| 561 unlock(); | |
| 562 } | |
| 563 | |
| 564 void Resource::setResourceToRevalidate(Resource* resource) | |
| 565 { | |
| 566 ASSERT(resource); | |
| 567 ASSERT(!m_resourceToRevalidate); | |
| 568 ASSERT(resource != this); | |
| 569 ASSERT(m_handlesToRevalidate.isEmpty()); | |
| 570 ASSERT(resource->type() == type()); | |
| 571 | |
| 572 WTF_LOG(ResourceLoading, "Resource %p setResourceToRevalidate %p", this, res
ource); | |
| 573 | |
| 574 // The following assert should be investigated whenever it occurs. Although
it should never fire, it currently does in rare circumstances. | |
| 575 // https://bugs.webkit.org/show_bug.cgi?id=28604. | |
| 576 // So the code needs to be robust to this assert failing thus the "if (m_res
ourceToRevalidate->m_proxyResource == this)" in Resource::clearResourceToRevalid
ate. | |
| 577 ASSERT(!resource->m_proxyResource); | |
| 578 | |
| 579 resource->m_proxyResource = this; | |
| 580 m_resourceToRevalidate = resource; | |
| 581 } | |
| 582 | |
| 583 void Resource::clearResourceToRevalidate() | |
| 584 { | |
| 585 ASSERT(m_resourceToRevalidate); | |
| 586 if (m_switchingClientsToRevalidatedResource) | |
| 587 return; | |
| 588 | |
| 589 // A resource may start revalidation before this method has been called, so
check that this resource is still the proxy resource before clearing it out. | |
| 590 if (m_resourceToRevalidate->m_proxyResource == this) { | |
| 591 m_resourceToRevalidate->m_proxyResource = nullptr; | |
| 592 m_resourceToRevalidate->deleteIfPossible(); | |
| 593 } | |
| 594 m_handlesToRevalidate.clear(); | |
| 595 m_resourceToRevalidate = nullptr; | |
| 596 deleteIfPossible(); | |
| 597 } | |
| 598 | |
| 599 void Resource::switchClientsToRevalidatedResource() | |
| 600 { | |
| 601 ASSERT(m_resourceToRevalidate); | |
| 602 ASSERT(memoryCache()->contains(m_resourceToRevalidate)); | |
| 603 ASSERT(!memoryCache()->contains(this)); | |
| 604 | |
| 605 WTF_LOG(ResourceLoading, "Resource %p switchClientsToRevalidatedResource %p"
, this, m_resourceToRevalidate.get()); | |
| 606 | |
| 607 m_resourceToRevalidate->m_identifier = m_identifier; | |
| 608 | |
| 609 m_switchingClientsToRevalidatedResource = true; | |
| 610 HashSet<ResourcePtrBase*>::iterator end = m_handlesToRevalidate.end(); | |
| 611 for (HashSet<ResourcePtrBase*>::iterator it = m_handlesToRevalidate.begin();
it != end; ++it) { | |
| 612 ResourcePtrBase* handle = *it; | |
| 613 handle->m_resource = m_resourceToRevalidate; | |
| 614 m_resourceToRevalidate->registerHandle(handle); | |
| 615 --m_handleCount; | |
| 616 } | |
| 617 ASSERT(!m_handleCount); | |
| 618 m_handlesToRevalidate.clear(); | |
| 619 | |
| 620 Vector<ResourceClient*> clientsToMove; | |
| 621 HashCountedSet<ResourceClient*>::iterator end2 = m_clients.end(); | |
| 622 for (HashCountedSet<ResourceClient*>::iterator it = m_clients.begin(); it !=
end2; ++it) { | |
| 623 ResourceClient* client = it->key; | |
| 624 unsigned count = it->value; | |
| 625 while (count) { | |
| 626 clientsToMove.append(client); | |
| 627 --count; | |
| 628 } | |
| 629 } | |
| 630 | |
| 631 unsigned moveCount = clientsToMove.size(); | |
| 632 for (unsigned n = 0; n < moveCount; ++n) | |
| 633 removeClient(clientsToMove[n]); | |
| 634 ASSERT(m_clients.isEmpty()); | |
| 635 | |
| 636 for (unsigned n = 0; n < moveCount; ++n) | |
| 637 m_resourceToRevalidate->addClientToSet(clientsToMove[n]); | |
| 638 for (unsigned n = 0; n < moveCount; ++n) { | |
| 639 // Calling didAddClient may do anything, including trying to cancel reva
lidation. | |
| 640 // Assert that it didn't succeed. | |
| 641 ASSERT(m_resourceToRevalidate); | |
| 642 // Calling didAddClient for a client may end up removing another client.
In that case it won't be in the set anymore. | |
| 643 if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) | |
| 644 m_resourceToRevalidate->didAddClient(clientsToMove[n]); | |
| 645 } | |
| 646 m_switchingClientsToRevalidatedResource = false; | |
| 647 } | |
| 648 | |
| 649 void Resource::updateResponseAfterRevalidation(const ResourceResponse& validatin
gResponse) | |
| 650 { | |
| 651 m_responseTimestamp = currentTime(); | |
| 652 | |
| 653 // RFC2616 10.3.5 | |
| 654 // Update cached headers from the 304 response | |
| 655 const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); | |
| 656 HTTPHeaderMap::const_iterator end = newHeaders.end(); | |
| 657 for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it)
{ | |
| 658 // Entity headers should not be sent by servers when generating a 304 | |
| 659 // response; misconfigured servers send them anyway. We shouldn't allow | |
| 660 // such headers to update the original request. We'll base this on the | |
| 661 // list defined by RFC2616 7.1, with a few additions for extension heade
rs | |
| 662 // we care about. | |
| 663 if (!shouldUpdateHeaderAfterRevalidation(it->key)) | |
| 664 continue; | |
| 665 m_response.setHTTPHeaderField(it->key, it->value); | |
| 666 } | |
| 667 } | |
| 668 | |
| 669 void Resource::revalidationSucceeded(const ResourceResponse& response) | |
| 670 { | |
| 671 ASSERT(m_resourceToRevalidate); | |
| 672 ASSERT(!memoryCache()->contains(m_resourceToRevalidate)); | |
| 673 ASSERT(m_resourceToRevalidate->isLoaded()); | |
| 674 | |
| 675 // Calling evict() can potentially delete revalidatingResource, which we use | |
| 676 // below. This mustn't be the case since revalidation means it is loaded | |
| 677 // and so canDelete() is false. | |
| 678 ASSERT(!canDelete()); | |
| 679 | |
| 680 m_resourceToRevalidate->updateResponseAfterRevalidation(response); | |
| 681 memoryCache()->replace(m_resourceToRevalidate, this); | |
| 682 | |
| 683 switchClientsToRevalidatedResource(); | |
| 684 assertAlive(); | |
| 685 // clearResourceToRevalidate deletes this. | |
| 686 clearResourceToRevalidate(); | |
| 687 } | |
| 688 | |
| 689 void Resource::revalidationFailed() | |
| 690 { | |
| 691 ASSERT(WTF::isMainThread()); | |
| 692 WTF_LOG(ResourceLoading, "Revalidation failed for %p", this); | |
| 693 ASSERT(resourceToRevalidate()); | |
| 694 clearResourceToRevalidate(); | |
| 695 } | |
| 696 | |
| 697 void Resource::registerHandle(ResourcePtrBase* h) | |
| 698 { | |
| 699 assertAlive(); | |
| 700 ++m_handleCount; | |
| 701 if (m_resourceToRevalidate) | |
| 702 m_handlesToRevalidate.add(h); | |
| 703 } | |
| 704 | |
| 705 void Resource::unregisterHandle(ResourcePtrBase* h) | |
| 706 { | |
| 707 assertAlive(); | |
| 708 ASSERT(m_handleCount > 0); | |
| 709 --m_handleCount; | |
| 710 | |
| 711 if (m_resourceToRevalidate) | |
| 712 m_handlesToRevalidate.remove(h); | |
| 713 | |
| 714 if (!m_handleCount) { | |
| 715 if (deleteIfPossible()) | |
| 716 return; | |
| 717 unlock(); | |
| 718 } else if (m_handleCount == 1 && memoryCache()->contains(this)) { | |
| 719 unlock(); | |
| 720 if (!hasClients()) | |
| 721 memoryCache()->prune(this); | |
| 722 } | |
| 723 } | |
| 724 | |
| 725 bool Resource::hasCacheControlNoStoreHeader() | |
| 726 { | |
| 727 return m_response.cacheControlContainsNoStore() || m_resourceRequest.cacheCo
ntrolContainsNoStore(); | |
| 728 } | |
| 729 | |
| 730 bool Resource::mustRevalidateDueToCacheHeaders() | |
| 731 { | |
| 732 return !canUseResponse(m_response, m_responseTimestamp) || m_resourceRequest
.cacheControlContainsNoCache() || m_resourceRequest.cacheControlContainsNoStore(
); | |
| 733 } | |
| 734 | |
| 735 bool Resource::canUseCacheValidator() | |
| 736 { | |
| 737 if (m_loading || errorOccurred()) | |
| 738 return false; | |
| 739 | |
| 740 if (hasCacheControlNoStoreHeader()) | |
| 741 return false; | |
| 742 return m_response.hasCacheValidatorFields() || m_resourceRequest.hasCacheVal
idatorFields(); | |
| 743 } | |
| 744 | |
| 745 bool Resource::isPurgeable() const | |
| 746 { | |
| 747 return m_data && !m_data->isLocked(); | |
| 748 } | |
| 749 | |
| 750 bool Resource::wasPurged() const | |
| 751 { | |
| 752 return m_wasPurged; | |
| 753 } | |
| 754 | |
| 755 bool Resource::lock() | |
| 756 { | |
| 757 if (!m_data) | |
| 758 return true; | |
| 759 if (m_data->isLocked()) | |
| 760 return true; | |
| 761 | |
| 762 ASSERT(!hasClients()); | |
| 763 | |
| 764 if (!m_data->lock()) { | |
| 765 m_wasPurged = true; | |
| 766 return false; | |
| 767 } | |
| 768 return true; | |
| 769 } | |
| 770 | |
| 771 size_t Resource::overheadSize() const | |
| 772 { | |
| 773 static const int kAverageClientsHashMapSize = 384; | |
| 774 return sizeof(Resource) + m_response.memoryUsage() + kAverageClientsHashMapS
ize + m_resourceRequest.url().string().length() * 2; | |
| 775 } | |
| 776 | |
| 777 void Resource::didChangePriority(ResourceLoadPriority loadPriority, int intraPri
orityValue) | |
| 778 { | |
| 779 if (m_loader) | |
| 780 m_loader->didChangePriority(loadPriority, intraPriorityValue); | |
| 781 } | |
| 782 | |
| 783 Resource::ResourceCallback* Resource::ResourceCallback::callbackHandler() | |
| 784 { | |
| 785 DEFINE_STATIC_LOCAL(ResourceCallback, callbackHandler, ()); | |
| 786 return &callbackHandler; | |
| 787 } | |
| 788 | |
| 789 Resource::ResourceCallback::ResourceCallback() | |
| 790 : m_callbackTimer(this, &ResourceCallback::timerFired) | |
| 791 { | |
| 792 } | |
| 793 | |
| 794 void Resource::ResourceCallback::schedule(Resource* resource) | |
| 795 { | |
| 796 if (!m_callbackTimer.isActive()) | |
| 797 m_callbackTimer.startOneShot(0, FROM_HERE); | |
| 798 resource->assertAlive(); | |
| 799 m_resourcesWithPendingClients.add(resource); | |
| 800 } | |
| 801 | |
| 802 void Resource::ResourceCallback::cancel(Resource* resource) | |
| 803 { | |
| 804 resource->assertAlive(); | |
| 805 m_resourcesWithPendingClients.remove(resource); | |
| 806 if (m_callbackTimer.isActive() && m_resourcesWithPendingClients.isEmpty()) | |
| 807 m_callbackTimer.stop(); | |
| 808 } | |
| 809 | |
| 810 bool Resource::ResourceCallback::isScheduled(Resource* resource) const | |
| 811 { | |
| 812 return m_resourcesWithPendingClients.contains(resource); | |
| 813 } | |
| 814 | |
| 815 void Resource::ResourceCallback::timerFired(Timer<ResourceCallback>*) | |
| 816 { | |
| 817 HashSet<Resource*>::iterator end = m_resourcesWithPendingClients.end(); | |
| 818 Vector<ResourcePtr<Resource> > resources; | |
| 819 for (HashSet<Resource*>::iterator it = m_resourcesWithPendingClients.begin()
; it != end; ++it) | |
| 820 resources.append(*it); | |
| 821 m_resourcesWithPendingClients.clear(); | |
| 822 | |
| 823 for (size_t i = 0; i < resources.size(); i++) { | |
| 824 resources[i]->assertAlive(); | |
| 825 resources[i]->finishPendingClients(); | |
| 826 resources[i]->assertAlive(); | |
| 827 } | |
| 828 | |
| 829 for (size_t i = 0; i < resources.size(); i++) | |
| 830 resources[i]->assertAlive(); | |
| 831 } | |
| 832 | |
| 833 static const char* initatorTypeNameToString(const AtomicString& initiatorTypeNam
e) | |
| 834 { | |
| 835 if (initiatorTypeName == FetchInitiatorTypeNames::css) | |
| 836 return "CSS resource"; | |
| 837 if (initiatorTypeName == FetchInitiatorTypeNames::document) | |
| 838 return "Document"; | |
| 839 if (initiatorTypeName == FetchInitiatorTypeNames::icon) | |
| 840 return "Icon"; | |
| 841 if (initiatorTypeName == FetchInitiatorTypeNames::internal) | |
| 842 return "Internal resource"; | |
| 843 if (initiatorTypeName == FetchInitiatorTypeNames::link) | |
| 844 return "Link element resource"; | |
| 845 if (initiatorTypeName == FetchInitiatorTypeNames::processinginstruction) | |
| 846 return "Processing instruction"; | |
| 847 if (initiatorTypeName == FetchInitiatorTypeNames::xml) | |
| 848 return "XML resource"; | |
| 849 return "Resource"; | |
| 850 } | |
| 851 | |
| 852 const char* Resource::resourceTypeToString(Type type, const FetchInitiatorInfo&
initiatorInfo) | |
| 853 { | |
| 854 switch (type) { | |
| 855 case Resource::MainResource: | |
| 856 return "Main resource"; | |
| 857 case Resource::Image: | |
| 858 return "Image"; | |
| 859 case Resource::Font: | |
| 860 return "Font"; | |
| 861 case Resource::Raw: | |
| 862 return initatorTypeNameToString(initiatorInfo.name); | |
| 863 case Resource::LinkPrefetch: | |
| 864 return "Link prefetch resource"; | |
| 865 case Resource::LinkSubresource: | |
| 866 return "Link subresource"; | |
| 867 case Resource::ImportResource: | |
| 868 return "Imported resource"; | |
| 869 case Resource::Media: | |
| 870 return "Media"; | |
| 871 } | |
| 872 ASSERT_NOT_REACHED(); | |
| 873 return initatorTypeNameToString(initiatorInfo.name); | |
| 874 } | |
| 875 | |
| 876 #if !LOG_DISABLED | |
| 877 const char* ResourceTypeName(Resource::Type type) | |
| 878 { | |
| 879 switch (type) { | |
| 880 case Resource::MainResource: | |
| 881 return "MainResource"; | |
| 882 case Resource::Image: | |
| 883 return "Image"; | |
| 884 case Resource::Font: | |
| 885 return "Font"; | |
| 886 case Resource::Raw: | |
| 887 return "Raw"; | |
| 888 case Resource::LinkPrefetch: | |
| 889 return "LinkPrefetch"; | |
| 890 case Resource::LinkSubresource: | |
| 891 return "LinkSubresource"; | |
| 892 case Resource::ImportResource: | |
| 893 return "ImportResource"; | |
| 894 case Resource::Media: | |
| 895 return "Media"; | |
| 896 } | |
| 897 ASSERT_NOT_REACHED(); | |
| 898 return "Unknown"; | |
| 899 } | |
| 900 #endif // !LOG_DISABLED | |
| 901 | |
| 902 } | |
| OLD | NEW |