| 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 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 "core/fetch/ImageResource.h" | |
| 25 | |
| 26 #include "core/fetch/ImageResourceContent.h" | |
| 27 #include "core/fetch/ImageResourceInfo.h" | |
| 28 #include "core/fetch/MemoryCache.h" | |
| 29 #include "core/fetch/ResourceClient.h" | |
| 30 #include "core/fetch/ResourceFetcher.h" | |
| 31 #include "core/fetch/ResourceLoader.h" | |
| 32 #include "core/fetch/ResourceLoadingLog.h" | |
| 33 #include "platform/Histogram.h" | |
| 34 #include "platform/RuntimeEnabledFeatures.h" | |
| 35 #include "platform/SharedBuffer.h" | |
| 36 #include "platform/tracing/TraceEvent.h" | |
| 37 #include "public/platform/Platform.h" | |
| 38 #include "wtf/CurrentTime.h" | |
| 39 #include "wtf/StdLibExtras.h" | |
| 40 #include <memory> | |
| 41 #include <v8.h> | |
| 42 | |
| 43 namespace blink { | |
| 44 namespace { | |
| 45 // The amount of time to wait before informing the clients that the image has | |
| 46 // been updated (in seconds). This effectively throttles invalidations that | |
| 47 // result from new data arriving for this image. | |
| 48 constexpr double kFlushDelaySeconds = 1.; | |
| 49 } // namespace | |
| 50 | |
| 51 class ImageResource::ImageResourceInfoImpl final | |
| 52 : public GarbageCollectedFinalized<ImageResourceInfoImpl>, | |
| 53 public ImageResourceInfo { | |
| 54 USING_GARBAGE_COLLECTED_MIXIN(ImageResourceInfoImpl); | |
| 55 | |
| 56 public: | |
| 57 ImageResourceInfoImpl(ImageResource* resource) : m_resource(resource) { | |
| 58 DCHECK(m_resource); | |
| 59 } | |
| 60 DEFINE_INLINE_VIRTUAL_TRACE() { | |
| 61 visitor->trace(m_resource); | |
| 62 ImageResourceInfo::trace(visitor); | |
| 63 } | |
| 64 | |
| 65 private: | |
| 66 const KURL& url() const override { return m_resource->url(); } | |
| 67 bool isSchedulingReload() const override { | |
| 68 return m_resource->m_isSchedulingReload; | |
| 69 } | |
| 70 bool hasDevicePixelRatioHeaderValue() const override { | |
| 71 return m_resource->m_hasDevicePixelRatioHeaderValue; | |
| 72 } | |
| 73 float devicePixelRatioHeaderValue() const override { | |
| 74 return m_resource->m_devicePixelRatioHeaderValue; | |
| 75 } | |
| 76 const ResourceResponse& response() const override { | |
| 77 return m_resource->response(); | |
| 78 } | |
| 79 Resource::Status getStatus() const override { | |
| 80 return m_resource->getStatus(); | |
| 81 } | |
| 82 bool isPlaceholder() const override { return m_resource->isPlaceholder(); } | |
| 83 bool isCacheValidator() const override { | |
| 84 return m_resource->isCacheValidator(); | |
| 85 } | |
| 86 bool schedulingReloadOrShouldReloadBrokenPlaceholder() const override { | |
| 87 return m_resource->m_isSchedulingReload || | |
| 88 m_resource->shouldReloadBrokenPlaceholder(); | |
| 89 } | |
| 90 bool isAccessAllowed( | |
| 91 SecurityOrigin* securityOrigin, | |
| 92 DoesCurrentFrameHaveSingleSecurityOrigin | |
| 93 doesCurrentFrameHasSingleSecurityOrigin) const override { | |
| 94 return m_resource->isAccessAllowed(securityOrigin, | |
| 95 doesCurrentFrameHasSingleSecurityOrigin); | |
| 96 } | |
| 97 bool hasCacheControlNoStoreHeader() const override { | |
| 98 return m_resource->hasCacheControlNoStoreHeader(); | |
| 99 } | |
| 100 const ResourceError& resourceError() const override { | |
| 101 return m_resource->resourceError(); | |
| 102 } | |
| 103 | |
| 104 void decodeError(bool allDataReceived) override { | |
| 105 m_resource->decodeError(allDataReceived); | |
| 106 } | |
| 107 void setDecodedSize(size_t size) override { | |
| 108 m_resource->setDecodedSize(size); | |
| 109 } | |
| 110 void willAddClientOrObserver() override { | |
| 111 m_resource->willAddClientOrObserver(Resource::MarkAsReferenced); | |
| 112 } | |
| 113 void didRemoveClientOrObserver() override { | |
| 114 m_resource->didRemoveClientOrObserver(); | |
| 115 } | |
| 116 void emulateLoadStartedForInspector( | |
| 117 ResourceFetcher* fetcher, | |
| 118 const KURL& url, | |
| 119 const AtomicString& initiatorName) override { | |
| 120 fetcher->emulateLoadStartedForInspector(m_resource.get(), url, | |
| 121 WebURLRequest::RequestContextImage, | |
| 122 initiatorName); | |
| 123 } | |
| 124 | |
| 125 const Member<ImageResource> m_resource; | |
| 126 }; | |
| 127 | |
| 128 class ImageResource::ImageResourceFactory : public ResourceFactory { | |
| 129 STACK_ALLOCATED(); | |
| 130 | |
| 131 public: | |
| 132 ImageResourceFactory(const FetchRequest& fetchRequest) | |
| 133 : ResourceFactory(Resource::Image), m_fetchRequest(&fetchRequest) {} | |
| 134 | |
| 135 Resource* create(const ResourceRequest& request, | |
| 136 const ResourceLoaderOptions& options, | |
| 137 const String&) const override { | |
| 138 return new ImageResource(request, options, ImageResourceContent::create(), | |
| 139 m_fetchRequest->placeholderImageRequestType() == | |
| 140 FetchRequest::AllowPlaceholder); | |
| 141 } | |
| 142 | |
| 143 private: | |
| 144 // Weak, unowned pointer. Must outlive |this|. | |
| 145 const FetchRequest* m_fetchRequest; | |
| 146 }; | |
| 147 | |
| 148 ImageResource* ImageResource::fetch(FetchRequest& request, | |
| 149 ResourceFetcher* fetcher) { | |
| 150 if (request.resourceRequest().requestContext() == | |
| 151 WebURLRequest::RequestContextUnspecified) { | |
| 152 request.mutableResourceRequest().setRequestContext( | |
| 153 WebURLRequest::RequestContextImage); | |
| 154 } | |
| 155 if (fetcher->context().pageDismissalEventBeingDispatched()) { | |
| 156 KURL requestURL = request.resourceRequest().url(); | |
| 157 if (requestURL.isValid()) { | |
| 158 ResourceRequestBlockedReason blockReason = fetcher->context().canRequest( | |
| 159 Resource::Image, request.resourceRequest(), requestURL, | |
| 160 request.options(), request.forPreload(), | |
| 161 request.getOriginRestriction()); | |
| 162 if (blockReason == ResourceRequestBlockedReason::None) | |
| 163 fetcher->context().sendImagePing(requestURL); | |
| 164 } | |
| 165 return nullptr; | |
| 166 } | |
| 167 | |
| 168 ImageResource* resource = toImageResource( | |
| 169 fetcher->requestResource(request, ImageResourceFactory(request))); | |
| 170 if (resource && | |
| 171 request.placeholderImageRequestType() != FetchRequest::AllowPlaceholder && | |
| 172 resource->m_isPlaceholder) { | |
| 173 // If the image is a placeholder, but this fetch doesn't allow a | |
| 174 // placeholder, then load the original image. Note that the cache is not | |
| 175 // bypassed here - it should be fine to use a cached copy if possible. | |
| 176 resource->reloadIfLoFiOrPlaceholderImage( | |
| 177 fetcher, kReloadAlwaysWithExistingCachePolicy); | |
| 178 } | |
| 179 return resource; | |
| 180 } | |
| 181 | |
| 182 ImageResource* ImageResource::create(const ResourceRequest& request) { | |
| 183 return new ImageResource(request, ResourceLoaderOptions(), | |
| 184 ImageResourceContent::create(), false); | |
| 185 } | |
| 186 | |
| 187 ImageResource::ImageResource(const ResourceRequest& resourceRequest, | |
| 188 const ResourceLoaderOptions& options, | |
| 189 ImageResourceContent* content, | |
| 190 bool isPlaceholder) | |
| 191 : Resource(resourceRequest, Image, options), | |
| 192 m_content(content), | |
| 193 m_devicePixelRatioHeaderValue(1.0), | |
| 194 m_hasDevicePixelRatioHeaderValue(false), | |
| 195 m_isSchedulingReload(false), | |
| 196 m_isPlaceholder(isPlaceholder), | |
| 197 m_flushTimer(this, &ImageResource::flushImageIfNeeded) { | |
| 198 DCHECK(getContent()); | |
| 199 RESOURCE_LOADING_DVLOG(1) << "new ImageResource(ResourceRequest) " << this; | |
| 200 getContent()->setImageResourceInfo(new ImageResourceInfoImpl(this)); | |
| 201 } | |
| 202 | |
| 203 ImageResource::~ImageResource() { | |
| 204 RESOURCE_LOADING_DVLOG(1) << "~ImageResource " << this; | |
| 205 } | |
| 206 | |
| 207 DEFINE_TRACE(ImageResource) { | |
| 208 visitor->trace(m_multipartParser); | |
| 209 visitor->trace(m_content); | |
| 210 Resource::trace(visitor); | |
| 211 MultipartImageResourceParser::Client::trace(visitor); | |
| 212 } | |
| 213 | |
| 214 void ImageResource::checkNotify() { | |
| 215 // Don't notify clients of completion if this ImageResource is | |
| 216 // about to be reloaded. | |
| 217 if (m_isSchedulingReload || shouldReloadBrokenPlaceholder()) | |
| 218 return; | |
| 219 | |
| 220 Resource::checkNotify(); | |
| 221 } | |
| 222 | |
| 223 bool ImageResource::hasClientsOrObservers() const { | |
| 224 return Resource::hasClientsOrObservers() || getContent()->hasObservers(); | |
| 225 } | |
| 226 | |
| 227 void ImageResource::didAddClient(ResourceClient* client) { | |
| 228 DCHECK((m_multipartParser && isLoading()) || !data() || | |
| 229 getContent()->hasImage()); | |
| 230 | |
| 231 // Don't notify observers and clients of completion if this ImageResource is | |
| 232 // about to be reloaded. | |
| 233 if (m_isSchedulingReload || shouldReloadBrokenPlaceholder()) | |
| 234 return; | |
| 235 | |
| 236 Resource::didAddClient(client); | |
| 237 } | |
| 238 | |
| 239 void ImageResource::destroyDecodedDataForFailedRevalidation() { | |
| 240 getContent()->clearImage(); | |
| 241 setDecodedSize(0); | |
| 242 } | |
| 243 | |
| 244 void ImageResource::destroyDecodedDataIfPossible() { | |
| 245 getContent()->destroyDecodedData(); | |
| 246 if (getContent()->hasImage() && !isPreloaded() && | |
| 247 getContent()->isRefetchableDataFromDiskCache()) { | |
| 248 UMA_HISTOGRAM_MEMORY_KB("Memory.Renderer.EstimatedDroppableEncodedSize", | |
| 249 encodedSize() / 1024); | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 void ImageResource::allClientsAndObserversRemoved() { | |
| 254 CHECK(!getContent()->hasImage() || !errorOccurred()); | |
| 255 // If possible, delay the resetting until back at the event loop. Doing so | |
| 256 // after a conservative GC prevents resetAnimation() from upsetting ongoing | |
| 257 // animation updates (crbug.com/613709) | |
| 258 if (!ThreadHeap::willObjectBeLazilySwept(this)) { | |
| 259 Platform::current()->currentThread()->getWebTaskRunner()->postTask( | |
| 260 BLINK_FROM_HERE, WTF::bind(&ImageResourceContent::doResetAnimation, | |
| 261 wrapWeakPersistent(getContent()))); | |
| 262 } else { | |
| 263 getContent()->doResetAnimation(); | |
| 264 } | |
| 265 if (m_multipartParser) | |
| 266 m_multipartParser->cancel(); | |
| 267 Resource::allClientsAndObserversRemoved(); | |
| 268 } | |
| 269 | |
| 270 PassRefPtr<const SharedBuffer> ImageResource::resourceBuffer() const { | |
| 271 if (data()) | |
| 272 return data(); | |
| 273 return getContent()->resourceBuffer(); | |
| 274 } | |
| 275 | |
| 276 void ImageResource::appendData(const char* data, size_t length) { | |
| 277 v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(length); | |
| 278 if (m_multipartParser) { | |
| 279 m_multipartParser->appendData(data, length); | |
| 280 } else { | |
| 281 Resource::appendData(data, length); | |
| 282 | |
| 283 // Update the image immediately if needed. | |
| 284 if (getContent()->shouldUpdateImageImmediately()) { | |
| 285 getContent()->updateImage(this->data(), | |
| 286 ImageResourceContent::KeepExistingImage, false); | |
| 287 return; | |
| 288 } | |
| 289 | |
| 290 // For other cases, only update at |kFlushDelaySeconds| intervals. This | |
| 291 // throttles how frequently we update |m_image| and how frequently we | |
| 292 // inform the clients which causes an invalidation of this image. In other | |
| 293 // words, we only invalidate this image every |kFlushDelaySeconds| seconds | |
| 294 // while loading. | |
| 295 if (!m_flushTimer.isActive()) { | |
| 296 double now = WTF::monotonicallyIncreasingTime(); | |
| 297 if (!m_lastFlushTime) | |
| 298 m_lastFlushTime = now; | |
| 299 | |
| 300 DCHECK_LE(m_lastFlushTime, now); | |
| 301 double flushDelay = m_lastFlushTime - now + kFlushDelaySeconds; | |
| 302 if (flushDelay < 0.) | |
| 303 flushDelay = 0.; | |
| 304 m_flushTimer.startOneShot(flushDelay, BLINK_FROM_HERE); | |
| 305 } | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 void ImageResource::flushImageIfNeeded(TimerBase*) { | |
| 310 // We might have already loaded the image fully, in which case we don't need | |
| 311 // to call |updateImage()|. | |
| 312 if (isLoading()) { | |
| 313 m_lastFlushTime = WTF::monotonicallyIncreasingTime(); | |
| 314 getContent()->updateImage(this->data(), | |
| 315 ImageResourceContent::KeepExistingImage, false); | |
| 316 } | |
| 317 } | |
| 318 | |
| 319 bool ImageResource::willPaintBrokenImage() const { | |
| 320 return errorOccurred(); | |
| 321 } | |
| 322 | |
| 323 void ImageResource::decodeError(bool allDataReceived) { | |
| 324 size_t size = encodedSize(); | |
| 325 | |
| 326 clearData(); | |
| 327 setEncodedSize(0); | |
| 328 if (!errorOccurred()) | |
| 329 setStatus(DecodeError); | |
| 330 | |
| 331 if (!allDataReceived && loader()) { | |
| 332 // TODO(hiroshige): Do not call didFinishLoading() directly. | |
| 333 loader()->didFinishLoading(monotonicallyIncreasingTime(), size, size); | |
| 334 } | |
| 335 | |
| 336 memoryCache()->remove(this); | |
| 337 } | |
| 338 | |
| 339 void ImageResource::updateImageAndClearBuffer() { | |
| 340 getContent()->updateImage(data(), ImageResourceContent::ClearExistingImage, | |
| 341 true); | |
| 342 clearData(); | |
| 343 } | |
| 344 | |
| 345 void ImageResource::finish(double loadFinishTime) { | |
| 346 if (m_multipartParser) { | |
| 347 m_multipartParser->finish(); | |
| 348 if (data()) | |
| 349 updateImageAndClearBuffer(); | |
| 350 } else { | |
| 351 getContent()->updateImage(data(), ImageResourceContent::KeepExistingImage, | |
| 352 true); | |
| 353 // As encoded image data can be created from m_image (see | |
| 354 // ImageResource::resourceBuffer(), we don't have to keep m_data. Let's | |
| 355 // clear this. As for the lifetimes of m_image and m_data, see this | |
| 356 // document: | |
| 357 // https://docs.google.com/document/d/1v0yTAZ6wkqX2U_M6BNIGUJpM1s0TIw1Vsqpxo
L7aciY/edit?usp=sharing | |
| 358 clearData(); | |
| 359 } | |
| 360 Resource::finish(loadFinishTime); | |
| 361 } | |
| 362 | |
| 363 void ImageResource::error(const ResourceError& error) { | |
| 364 if (m_multipartParser) | |
| 365 m_multipartParser->cancel(); | |
| 366 // TODO(hiroshige): Move setEncodedSize() call to Resource::error() if it | |
| 367 // is really needed, or remove it otherwise. | |
| 368 setEncodedSize(0); | |
| 369 Resource::error(error); | |
| 370 getContent()->clearImageAndNotifyObservers( | |
| 371 ImageResourceContent::ShouldNotifyFinish); | |
| 372 } | |
| 373 | |
| 374 void ImageResource::responseReceived( | |
| 375 const ResourceResponse& response, | |
| 376 std::unique_ptr<WebDataConsumerHandle> handle) { | |
| 377 DCHECK(!handle); | |
| 378 DCHECK(!m_multipartParser); | |
| 379 // If there's no boundary, just handle the request normally. | |
| 380 if (response.isMultipart() && !response.multipartBoundary().isEmpty()) { | |
| 381 m_multipartParser = new MultipartImageResourceParser( | |
| 382 response, response.multipartBoundary(), this); | |
| 383 } | |
| 384 Resource::responseReceived(response, std::move(handle)); | |
| 385 if (RuntimeEnabledFeatures::clientHintsEnabled()) { | |
| 386 m_devicePixelRatioHeaderValue = | |
| 387 this->response() | |
| 388 .httpHeaderField(HTTPNames::Content_DPR) | |
| 389 .toFloat(&m_hasDevicePixelRatioHeaderValue); | |
| 390 if (!m_hasDevicePixelRatioHeaderValue || | |
| 391 m_devicePixelRatioHeaderValue <= 0.0) { | |
| 392 m_devicePixelRatioHeaderValue = 1.0; | |
| 393 m_hasDevicePixelRatioHeaderValue = false; | |
| 394 } | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 static bool isLoFiImage(const ImageResource& resource) { | |
| 399 if (resource.resourceRequest().loFiState() != WebURLRequest::LoFiOn) | |
| 400 return false; | |
| 401 return !resource.isLoaded() || | |
| 402 resource.response() | |
| 403 .httpHeaderField("chrome-proxy-content-transform") | |
| 404 .contains("empty-image"); | |
| 405 } | |
| 406 | |
| 407 void ImageResource::reloadIfLoFiOrPlaceholderImage( | |
| 408 ResourceFetcher* fetcher, | |
| 409 ReloadLoFiOrPlaceholderPolicy policy) { | |
| 410 if (policy == kReloadIfNeeded && !shouldReloadBrokenPlaceholder()) | |
| 411 return; | |
| 412 | |
| 413 if (!m_isPlaceholder && !isLoFiImage(*this)) | |
| 414 return; | |
| 415 | |
| 416 // Prevent clients and observers from being notified of completion while the | |
| 417 // reload is being scheduled, so that e.g. canceling an existing load in | |
| 418 // progress doesn't cause clients and observers to be notified of completion | |
| 419 // prematurely. | |
| 420 DCHECK(!m_isSchedulingReload); | |
| 421 m_isSchedulingReload = true; | |
| 422 | |
| 423 if (policy != kReloadAlwaysWithExistingCachePolicy) | |
| 424 setCachePolicyBypassingCache(); | |
| 425 setLoFiStateOff(); | |
| 426 | |
| 427 if (m_isPlaceholder) { | |
| 428 m_isPlaceholder = false; | |
| 429 clearRangeRequestHeader(); | |
| 430 } | |
| 431 | |
| 432 if (isLoading()) { | |
| 433 loader()->cancel(); | |
| 434 // Canceling the loader causes error() to be called, which in turn calls | |
| 435 // clear() and notifyObservers(), so there's no need to call these again | |
| 436 // here. | |
| 437 } else { | |
| 438 clearData(); | |
| 439 setEncodedSize(0); | |
| 440 getContent()->clearImageAndNotifyObservers( | |
| 441 ImageResourceContent::DoNotNotifyFinish); | |
| 442 } | |
| 443 | |
| 444 setStatus(NotStarted); | |
| 445 | |
| 446 DCHECK(m_isSchedulingReload); | |
| 447 m_isSchedulingReload = false; | |
| 448 | |
| 449 fetcher->startLoad(this); | |
| 450 } | |
| 451 | |
| 452 void ImageResource::onePartInMultipartReceived( | |
| 453 const ResourceResponse& response) { | |
| 454 DCHECK(m_multipartParser); | |
| 455 | |
| 456 setResponse(response); | |
| 457 if (m_multipartParsingState == MultipartParsingState::WaitingForFirstPart) { | |
| 458 // We have nothing to do because we don't have any data. | |
| 459 m_multipartParsingState = MultipartParsingState::ParsingFirstPart; | |
| 460 return; | |
| 461 } | |
| 462 updateImageAndClearBuffer(); | |
| 463 | |
| 464 if (m_multipartParsingState == MultipartParsingState::ParsingFirstPart) { | |
| 465 m_multipartParsingState = MultipartParsingState::FinishedParsingFirstPart; | |
| 466 // Notify finished when the first part ends. | |
| 467 if (!errorOccurred()) | |
| 468 setStatus(Cached); | |
| 469 // We notify clients and observers of finish in checkNotify() and | |
| 470 // updateImageAndClearBuffer(), respectively, and they will not be | |
| 471 // notified again in Resource::finish()/error(). | |
| 472 checkNotify(); | |
| 473 if (loader()) | |
| 474 loader()->didFinishLoadingFirstPartInMultipart(); | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 void ImageResource::multipartDataReceived(const char* bytes, size_t size) { | |
| 479 DCHECK(m_multipartParser); | |
| 480 Resource::appendData(bytes, size); | |
| 481 } | |
| 482 | |
| 483 bool ImageResource::isAccessAllowed( | |
| 484 SecurityOrigin* securityOrigin, | |
| 485 ImageResourceInfo::DoesCurrentFrameHaveSingleSecurityOrigin | |
| 486 doesCurrentFrameHasSingleSecurityOrigin) const { | |
| 487 if (response().wasFetchedViaServiceWorker()) { | |
| 488 return response().serviceWorkerResponseType() != | |
| 489 WebServiceWorkerResponseTypeOpaque; | |
| 490 } | |
| 491 if (doesCurrentFrameHasSingleSecurityOrigin != | |
| 492 ImageResourceInfo::HasSingleSecurityOrigin) | |
| 493 return false; | |
| 494 if (passesAccessControlCheck(securityOrigin)) | |
| 495 return true; | |
| 496 return !securityOrigin->taintsCanvas(response().url()); | |
| 497 } | |
| 498 | |
| 499 ImageResourceContent* ImageResource::getContent() { | |
| 500 return m_content; | |
| 501 } | |
| 502 | |
| 503 const ImageResourceContent* ImageResource::getContent() const { | |
| 504 return m_content; | |
| 505 } | |
| 506 | |
| 507 ResourcePriority ImageResource::priorityFromObservers() { | |
| 508 return getContent()->priorityFromObservers(); | |
| 509 } | |
| 510 | |
| 511 } // namespace blink | |
| OLD | NEW |