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 |