// 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());
}
Beispiel #4
0
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);
}