// Verifies that CachedResources are evicted from the decode cache // according to their DecodeCachePriority. static void TestDecodeCacheOrder(const ResourcePtr<Resource>& cachedImageLowPriority, const ResourcePtr<Resource>& cachedImageHighPriority) { memoryCache()->setDelayBeforeLiveDecodedPrune(0); memoryCache()->setMaxPruneDeferralDelay(0); MockImageResourceClient clientLowPriority(cachedImageLowPriority); MockImageResourceClient clientHighPriority(cachedImageHighPriority); const char data[5] = "abcd"; cachedImageLowPriority->appendData(data, 1u); cachedImageHighPriority->appendData(data, 4u); const unsigned lowPrioritySize = cachedImageLowPriority->size(); const unsigned highPrioritySize = cachedImageHighPriority->size(); const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize(); const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize(); const unsigned totalSize = lowPrioritySize + highPrioritySize; // Verify that the sizes are different to ensure that we can test eviction order. ASSERT_GT(lowPrioritySize, 0u); ASSERT_NE(lowPrioritySize, highPrioritySize); ASSERT_GT(lowPriorityMockDecodeSize, 0u); ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), 0u); // Add the items. The item added first would normally be evicted first. memoryCache()->add(cachedImageHighPriority.get()); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize); memoryCache()->add(cachedImageLowPriority.get()); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize); // Insert all items in the decoded items list with the same priority memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange); memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize); // Now we will assign their priority and make sure they are moved to the correct buckets. memoryCache()->updateDecodedResource(cachedImageLowPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityLow); memoryCache()->updateDecodedResource(cachedImageHighPriority.get(), UpdateForPropertyChange, MemoryCacheLiveResourcePriorityHigh); // Should first prune the LowPriority item. memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); memoryCache()->prune(); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize); // Should prune the HighPriority item. memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); memoryCache()->prune(); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize); }
// Verified that when ordering a prune in a runLoop task, the prune // is deferred to the end of the task. TEST_F(MemoryCacheTest, LiveResourceEvictionAtEndOfTask) { memoryCache()->setDelayBeforeLiveDecodedPrune(0); const unsigned totalCapacity = 1; const unsigned minDeadCapacity = 0; const unsigned maxDeadCapacity = 0; memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); const char data[6] = "abcde"; ResourcePtr<Resource> cachedDeadResource = new Resource(ResourceRequest(""), Resource::Raw); cachedDeadResource->appendData(data, 3); ResourcePtr<Resource> cachedLiveResource = new FakeDecodedResource(ResourceRequest(""), Resource::Raw); MockImageResourceClient client; cachedLiveResource->addClient(&client); cachedLiveResource->appendData(data, 4); class Task1 : public WebKit::WebThread::Task { public: Task1(const ResourcePtr<Resource>& live, const ResourcePtr<Resource>& dead) : m_live(live) , m_dead(dead) { } virtual void run() OVERRIDE { // The resource size has to be nonzero for this test to be meaningful, but // we do not rely on it having any particular value. ASSERT_GT(m_live->size(), 0u); ASSERT_GT(m_dead->size(), 0u); ASSERT_EQ(0u, memoryCache()->deadSize()); ASSERT_EQ(0u, memoryCache()->liveSize()); memoryCache()->add(m_dead.get()); memoryCache()->add(m_live.get()); memoryCache()->insertInLiveDecodedResourcesList(m_live.get()); ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); ASSERT_GT(m_live->decodedSize(), 0u); memoryCache()->prune(); // Dead resources are pruned immediately ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); ASSERT_GT(m_live->decodedSize(), 0u); } private:
TEST(ImageResourceTest, DecodedDataRemainsWhileHasClients) { ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest(), nullptr); cachedImage->setLoading(true); MockImageResourceClient client(cachedImage); // Send the image response. cachedImage->responseReceived(ResourceResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom, String()), nullptr); Vector<unsigned char> jpeg = jpegImage(); cachedImage->responseReceived(ResourceResponse(KURL(), "image/jpeg", jpeg.size(), nullAtom, String()), nullptr); cachedImage->appendData(reinterpret_cast<const char*>(jpeg.data()), jpeg.size()); cachedImage->finish(); ASSERT_FALSE(cachedImage->errorOccurred()); ASSERT_TRUE(cachedImage->hasImage()); ASSERT_FALSE(cachedImage->image()->isNull()); ASSERT_TRUE(client.notifyFinishedCalled()); // The prune comes when the ImageResource still has clients. The image should not be deleted. cachedImage->prune(); ASSERT_TRUE(cachedImage->hasClients()); ASSERT_TRUE(cachedImage->hasImage()); ASSERT_FALSE(cachedImage->image()->isNull()); // The ImageResource no longer has clients. The image should be deleted by prune. client.removeAsClient(); cachedImage->prune(); ASSERT_FALSE(cachedImage->hasClients()); ASSERT_FALSE(cachedImage->hasImage()); ASSERT_TRUE(cachedImage->image()->isNull()); }
TEST(RawResourceTest, RevalidationSucceeded) { ResourcePtr<Resource> resource = new RawResource(ResourceRequest("data:text/html,"), Resource::Raw); ResourceResponse response; response.setHTTPStatusCode(200); resource->responseReceived(response, nullptr); const char data[5] = "abcd"; resource->appendData(data, 4); resource->finish(); memoryCache()->add(resource.get()); // Simulate a successful revalidation. resource->setRevalidatingRequest(ResourceRequest("data:text/html,")); OwnPtr<DummyClient> client = adoptPtr(new DummyClient); resource->addClient(client.get()); ResourceResponse revalidatingResponse; revalidatingResponse.setHTTPStatusCode(304); resource->responseReceived(revalidatingResponse, nullptr); EXPECT_FALSE(resource->isCacheValidator()); EXPECT_EQ(200, resource->response().httpStatusCode()); EXPECT_EQ(4u, resource->resourceBuffer()->size()); EXPECT_EQ(memoryCache()->resourceForURL(KURL(ParsedURLString, "data:text/html,")), resource.get()); memoryCache()->remove(resource.get()); resource->removeClient(client.get()); EXPECT_FALSE(resource->hasClients()); EXPECT_FALSE(client->called()); EXPECT_EQ("abcd", String(client->data().data(), client->data().size())); }
// Verifies that dead resources that exceed dead resource capacity are evicted // from cache when pruning. TEST_F(MemoryCacheTest, DeadResourceEviction) { const unsigned totalCapacity = 1000000; const unsigned minDeadCapacity = 0; const unsigned maxDeadCapacity = 0; memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); ResourcePtr<Resource> cachedResource = new Resource(ResourceRequest(""), Resource::Raw); const char data[5] = "abcd"; cachedResource->appendData(data, 3); // The resource size has to be nonzero for this test to be meaningful, but // we do not rely on it having any particular value. ASSERT_GT(cachedResource->size(), 0u); ASSERT_EQ(0u, memoryCache()->deadSize()); ASSERT_EQ(0u, memoryCache()->liveSize()); memoryCache()->add(cachedResource.get()); ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize()); ASSERT_EQ(0u, memoryCache()->liveSize()); memoryCache()->prune(); ASSERT_EQ(0u, memoryCache()->deadSize()); ASSERT_EQ(0u, memoryCache()->liveSize()); }
// Verifies that cached resources are evicted immediately after release when // the total dead resource size is more than double the dead resource capacity. static void TestClientRemoval(const ResourcePtr<Resource>& resource1, const ResourcePtr<Resource>& resource2) { const char data[6] = "abcde"; MockImageResourceClient client1(resource1); resource1->appendData(data, 4u); MockImageResourceClient client2(resource2); resource2->appendData(data, 4u); const unsigned minDeadCapacity = 0; const unsigned maxDeadCapacity = ((resource1->size() + resource2->size()) / 2) - 1; const unsigned totalCapacity = maxDeadCapacity; memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); memoryCache()->add(resource1.get()); memoryCache()->add(resource2.get()); // Call prune. There is nothing to prune, but this will initialize // the prune timestamp, allowing future prunes to be deferred. memoryCache()->prune(); ASSERT_GT(resource1->decodedSize(), 0u); ASSERT_GT(resource2->decodedSize(), 0u); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), resource1->size() + resource2->size()); // Removing the client from resource1 should result in all resources // remaining in cache since the prune is deferred. client1.removeAsClient(); ASSERT_GT(resource1->decodedSize(), 0u); ASSERT_GT(resource2->decodedSize(), 0u); ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); ASSERT_EQ(memoryCache()->liveSize(), resource2->size()); ASSERT_TRUE(memoryCache()->contains(resource1.get())); ASSERT_TRUE(memoryCache()->contains(resource2.get())); // Removing the client from resource2 should result in immediate // eviction of resource2 because we are over the prune deferral limit. client2.removeAsClient(); ASSERT_GT(resource1->decodedSize(), 0u); ASSERT_GT(resource2->decodedSize(), 0u); ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); ASSERT_EQ(memoryCache()->liveSize(), 0u); ASSERT_TRUE(memoryCache()->contains(resource1.get())); ASSERT_FALSE(memoryCache()->contains(resource2.get())); }
TEST(ImageResourceTest, MultipartImage) { ResourceFetcher* fetcher = ResourceFetcher::create(nullptr); KURL testURL(ParsedURLString, "http://www.test.com/cancelTest.html"); URLTestHelpers::registerMockedURLLoad(testURL, "cancelTest.html", "text/html"); // Emulate starting a real load, but don't expect any "real" WebURLLoaderClient callbacks. ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest(testURL), nullptr); cachedImage->setIdentifier(createUniqueIdentifier()); cachedImage->load(fetcher, ResourceLoaderOptions()); Platform::current()->unitTestSupport()->unregisterMockedURL(testURL); MockImageResourceClient client(cachedImage); EXPECT_EQ(Resource::Pending, cachedImage->status()); // Send the multipart response. No image or data buffer is created. // Note that the response must be routed through ResourceLoader to // ensure the load is flagged as multipart. ResourceResponse multipartResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom, String()); cachedImage->loader()->didReceiveResponse(nullptr, WrappedResourceResponse(multipartResponse), nullptr); ASSERT_FALSE(cachedImage->resourceBuffer()); ASSERT_FALSE(cachedImage->hasImage()); ASSERT_EQ(client.imageChangedCount(), 0); ASSERT_FALSE(client.notifyFinishedCalled()); // Send the response for the first real part. No image or data buffer is created. const char* svgData = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect width='1' height='1' fill='green'/></svg>"; unsigned svgDataLength = strlen(svgData); ResourceResponse payloadResponse(KURL(), "image/svg+xml", svgDataLength, nullAtom, String()); cachedImage->loader()->didReceiveResponse(nullptr, WrappedResourceResponse(payloadResponse), nullptr); ASSERT_FALSE(cachedImage->resourceBuffer()); ASSERT_FALSE(cachedImage->hasImage()); ASSERT_EQ(client.imageChangedCount(), 0); ASSERT_FALSE(client.notifyFinishedCalled()); // The first bytes arrive. The data buffer is created, but no image is created. cachedImage->appendData(svgData, svgDataLength); ASSERT_TRUE(cachedImage->resourceBuffer()); ASSERT_EQ(cachedImage->resourceBuffer()->size(), svgDataLength); ASSERT_FALSE(cachedImage->hasImage()); ASSERT_EQ(client.imageChangedCount(), 0); ASSERT_FALSE(client.notifyFinishedCalled()); // This part finishes. The image is created, callbacks are sent, and the data buffer is cleared. cachedImage->finish(); ASSERT_FALSE(cachedImage->resourceBuffer()); ASSERT_FALSE(cachedImage->errorOccurred()); ASSERT_TRUE(cachedImage->hasImage()); ASSERT_FALSE(cachedImage->image()->isNull()); ASSERT_EQ(cachedImage->image()->width(), 1); ASSERT_EQ(cachedImage->image()->height(), 1); ASSERT_EQ(client.imageChangedCount(), 2); ASSERT_TRUE(client.notifyFinishedCalled()); }
TEST(ImageResourceTest, UpdateBitmapImages) { ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest(), nullptr); cachedImage->setLoading(true); MockImageResourceClient client(cachedImage); // Send the image response. Vector<unsigned char> jpeg = jpegImage(); cachedImage->responseReceived(ResourceResponse(KURL(), "image/jpeg", jpeg.size(), nullAtom, String()), nullptr); cachedImage->appendData(reinterpret_cast<const char*>(jpeg.data()), jpeg.size()); cachedImage->finish(); ASSERT_FALSE(cachedImage->errorOccurred()); ASSERT_TRUE(cachedImage->hasImage()); ASSERT_FALSE(cachedImage->image()->isNull()); ASSERT_EQ(client.imageChangedCount(), 2); ASSERT_TRUE(client.notifyFinishedCalled()); ASSERT_TRUE(cachedImage->image()->isBitmapImage()); }
static void TestLiveResourceEvictionAtEndOfTask(Resource* cachedDeadResource, const ResourcePtr<Resource>& cachedLiveResource) { memoryCache()->setDelayBeforeLiveDecodedPrune(0); const unsigned totalCapacity = 1; const unsigned minDeadCapacity = 0; const unsigned maxDeadCapacity = 0; memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); const char data[6] = "abcde"; cachedDeadResource->appendData(data, 3u); MockImageResourceClient client(cachedLiveResource); cachedLiveResource->appendData(data, 4u); class Task1 : public WebTaskRunner::Task { public: Task1(const ResourcePtr<Resource>& live, Resource* dead) : m_live(live) , m_dead(dead) { } void run() override { // The resource size has to be nonzero for this test to be meaningful, but // we do not rely on it having any particular value. ASSERT_GT(m_live->size(), 0u); ASSERT_GT(m_dead->size(), 0u); ASSERT_EQ(0u, memoryCache()->deadSize()); ASSERT_EQ(0u, memoryCache()->liveSize()); memoryCache()->add(m_dead); memoryCache()->add(m_live.get()); memoryCache()->updateDecodedResource(m_live.get(), UpdateForPropertyChange); ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); ASSERT_GT(m_live->decodedSize(), 0u); memoryCache()->prune(); // Dead resources are pruned immediately ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); ASSERT_GT(m_live->decodedSize(), 0u); } private: ResourcePtr<Resource> m_live; Resource* m_dead; }; class Task2 : public WebTaskRunner::Task { public: Task2(unsigned liveSizeWithoutDecode) : m_liveSizeWithoutDecode(liveSizeWithoutDecode) { } void run() override { // Next task: now, the live resource was evicted. ASSERT_EQ(0u, memoryCache()->deadSize()); ASSERT_EQ(m_liveSizeWithoutDecode, memoryCache()->liveSize()); } private: unsigned m_liveSizeWithoutDecode; }; Platform::current()->currentThread()->taskRunner()->postTask(FROM_HERE, new Task1(cachedLiveResource, cachedDeadResource)); Platform::current()->currentThread()->taskRunner()->postTask(FROM_HERE, new Task2(cachedLiveResource->encodedSize() + cachedLiveResource->overheadSize())); testing::runPendingTasks(); }
// Verifies that CachedResources are evicted from the decode cache // according to their DecodeCachePriority. TEST_F(MemoryCacheTest, DecodeCacheOrder) { memoryCache()->setDelayBeforeLiveDecodedPrune(0); ResourcePtr<MockImageResource> cachedImageLowPriority = new MockImageResource(ResourceRequest(""), Resource::Raw); ResourcePtr<MockImageResource> cachedImageHighPriority = new MockImageResource(ResourceRequest(""), Resource::Raw); MockImageResourceClient clientLowPriority; MockImageResourceClient clientHighPriority; cachedImageLowPriority->addClient(&clientLowPriority); cachedImageHighPriority->addClient(&clientHighPriority); const char data[5] = "abcd"; cachedImageLowPriority->appendData(data, 1); cachedImageHighPriority->appendData(data, 4); const unsigned lowPrioritySize = cachedImageLowPriority->size(); const unsigned highPrioritySize = cachedImageHighPriority->size(); const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize(); const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize(); const unsigned totalSize = lowPrioritySize + highPrioritySize; // Verify that the sizes are different to ensure that we can test eviction order. ASSERT_GT(lowPrioritySize, 0u); ASSERT_NE(lowPrioritySize, highPrioritySize); ASSERT_GT(lowPriorityMockDecodeSize, 0u); ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), 0u); // Add the items. The item added first would normally be evicted first. memoryCache()->add(cachedImageHighPriority.get()); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize); memoryCache()->add(cachedImageLowPriority.get()); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize); // Insert all items in the decoded items list with the same priority memoryCache()->insertInLiveDecodedResourcesList(cachedImageHighPriority.get()); memoryCache()->insertInLiveDecodedResourcesList(cachedImageLowPriority.get()); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize); // Now we will assign their priority and make sure they are moved to the correct buckets. cachedImageLowPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow); cachedImageHighPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh); // Should first prune the LowPriority item. memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); memoryCache()->prune(); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize); // Should prune the HighPriority item. memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); memoryCache()->prune(); ASSERT_EQ(memoryCache()->deadSize(), 0u); ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize); }