1 /* 2 * Copyright (c) 2013, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/fetch/ImageResource.h" 33 34 #include "core/fetch/ImageResourceClient.h" 35 #include "core/fetch/MemoryCache.h" 36 #include "core/fetch/MockImageResourceClient.h" 37 #include "core/fetch/ResourceFetcher.h" 38 #include "core/fetch/ResourcePtr.h" 39 #include "core/loader/DocumentLoader.h" 40 #include "core/loader/UniqueIdentifier.h" 41 #include "core/testing/DummyPageHolder.h" 42 #include "core/testing/URLTestHelpers.h" 43 #include "core/testing/UnitTestHelpers.h" 44 #include "platform/SharedBuffer.h" 45 #include "public/platform/Platform.h" 46 #include "public/platform/WebURL.h" 47 #include "public/platform/WebURLResponse.h" 48 #include "public/platform/WebUnitTestSupport.h" 49 50 using namespace blink; 51 52 namespace { 53 54 static Vector<unsigned char> jpegImage() 55 { 56 Vector<unsigned char> jpeg; 57 58 static const unsigned char data[] = { 59 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 60 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 61 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0xff, 0xdb, 0x00, 0x43, 62 0x00, 0x05, 0x03, 0x04, 0x04, 0x04, 0x03, 0x05, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 63 0x07, 0x0c, 0x08, 0x07, 0x07, 0x07, 0x07, 0x0f, 0x0b, 0x0b, 0x09, 0x0c, 0x11, 0x0f, 0x12, 64 0x12, 0x11, 0x0f, 0x11, 0x11, 0x13, 0x16, 0x1c, 0x17, 0x13, 0x14, 0x1a, 0x15, 0x11, 0x11, 65 0x18, 0x21, 0x18, 0x1a, 0x1d, 0x1d, 0x1f, 0x1f, 0x1f, 0x13, 0x17, 0x22, 0x24, 0x22, 0x1e, 66 0x24, 0x1c, 0x1e, 0x1f, 0x1e, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x05, 0x05, 0x05, 0x07, 0x06, 67 0x07, 0x0e, 0x08, 0x08, 0x0e, 0x1e, 0x14, 0x11, 0x14, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 68 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 69 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 70 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0xff, 71 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 72 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x15, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 73 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xc4, 0x00, 0x14, 74 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 77 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 79 0x00, 0xb2, 0xc0, 0x07, 0xff, 0xd9 80 }; 81 82 jpeg.append(data, sizeof(data)); 83 return jpeg; 84 } 85 86 TEST(ImageResourceTest, MultipartImage) 87 { 88 ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest()); 89 cachedImage->setLoading(true); 90 91 MockImageResourceClient client; 92 cachedImage->addClient(&client); 93 94 // Send the multipart response. No image or data buffer is created. 95 cachedImage->responseReceived(ResourceResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom, String())); 96 ASSERT_FALSE(cachedImage->resourceBuffer()); 97 ASSERT_FALSE(cachedImage->hasImage()); 98 ASSERT_EQ(client.imageChangedCount(), 0); 99 ASSERT_FALSE(client.notifyFinishedCalled()); 100 101 // Send the response for the first real part. No image or data buffer is created. 102 const char* svgData = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect width='1' height='1' fill='green'/></svg>"; 103 unsigned svgDataLength = strlen(svgData); 104 cachedImage->responseReceived(ResourceResponse(KURL(), "image/svg+xml", svgDataLength, nullAtom, String())); 105 ASSERT_FALSE(cachedImage->resourceBuffer()); 106 ASSERT_FALSE(cachedImage->hasImage()); 107 ASSERT_EQ(client.imageChangedCount(), 0); 108 ASSERT_FALSE(client.notifyFinishedCalled()); 109 110 // The first bytes arrive. The data buffer is created, but no image is created. 111 cachedImage->appendData(svgData, svgDataLength); 112 ASSERT_TRUE(cachedImage->resourceBuffer()); 113 ASSERT_EQ(cachedImage->resourceBuffer()->size(), svgDataLength); 114 ASSERT_FALSE(cachedImage->hasImage()); 115 ASSERT_EQ(client.imageChangedCount(), 0); 116 ASSERT_FALSE(client.notifyFinishedCalled()); 117 118 // This part finishes. The image is created, callbacks are sent, and the data buffer is cleared. 119 cachedImage->finish(); 120 ASSERT_FALSE(cachedImage->resourceBuffer()); 121 ASSERT_FALSE(cachedImage->errorOccurred()); 122 ASSERT_TRUE(cachedImage->hasImage()); 123 ASSERT_FALSE(cachedImage->image()->isNull()); 124 ASSERT_EQ(cachedImage->image()->width(), 1); 125 ASSERT_EQ(cachedImage->image()->height(), 1); 126 ASSERT_EQ(client.imageChangedCount(), 2); 127 ASSERT_TRUE(client.notifyFinishedCalled()); 128 } 129 130 TEST(ImageResourceTest, CancelOnDetach) 131 { 132 KURL testURL(ParsedURLString, "http://www.test.com/cancelTest.html"); 133 URLTestHelpers::registerMockedURLLoad(testURL, "cancelTest.html", "text/html"); 134 135 // Create enough of a mocked world to get a functioning ResourceLoader. 136 OwnPtr<DummyPageHolder> dummyPageHolder = DummyPageHolder::create(); 137 RefPtr<DocumentLoader> documentLoader = DocumentLoader::create(&dummyPageHolder->frame(), ResourceRequest(testURL), SubstituteData()); 138 139 // Emulate starting a real load. 140 ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest(testURL)); 141 cachedImage->setIdentifier(createUniqueIdentifier()); 142 143 cachedImage->load(documentLoader->fetcher(), ResourceLoaderOptions()); 144 memoryCache()->add(cachedImage.get()); 145 146 MockImageResourceClient client; 147 cachedImage->addClient(&client); 148 EXPECT_EQ(Resource::Pending, cachedImage->status()); 149 150 // The load should still be alive, but a timer should be started to cancel the load inside removeClient(). 151 cachedImage->removeClient(&client); 152 EXPECT_EQ(Resource::Pending, cachedImage->status()); 153 EXPECT_NE(reinterpret_cast<Resource*>(0), memoryCache()->resourceForURL(testURL)); 154 155 // Trigger the cancel timer, ensure the load was cancelled and the resource was evicted from the cache. 156 blink::testing::runPendingTasks(); 157 EXPECT_EQ(Resource::LoadError, cachedImage->status()); 158 EXPECT_EQ(reinterpret_cast<Resource*>(0), memoryCache()->resourceForURL(testURL)); 159 160 blink::Platform::current()->unitTestSupport()->unregisterMockedURL(testURL); 161 } 162 163 TEST(ImageResourceTest, DecodedDataRemainsWhileHasClients) 164 { 165 ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest()); 166 cachedImage->setLoading(true); 167 168 MockImageResourceClient client; 169 cachedImage->addClient(&client); 170 171 // Send the image response. 172 cachedImage->responseReceived(ResourceResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom, String())); 173 174 Vector<unsigned char> jpeg = jpegImage(); 175 cachedImage->responseReceived(ResourceResponse(KURL(), "image/jpeg", jpeg.size(), nullAtom, String())); 176 cachedImage->appendData(reinterpret_cast<const char*>(jpeg.data()), jpeg.size()); 177 cachedImage->finish(); 178 ASSERT_FALSE(cachedImage->errorOccurred()); 179 ASSERT_TRUE(cachedImage->hasImage()); 180 ASSERT_FALSE(cachedImage->image()->isNull()); 181 ASSERT_TRUE(client.notifyFinishedCalled()); 182 183 // The prune comes when the ImageResource still has clients. The image should not be deleted. 184 cachedImage->prune(); 185 ASSERT_TRUE(cachedImage->hasClients()); 186 ASSERT_TRUE(cachedImage->hasImage()); 187 ASSERT_FALSE(cachedImage->image()->isNull()); 188 189 // The ImageResource no longer has clients. The image should be deleted by prune. 190 cachedImage->removeClient(&client); 191 cachedImage->prune(); 192 ASSERT_FALSE(cachedImage->hasClients()); 193 ASSERT_FALSE(cachedImage->hasImage()); 194 ASSERT_TRUE(cachedImage->image()->isNull()); 195 } 196 197 TEST(ImageResourceTest, UpdateBitmapImages) 198 { 199 ResourcePtr<ImageResource> cachedImage = new ImageResource(ResourceRequest()); 200 cachedImage->setLoading(true); 201 202 MockImageResourceClient client; 203 cachedImage->addClient(&client); 204 205 // Send the image response. 206 cachedImage->responseReceived(ResourceResponse(KURL(), "multipart/x-mixed-replace", 0, nullAtom, String())); 207 208 Vector<unsigned char> jpeg = jpegImage(); 209 cachedImage->responseReceived(ResourceResponse(KURL(), "image/jpeg", jpeg.size(), nullAtom, String())); 210 cachedImage->appendData(reinterpret_cast<const char*>(jpeg.data()), jpeg.size()); 211 cachedImage->finish(); 212 ASSERT_FALSE(cachedImage->errorOccurred()); 213 ASSERT_TRUE(cachedImage->hasImage()); 214 ASSERT_FALSE(cachedImage->image()->isNull()); 215 ASSERT_EQ(client.imageChangedCount(), 1); 216 ASSERT_TRUE(client.notifyFinishedCalled()); 217 218 HashSet<ImageResource*> bitmapImages; 219 ASSERT_TRUE(cachedImage->image()->isBitmapImage()); 220 bitmapImages.add(cachedImage.get()); 221 222 // Updating bitmap resources produces image changed callbacks on their clients. 223 ImageResource::updateBitmapImages(bitmapImages); 224 ASSERT_EQ(client.imageChangedCount(), 2); 225 ImageResource::updateBitmapImages(bitmapImages, true); 226 ASSERT_EQ(client.imageChangedCount(), 3); 227 } 228 229 } // namespace 230