Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(134)

Unified Diff: third_party/WebKit/Source/core/fetch/ImageResource.cpp

Issue 2710113002: DO NOT SUBMIT really old prototype of sprite recognition for image replacement
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/fetch/ImageResource.cpp
diff --git a/third_party/WebKit/Source/core/fetch/ImageResource.cpp b/third_party/WebKit/Source/core/fetch/ImageResource.cpp
index be3e5de6e7041ee64971b7cbd566affdb0d64bc1..c697eb3efb1384d4f8e67ff6db09dbb4904def71 100644
--- a/third_party/WebKit/Source/core/fetch/ImageResource.cpp
+++ b/third_party/WebKit/Source/core/fetch/ImageResource.cpp
@@ -29,10 +29,12 @@
#include "core/fetch/ResourceFetcher.h"
#include "core/fetch/ResourceLoader.h"
#include "core/fetch/ResourceLoadingLog.h"
+#include "core/fetch/SubstituteData.h"
#include "core/svg/graphics/SVGImage.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/SharedBuffer.h"
#include "platform/TraceEvent.h"
+#include "platform/geometry/IntSize.h"
#include "platform/graphics/BitmapImage.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCachePolicy.h"
@@ -40,12 +42,286 @@
#include "wtf/HashCountedSet.h"
#include "wtf/StdLibExtras.h"
#include "wtf/Vector.h"
+#include "wtf/text/StringToNumber.h"
+#include <cstdio>
#include <memory>
#include <v8.h>
namespace blink {
-ImageResource* ImageResource::fetch(FetchRequest& request, ResourceFetcher* fetcher)
+// Helper class that manages the state involved with attempting to load a
+// placeholder for an image.
+class ImageResource::PlaceholderLoaderJob : public GarbageCollectedFinalized<PlaceholderLoaderJob> {
+public:
+ explicit PlaceholderLoaderJob(ResourceFetcher*);
+ ~PlaceholderLoaderJob();
+
+ DECLARE_TRACE();
+
+ void onImageFetchComplete(ImageResource*);
+
+private:
+ enum class State {
+ Range = 0,
+ CacheOnly,
+ Placeholder,
+ BypassCacheFull
+ };
+
+ void finish(ImageResource* image)
+ {
+ DCHECK_EQ(image->m_placeholderLoaderJob.get(), this);
+ image->m_placeholderLoaderJob = nullptr;
+ }
+
+ void onRangeFetchComplete(ImageResource*);
+ void doCacheOnlyFetch(ImageResource*);
+ void onCacheOnlyFetchComplete(ImageResource*);
+ void loadPlaceholderOrBypassCache(ImageResource*);
+ void doPlaceholderFetch(ImageResource*, const IntSize&);
+ void doBypassCacheFullFetch(ImageResource*);
+
+ State m_state;
+ Member<ResourceFetcher> m_fetcher;
+ IntSize m_dimensionsFromRange;
+};
+
+ImageResource::PlaceholderLoaderJob::PlaceholderLoaderJob(ResourceFetcher* fetcher)
+ : m_state(State::Range)
+ , m_fetcher(fetcher)
+{
+ DCHECK(fetcher);
+}
+
+ImageResource::PlaceholderLoaderJob::~PlaceholderLoaderJob() {}
+
+DEFINE_TRACE(ImageResource::PlaceholderLoaderJob)
+{
+ visitor->trace(m_fetcher);
+}
+
+void ImageResource::PlaceholderLoaderJob::onImageFetchComplete(ImageResource* image)
+{
+ DCHECK(image);
+
+ switch (m_state) {
+ case State::Range:
+ onRangeFetchComplete(image);
+ break;
+ case State::CacheOnly:
+ onCacheOnlyFetchComplete(image);
+ break;
+ default:
+ finish(image);
+ break;
+ }
+}
+
+// Parses the values from a Content-Range response header of the form
+// "bytes %u-%u/%u". Returns true iff parsing is successful.
+static bool parseContentRangeHeader(const String& contentRange, uint64_t* rangeFirst, uint64_t* rangeLast, uint64_t* totalSize)
+{
+ // Example Content-Range header: "bytes 0-2047/16366".
+ if (!contentRange.startsWith("bytes "))
+ return false;
+ size_t dashPos = contentRange.find('-', arraysize("bytes ") - 1);
+ if (dashPos == kNotFound)
+ return false;
+ size_t slashPos = contentRange.find('/', dashPos + 1);
+ if (slashPos == kNotFound)
+ return false;
+
+ const size_t rangeFirstPos = arraysize("bytes ") - 1, rangeLastPos = dashPos + 1, totalSizePos = slashPos + 1;
+ bool rangeFirstOk = false, rangeLastOk = false, totalSizeOk = false;
+ if (contentRange.is8Bit()) {
+ *rangeFirst = charactersToUInt64Strict(contentRange.characters8() + rangeFirstPos, dashPos - rangeFirstPos, &rangeFirstOk);
+ *rangeLast = charactersToUInt64Strict(contentRange.characters8() + rangeLastPos, slashPos - rangeLastPos, &rangeLastOk);
+ *totalSize = charactersToUInt64Strict(contentRange.characters8() + totalSizePos, contentRange.length() - totalSizePos, &totalSizeOk);
+ } else {
+ *rangeFirst = charactersToUInt64Strict(contentRange.characters16() + rangeFirstPos, dashPos - rangeFirstPos, &rangeFirstOk);
+ *rangeLast = charactersToUInt64Strict(contentRange.characters16() + rangeLastPos, slashPos - rangeLastPos, &rangeLastOk);
+ *totalSize = charactersToUInt64Strict(contentRange.characters16() + totalSizePos, contentRange.length() - totalSizePos, &totalSizeOk);
+ }
+ return rangeFirstOk && rangeLastOk && totalSizeOk;
+}
+
+// Returns true if |response| likely represents the entire resource instead of
+// just a partial range.
+static bool hasEntireResource(const ResourceResponse& response)
+{
+ if (response.httpStatusCode() != 206)
+ return true;
+
+ const String& contentRangeHeader = response.httpHeaderField("content-range");
+ if (contentRangeHeader.isNull()) {
+ // If there's no Content-Range header, then assume that the response is
+ // the entire resource.
+ return true;
+ }
+
+ uint64_t rangeFirst, rangeLast, totalSize;
+ if (!parseContentRangeHeader(contentRangeHeader, &rangeFirst, &rangeLast, &totalSize))
+ return false;
+
+ // Check if the returned range is the entire resource.
+ return rangeFirst == 0 && rangeLast + 1 == totalSize;
+}
+
+void ImageResource::PlaceholderLoaderJob::onRangeFetchComplete(ImageResource* image)
+{
+ DCHECK_EQ(State::Range, m_state);
+
+ // If the response consists of the entire image, e.g. if the image was
+ // smaller than the requested range or if the server responded with a 200
+ // response for the full image, then just use the full response to show the
+ // full image.
+ // Also avoid re-fetching requests that led to 204 responses, since those
+ // typically indicate that it was for a tracking request, and there's likely
+ // no point in attempting to re-fetch the image.
+ if ((image->response().httpStatusCode() == 204 || !image->willPaintBrokenImage()) && hasEntireResource(image->response())) {
+ image->setIsPlaceholder(false);
+ finish(image);
+ return;
+ }
+
+ if (image->hasImage())
+ m_dimensionsFromRange = image->getImage()->size();
+
+ if (image->response().wasCached() && image->response().httpStatusCode() == 206 && image->resourceRequest().getCachePolicy() != WebCachePolicy::BypassingCache) {
+ // If the range response came from the cache, then that means that it
+ // was fresh in the cache and there's a chance that the entire image
+ // response is fresh and in the cache, so attempt to fetch the entire
+ // image from the cache.
+ doCacheOnlyFetch(image);
+ return;
+ }
+ loadPlaceholderOrBypassCache(image);
+}
+
+void ImageResource::PlaceholderLoaderJob::doCacheOnlyFetch(ImageResource* image)
+{
+ m_state = State::CacheOnly;
+
+ ResourceRequest request = image->getOriginalResourceRequest();
+ request.setCachePolicy(WebCachePolicy::ReturnCacheDataDontLoad);
+ image->reload(m_fetcher.get(), request, SubstituteData());
+}
+
+void ImageResource::PlaceholderLoaderJob::onCacheOnlyFetchComplete(ImageResource* image)
+{
+ DCHECK_EQ(State::CacheOnly, m_state);
+
+ if (!image->willPaintBrokenImage() && hasEntireResource(image->response())) {
+ image->setIsPlaceholder(false);
+ finish(image);
+ return;
+ }
+ loadPlaceholderOrBypassCache(image);
+}
+
+void ImageResource::PlaceholderLoaderJob::loadPlaceholderOrBypassCache(ImageResource* image)
+{
+ if (!m_dimensionsFromRange.isEmpty()) {
+ doPlaceholderFetch(image, m_dimensionsFromRange);
+ return;
+ }
+ if (image->getOriginalResourceRequest().getCachePolicy() != WebCachePolicy::ReturnCacheDataDontLoad) {
+ doBypassCacheFullFetch(image);
+ return;
+ }
+ // Return the broken image since there's nothing else that can be done here.
+ image->setIsPlaceholder(false);
+ finish(image);
+}
+
+static Vector<char> generatePlaceholderBox(const IntSize& dimensions)
+{
+ DCHECK(!dimensions.isEmpty());
+
+ static const char kFormat[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\">\n"
+ "<rect width=\"100%%\" height=\"100%%\" style=\"fill:rgba(127,127,127,0.4);stroke-width:%d;stroke:black\" />\n"
+ "</svg>";
+ // Don't add the black border to tiny images, to avoid coloring the entire image black.
+ int strokeWidth = dimensions.width() >= 10 && dimensions.height() >= 10 ? 2 : 0;
+
+ int expectedResult = std::snprintf(nullptr, 0, kFormat, dimensions.width(), dimensions.height(), strokeWidth);
+ DCHECK_LE(0, expectedResult);
+ Vector<char> out(safeCast<size_t>(expectedResult) + 1 /* for trailing '\0' */);
+ int result = std::snprintf(out.data(), out.size(), kFormat, dimensions.width(), dimensions.height(), strokeWidth);
+ DCHECK_EQ(expectedResult, result);
+ DCHECK_EQ('\0', out.last());
+ out.removeLast(); // Cut off the trailing '\0'.
+ return out;
+}
+
+void ImageResource::PlaceholderLoaderJob::doPlaceholderFetch(ImageResource* image, const IntSize& dimensions)
+{
+ DCHECK(!dimensions.isEmpty());
+ m_state = State::Placeholder;
+
+ Vector<char> svgBox = generatePlaceholderBox(dimensions);
+ SubstituteData substituteData(SharedBuffer::adoptVector(svgBox), "image/svg+xml", "utf-8", KURL(), LoadNormally);
+ DCHECK(image->isPlaceholder());
+ image->reload(m_fetcher.get(), image->getOriginalResourceRequest(), substituteData);
+}
+
+void ImageResource::PlaceholderLoaderJob::doBypassCacheFullFetch(ImageResource* image)
+{
+ m_state = State::BypassCacheFull;
+
+ // This reload is done bypassing the cache for several reasons:
+ //
+ // (1) CacheOnly fetches fail for sparse entries in the disk cache even if
+ // all the ranges of the full resource are available separately, so
+ // bypassing the cache here ensures that the full response is cached
+ // non-sparsely, so future fetches of the same image will be properly
+ // satisfied by the full cached response, reducing the cost of future
+ // fetches of this same image.
+ //
+ // (2) It's possible that the origin server doesn't understand ranges,
+ // and it returned a broken range, so bypassing the cache for this reload
+ // will hopefully return the full unbroken image instead of attempting to
+ // fetch the latter range of the image and combine that with the existing
+ // potentially broken range.
+ //
+ // TODO(sclittle): Investigate if the issue with (1) can be fixed, i.e. make
+ // the disk cache support sparse cache entries together with
+ // WebCachePolicy::ReturnCacheDataDontLoad.
+ ResourceRequest request = image->getOriginalResourceRequest();
+ request.setCachePolicy(WebCachePolicy::BypassingCache);
+ image->setIsPlaceholder(false);
+ image->reload(m_fetcher.get(), request, SubstituteData());
+}
+
+static void modifyRequestToFetchPlaceholderRange(ResourceRequest* request)
+{
+ DCHECK_EQ(nullAtom, request->httpHeaderField("range"));
+
+ // Fetch the first few bytes of the image. This number is tuned to both (a)
+ // likely capture the entire image for small images and (b) likely contain the
+ // dimensions for larger images.
+ // TODO(sclittle): Calculate the optimal value for this number.
+ request->setHTTPHeaderField("range", "bytes=0-2047");
+}
+
+static void unmodifyRequestToFetchPlaceholderRange(ResourceRequest* request)
+{
+ DCHECK_EQ("bytes=0-2047", request->httpHeaderField("range"));
+ request->clearHTTPHeaderField("range");
+}
+
+Resource* ImageResource::ImageResourceFactory::create(const ResourceRequest& request, const ResourceLoaderOptions& options, const String&) const
+{
+ if (m_isPlaceholder) {
+ ResourceRequest originalRequest = request;
+ unmodifyRequestToFetchPlaceholderRange(&originalRequest);
+ return new ImageResource(request, options, originalRequest, m_isPlaceholder);
+ }
+ return new ImageResource(request, options, request, m_isPlaceholder);
+}
+
+ImageResource* ImageResource::fetch(FetchRequest& request, ResourceFetcher* fetcher, PlaceholderRequestType placeholderRequestType)
{
if (request.resourceRequest().requestContext() == WebURLRequest::RequestContextUnspecified)
request.mutableResourceRequest().setRequestContext(WebURLRequest::RequestContextImage);
@@ -56,14 +332,42 @@ ImageResource* ImageResource::fetch(FetchRequest& request, ResourceFetcher* fetc
return nullptr;
}
- return toImageResource(fetcher->requestResource(request, ImageResourceFactory()));
+ bool attemptPlaceholder = placeholderRequestType == PlaceholderRequestType::AllowPlaceholder && request.url().protocolIsInHTTPFamily()
+ // Only issue range requests for GET requests, since that way it should
+ // be safe to re-issue the request without side effects.
+ && request.resourceRequest().httpMethod() == "GET"
+ // If the request already has a range request header, then don't
+ // overwrite that range header, and just attempt to fetch the image
+ // normally without generating a placeholder.
+ && request.resourceRequest().httpHeaderField("range").isNull();
+
+ if (attemptPlaceholder)
+ modifyRequestToFetchPlaceholderRange(&request.mutableResourceRequest());
+ ImageResource* image = toImageResource(fetcher->requestResource(request, ImageResourceFactory(attemptPlaceholder)));
+
+ // Check if the image is loading a placeholder already, otherwise add a PlaceholderLoaderJob.
+ if (attemptPlaceholder && (image->isLoading() || image->stillNeedsLoad()) && image->isPlaceholder() && !image->m_placeholderLoaderJob)
+ image->m_placeholderLoaderJob = new PlaceholderLoaderJob(fetcher);
+
+ // If this image shouldn't be a placeholder but it is, then reload it as not a placeholder.
+ if (!attemptPlaceholder && image->isPlaceholder()) {
+ image->m_placeholderLoaderJob = nullptr;
+ // Note that the cache is not bypassed for this reload - it should be fine
+ // to use a cached copy if it exists.
+ image->setIsPlaceholder(false);
+ image->reload(fetcher, request.resourceRequest(), SubstituteData());
+ }
+ return image;
}
-ImageResource::ImageResource(const ResourceRequest& resourceRequest, const ResourceLoaderOptions& options)
+ImageResource::ImageResource(const ResourceRequest& resourceRequest, const ResourceLoaderOptions& options, const ResourceRequest& originalRequest, bool isPlaceholder)
: Resource(resourceRequest, Image, options)
, m_devicePixelRatioHeaderValue(1.0)
, m_image(nullptr)
, m_hasDevicePixelRatioHeaderValue(false)
+ , m_originalRequest(originalRequest)
+ , m_isPlaceholder(isPlaceholder)
+ , m_isSchedulingReload(false)
{
RESOURCE_LOADING_DVLOG(1) << "new ImageResource(ResourceRequest) " << this;
}
@@ -73,6 +377,9 @@ ImageResource::ImageResource(blink::Image* image, const ResourceLoaderOptions& o
, m_devicePixelRatioHeaderValue(1.0)
, m_image(image)
, m_hasDevicePixelRatioHeaderValue(false)
+ , m_originalRequest(resourceRequest())
+ , m_isPlaceholder(false)
+ , m_isSchedulingReload(false)
{
RESOURCE_LOADING_DVLOG(1) << "new ImageResource(Image) " << this;
setStatus(Cached);
@@ -87,6 +394,7 @@ ImageResource::~ImageResource()
DEFINE_TRACE(ImageResource)
{
visitor->trace(m_multipartParser);
+ visitor->trace(m_placeholderLoaderJob);
Resource::trace(visitor);
ImageObserver::trace(visitor);
MultipartImageResourceParser::Client::trace(visitor);
@@ -94,6 +402,15 @@ DEFINE_TRACE(ImageResource)
void ImageResource::checkNotify()
{
+ if (m_isSchedulingReload)
+ return;
+
+ if (m_placeholderLoaderJob)
+ m_placeholderLoaderJob->onImageFetchComplete(this);
+
+ if (m_isSchedulingReload || m_placeholderLoaderJob)
+ return;
+
notifyObserversInternal(MarkFinishedOption::ShouldMarkFinished);
Resource::checkNotify();
}
@@ -114,6 +431,9 @@ void ImageResource::notifyObserversInternal(MarkFinishedOption markFinishedOptio
void ImageResource::markObserverFinished(ImageResourceObserver* observer)
{
+ if (m_isSchedulingReload || m_placeholderLoaderJob)
+ return;
+
if (m_observers.contains(observer)) {
m_finishedObservers.add(observer);
m_observers.remove(observer);
@@ -345,11 +665,19 @@ void ImageResource::clear()
inline void ImageResource::createImage()
{
+ bool wantSVGImage = response().mimeType() == "image/svg+xml";
+ // It's possible for the existing |m_image| to be a bitmap image while the
+ // response is now an SVG image, or vice versa, when an image is reloaded.
+ // In these cases, clear and delete the existing image so that a new image
+ // of the correct type can be created and used instead.
+ if (m_image && m_image->isSVGImage() != wantSVGImage)
+ clearImage();
+
// Create the image if it doesn't yet exist.
if (m_image)
return;
- if (response().mimeType() == "image/svg+xml") {
+ if (wantSVGImage) {
m_image = SVGImage::create(this);
} else {
m_image = BitmapImage::create(this);
@@ -399,7 +727,11 @@ void ImageResource::updateImage(bool allDataReceived)
setStatus(DecodeError);
if (!allDataReceived && loader())
loader()->didFinishLoading(nullptr, monotonicallyIncreasingTime(), size);
- memoryCache()->remove(this);
+ // It's possible that the error could have been resolved by the
+ // didFinishLoading() call, so check again that the image is still in an
+ // error state before proceeding.
+ if (errorOccurred())
+ memoryCache()->remove(this);
}
// It would be nice to only redraw the decoded band of the image, but with the current design
@@ -534,18 +866,62 @@ void ImageResource::updateImageAnimationPolicy()
void ImageResource::reloadIfLoFi(ResourceFetcher* fetcher)
{
- if (resourceRequest().loFiState() != WebURLRequest::LoFiOn)
+ if (!m_isPlaceholder && (resourceRequest().loFiState() != WebURLRequest::LoFiOn || (isLoaded() && !response().httpHeaderField("chrome-proxy").contains("q=low"))))
return;
- if (isLoaded() && !response().httpHeaderField("chrome-proxy").contains("q=low"))
- return;
- setCachePolicyBypassingCache();
- setLoFiStateOff();
+
+ ResourceRequest reloadRequest = m_originalRequest;
+ reloadRequest.setCachePolicy(WebCachePolicy::BypassingCache);
+ reloadRequest.setLoFiState(WebURLRequest::LoFiOff);
+ m_isPlaceholder = false;
+ m_placeholderLoaderJob = nullptr;
+
+ reload(fetcher, reloadRequest, SubstituteData());
+}
+
+static void loadStaticResponse(ImageResource* resource, const SubstituteData& substituteData)
+{
+ DCHECK(resource);
+ DCHECK(substituteData.isValid());
+
+ // This code was adapted from ResourceFetcher::resourceForStaticData().
+ ResourceResponse response(resource->resourceRequest().url(), substituteData.mimeType(), substituteData.content()->size(), substituteData.textEncoding(), String());
+ response.setHTTPStatusCode(200);
+ response.setHTTPStatusText("OK");
+
+ resource->setNeedsSynchronousCacheHit(substituteData.forceSynchronousLoad());
+ resource->responseReceived(response, nullptr);
+ resource->setDataBufferingPolicy(BufferData);
+ resource->setResourceBuffer(PassRefPtr<SharedBuffer>(substituteData.content()));
+ resource->finish();
+}
+
+void ImageResource::reload(ResourceFetcher* fetcher, const ResourceRequest& request, const SubstituteData& substituteData)
+{
+ DCHECK(!m_isSchedulingReload);
+ m_isSchedulingReload = true;
+
if (isLoading())
loader()->cancel();
+ clearError();
clear();
- notifyObservers();
+ if (!substituteData.isValid()) {
+ // If the reload will be done asynchronously, notify observers to update
+ // the image's appearance while waiting for the async reload to
+ // complete.
+ notifyObservers();
+ }
setStatus(NotStarted);
+
+ DCHECK(m_isSchedulingReload);
+ m_isSchedulingReload = false;
+
+ setResourceRequest(request);
+
+ if (substituteData.isValid()) {
+ loadStaticResponse(this, substituteData);
+ return;
+ }
fetcher->startLoad(this);
}
« no previous file with comments | « third_party/WebKit/Source/core/fetch/ImageResource.h ('k') | third_party/WebKit/Source/core/fetch/ImageResourceTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698