text: /*
   * Copyright (c) 2013, Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "third_party/blink/renderer/core/loader/resource/image_resource.h"
  
  #include <memory>
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/blink/public/common/loader/referrer_utils.h"
  #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
  #include "third_party/blink/public/platform/platform.h"
  #include "third_party/blink/public/platform/web_url.h"
  #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
  #include "third_party/blink/public/platform/web_url_response.h"
  #include "third_party/blink/renderer/core/frame/local_dom_window.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/loader/empty_clients.h"
  #include "third_party/blink/renderer/core/loader/resource/mock_image_resource_observer.h"
  #include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
  #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
  #include "third_party/blink/renderer/core/testing/scoped_mock_overlay_scrollbars.h"
  #include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
  #include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
  #include "third_party/blink/renderer/platform/graphics/image.h"
  #include "third_party/blink/renderer/platform/heap/heap.h"
  #include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
  #include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
  #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_info.h"
  #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
  #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
  #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
  #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
  #include "third_party/blink/renderer/platform/loader/fetch/resource_finish_observer.h"
  #include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h"
  #include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
  #include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
  #include "third_party/blink/renderer/platform/loader/testing/mock_resource_client.h"
  #include "third_party/blink/renderer/platform/loader/testing/test_loader_factory.h"
  #include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
  #include "third_party/blink/renderer/platform/network/http_names.h"
  #include "third_party/blink/renderer/platform/scheduler/test/fake_frame_scheduler.h"
  #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
  #include "third_party/blink/renderer/platform/testing/mock_context_lifecycle_notifier.h"
  #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
  #include "third_party/blink/renderer/platform/testing/scoped_mocked_url.h"
  #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
  #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
  #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
  #include "third_party/blink/renderer/platform/wtf/text/base64.h"
  
  namespace blink {
  
  using test::ScopedMockedURLLoad;
  
  namespace {
  
  // An image of size 1x1.
  constexpr unsigned char kJpegImage[] = {
      0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
      0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13,
      0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68,
      0x20, 0x47, 0x49, 0x4d, 0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x05, 0x03,
      0x04, 0x04, 0x04, 0x03, 0x05, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06,
      0x07, 0x0c, 0x08, 0x07, 0x07, 0x07, 0x07, 0x0f, 0x0b, 0x0b, 0x09, 0x0c,
      0x11, 0x0f, 0x12, 0x12, 0x11, 0x0f, 0x11, 0x11, 0x13, 0x16, 0x1c, 0x17,
      0x13, 0x14, 0x1a, 0x15, 0x11, 0x11, 0x18, 0x21, 0x18, 0x1a, 0x1d, 0x1d,
      0x1f, 0x1f, 0x1f, 0x13, 0x17, 0x22, 0x24, 0x22, 0x1e, 0x24, 0x1c, 0x1e,
      0x1f, 0x1e, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x05, 0x05, 0x05, 0x07, 0x06,
      0x07, 0x0e, 0x08, 0x08, 0x0e, 0x1e, 0x14, 0x11, 0x14, 0x1e, 0x1e, 0x1e,
      0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
      0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
      0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
      0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0xff,
      0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x22, 0x00,
      0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x15, 0x00, 0x01,
      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
      0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f,
      0x00, 0xb2, 0xc0, 0x07, 0xff, 0xd9};
  
  constexpr int kJpegImageWidth = 1;
  constexpr int kJpegImageHeight = 1;
  
  constexpr size_t kJpegImageSubrangeWithDimensionsLength =
      sizeof(kJpegImage) - 1;
  constexpr size_t kJpegImageSubrangeWithoutDimensionsLength = 3;
  
  class ImageResourceTest : public testing::Test,
                            private ScopedMockOverlayScrollbars {
    void TearDown() override {
      // Trigger a GC so MockFinishObserver gets destroyed and EXPECT_CALL gets
      // checked before the test ends.
      ThreadState::Current()->CollectAllGarbageForTesting(
          ThreadState::StackState::kNoHeapPointers);
    }
  };
  
  // Ensure that the image decoder can determine the dimensions of kJpegImage from
  // just the first kJpegImageSubrangeWithDimensionsLength bytes. If this test
  // fails, then the test data here probably needs to be updated.
  TEST_F(ImageResourceTest, DimensionsDecodableFromPartialTestImage) {
    scoped_refptr<Image> image = BitmapImage::Create();
    EXPECT_EQ(
        Image::kSizeAvailable,
        image->SetData(SharedBuffer::Create(
                           kJpegImage, kJpegImageSubrangeWithDimensionsLength),
                       true));
    EXPECT_TRUE(IsA<BitmapImage>(image.get()));
    EXPECT_EQ(1, image->width());
    EXPECT_EQ(1, image->height());
  }
  
  // An image of size 50x50.
  constexpr unsigned char kJpegImage2[] = {
      0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
      0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43,
      0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0x00, 0x43, 0x01, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      0xff, 0xff, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x03,
      0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00,
      0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x10,
      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x15, 0x01, 0x01, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03,
      0x11, 0x00, 0x3f, 0x00, 0x00, 0x94, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xd9};
  
  constexpr char kSvgImage[] =
      "<svg width=\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" "
      "xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
      "<rect x=\"0\" y=\"0\" width=\"100px\" height=\"100px\" fill=\"red\"/>"
      "</svg>";
  
  constexpr char kSvgImage2[] =
      "<svg width=\"300\" height=\"300\" xmlns=\"http://www.w3.org/2000/svg\" "
      "xmlns:xlink=\"http://www.w3.org/1999/xlink\">"
      "<rect x=\"0\" y=\"0\" width=\"200px\" height=\"200px\" fill=\"green\"/>"
      "</svg>";
  
  constexpr char kTestURL[] = "http://www.test.com/cancelTest.html";
  
  String GetTestFilePath() {
    return test::CoreTestDataPath("cancelTest.html");
  }
  
  constexpr char kSvgImageWithSubresource[] =
      "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"198\" height=\"100\">"
      "<style>"
      "  <![CDATA[@font-face{font-family:\"test\"; "
      "    src:url('data:font/ttf;base64,invalidFontData');}]]>"
      "</style>"
      "<text x=\"50\" y=\"50\" font-family=\"test\" font-size=\"16\">Fox</text>"
      "</svg>";
  
  void ReceiveResponse(ImageResource* image_resource,
                       const KURL& url,
                       const AtomicString& mime_type,
                       const char* data,
                       size_t data_size) {
    ResourceResponse resource_response(url);
    resource_response.SetMimeType(mime_type);
    resource_response.SetHttpStatusCode(200);
    image_resource->NotifyStartLoad();
    image_resource->ResponseReceived(resource_response);
    image_resource->AppendData(data, data_size);
    image_resource->FinishForTest();
  }
  
  AtomicString BuildContentRange(size_t range_length, size_t total_length) {
    return AtomicString(String("bytes 0-" + String::Number(range_length - 1) +
                               "/" + String::Number(total_length)));
  }
  
  ResourceFetcher* CreateFetcher() {
    auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
    return MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
        properties->MakeDetachable(), MakeGarbageCollected<MockFetchContext>(),
        base::MakeRefCounted<scheduler::FakeTaskRunner>(),
        base::MakeRefCounted<scheduler::FakeTaskRunner>(),
        MakeGarbageCollected<TestLoaderFactory>(),
        MakeGarbageCollected<MockContextLifecycleNotifier>(),
        nullptr /* back_forward_cache_loader_helper */));
  }
  
  TEST_F(ImageResourceTest, MultipartImage) {
    ResourceFetcher* fetcher = CreateFetcher();
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    // Emulate starting a real load, but don't expect any "real"
    // WebURLLoaderClient callbacks.
    ImageResource* image_resource = ImageResource::CreateForTest(test_url);
    fetcher->StartLoad(image_resource);
  
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
  
    // 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 multipart_response(NullURL());
    multipart_response.SetMimeType("multipart/x-mixed-replace");
    multipart_response.SetHttpHeaderField(
        http_names::kContentType, "multipart/x-mixed-replace; boundary=boundary");
    image_resource->Loader()->DidReceiveResponse(
        WrappedResourceResponse(multipart_response));
    EXPECT_FALSE(image_resource->ResourceBuffer());
    EXPECT_FALSE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(0, observer->ImageChangedCount());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ("multipart/x-mixed-replace",
              image_resource->GetResponse().MimeType());
  
    const char kFirstPart[] =
        "--boundary\n"
        "Content-Type: image/svg+xml\n\n";
    image_resource->AppendData(kFirstPart, strlen(kFirstPart));
    // Send the response for the first real part. No image or data buffer is
    // created.
    EXPECT_FALSE(image_resource->ResourceBuffer());
    EXPECT_FALSE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(0, observer->ImageChangedCount());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ("image/svg+xml", image_resource->GetResponse().MimeType());
  
    const char kSecondPart[] =
        "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><rect "
        "width='1' height='1' fill='green'/></svg>\n";
    // The first bytes arrive. The data buffer is created, but no image is
    // created.
    image_resource->AppendData(kSecondPart, strlen(kSecondPart));
    EXPECT_TRUE(image_resource->ResourceBuffer());
    EXPECT_FALSE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(0, observer->ImageChangedCount());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
  
    // Add an observer to check an assertion error doesn't happen
    // (crbug.com/630983).
    auto* observer2 = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
    EXPECT_EQ(0, observer2->ImageChangedCount());
    EXPECT_FALSE(observer2->ImageNotifyFinishedCalled());
  
    const char kThirdPart[] = "--boundary";
    image_resource->AppendData(kThirdPart, strlen(kThirdPart));
    ASSERT_TRUE(image_resource->ResourceBuffer());
    EXPECT_EQ(strlen(kSecondPart) - 1, image_resource->ResourceBuffer()->size());
  
    // This part finishes. The image is created, callbacks are sent, and the data
    // buffer is cleared.
    image_resource->Loader()->DidFinishLoading(base::TimeTicks(), 0, 0, 0, false);
    EXPECT_TRUE(image_resource->ResourceBuffer());
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
    EXPECT_TRUE(IsA<SVGImage>(image_resource->GetContent()->GetImage()));
    EXPECT_TRUE(image_resource->GetContent()
                    ->GetImage()
                    ->PaintImageForCurrentFrame()
                    .is_multipart());
  
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(1, observer2->ImageChangedCount());
    EXPECT_TRUE(observer2->ImageNotifyFinishedCalled());
  }
  
  TEST_F(ImageResourceTest, BitmapMultipartImage) {
    ResourceFetcher* fetcher = CreateFetcher();
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
    ResourceRequest resource_request(test_url);
    resource_request.SetInspectorId(CreateUniqueIdentifier());
    resource_request.SetRequestorOrigin(SecurityOrigin::CreateUniqueOpaque());
    resource_request.SetReferrerPolicy(
        ReferrerUtils::MojoReferrerPolicyResolveDefault(
            resource_request.GetReferrerPolicy()));
    resource_request.SetPriority(WebURLRequest::Priority::kLow);
    ImageResource* image_resource =
        ImageResource::Create(resource_request, nullptr /* world */);
    fetcher->StartLoad(image_resource);
  
    ResourceResponse multipart_response(NullURL());
    multipart_response.SetMimeType("multipart/x-mixed-replace");
    multipart_response.SetHttpHeaderField(
        http_names::kContentType, "multipart/x-mixed-replace; boundary=boundary");
    image_resource->Loader()->DidReceiveResponse(
        WrappedResourceResponse(multipart_response));
    EXPECT_FALSE(image_resource->GetContent()->HasImage());
  
    const char kBoundary[] = "--boundary\n";
    const char kContentType[] = "Content-Type: image/jpeg\n\n";
    image_resource->AppendData(kBoundary, strlen(kBoundary));
    image_resource->AppendData(kContentType, strlen(kContentType));
    image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage),
                               sizeof(kJpegImage));
    image_resource->AppendData(kBoundary, strlen(kBoundary));
    image_resource->Loader()->DidFinishLoading(base::TimeTicks(), 0, 0, 0, false);
    EXPECT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_TRUE(image_resource->GetContent()
                    ->GetImage()
                    ->PaintImageForCurrentFrame()
                    .is_multipart());
  }
  
  TEST_F(ImageResourceTest, CancelOnRemoveObserver) {
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    ResourceFetcher* fetcher = CreateFetcher();
    scheduler::FakeTaskRunner* task_runner =
        static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
    task_runner->SetTime(1);
  
    // Emulate starting a real load.
    ImageResource* image_resource = ImageResource::CreateForTest(test_url);
  
    fetcher->StartLoad(image_resource);
    GetMemoryCache()->Add(image_resource);
  
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
  
    // The load should still be alive, but a timer should be started to cancel the
    // load inside removeClient().
    observer->RemoveAsObserver();
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
    EXPECT_TRUE(GetMemoryCache()->ResourceForURL(test_url));
  
    // Trigger the cancel timer, ensure the load was cancelled and the resource
    // was evicted from the cache.
    task_runner->RunUntilIdle();
    EXPECT_EQ(ResourceStatus::kLoadError, image_resource->GetStatus());
    EXPECT_FALSE(GetMemoryCache()->ResourceForURL(test_url));
  }
  
  class MockFinishObserver : public ResourceFinishObserver {
   public:
    static MockFinishObserver* Create() {
      return MakeGarbageCollected<testing::StrictMock<MockFinishObserver>>();
    }
    MOCK_METHOD0(NotifyFinished, void());
    String DebugName() const override { return "MockFinishObserver"; }
  
    void Trace(Visitor* visitor) const override {
      blink::ResourceFinishObserver::Trace(visitor);
    }
  
   protected:
    MockFinishObserver() = default;
  };
  
  TEST_F(ImageResourceTest, CancelWithImageAndFinishObserver) {
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    ResourceFetcher* fetcher = CreateFetcher();
    scheduler::FakeTaskRunner* task_runner =
        static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
  
    // Emulate starting a real load.
    ImageResource* image_resource = ImageResource::CreateForTest(test_url);
  
    fetcher->StartLoad(image_resource);
    GetMemoryCache()->Add(image_resource);
  
    Persistent<MockFinishObserver> finish_observer = MockFinishObserver::Create();
    image_resource->AddFinishObserver(finish_observer,
                                      fetcher->GetTaskRunner().get());
  
    // Send the image response.
    ResourceResponse resource_response(NullURL());
    resource_response.SetMimeType("image/jpeg");
    resource_response.SetExpectedContentLength(sizeof(kJpegImage));
    image_resource->ResponseReceived(resource_response);
    image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage),
                               sizeof(kJpegImage));
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
  
    // This shouldn't crash. crbug.com/701723
    image_resource->Loader()->Cancel();
  
    EXPECT_EQ(ResourceStatus::kLoadError, image_resource->GetStatus());
    EXPECT_FALSE(GetMemoryCache()->ResourceForURL(test_url));
  
    // ResourceFinishObserver is notified asynchronously.
    EXPECT_CALL(*finish_observer, NotifyFinished());
    task_runner->RunUntilIdle();
  }
  
  TEST_F(ImageResourceTest, DecodedDataRemainsWhileHasClients) {
    ImageResource* image_resource = ImageResource::CreateForTest(NullURL());
    image_resource->NotifyStartLoad();
  
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    // Send the image response.
    ResourceResponse resource_response(NullURL());
    resource_response.SetMimeType("multipart/x-mixed-replace");
    image_resource->ResponseReceived(resource_response);
  
    resource_response.SetMimeType("image/jpeg");
    resource_response.SetExpectedContentLength(sizeof(kJpegImage));
    image_resource->ResponseReceived(resource_response);
    image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage),
                               sizeof(kJpegImage));
    EXPECT_NE(0u, image_resource->EncodedSizeMemoryUsageForTesting());
    image_resource->FinishForTest();
    EXPECT_EQ(0u, image_resource->EncodedSizeMemoryUsageForTesting());
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
  
    // The prune comes when the ImageResource still has observers. The image
    // should not be deleted.
    image_resource->Prune();
    EXPECT_TRUE(image_resource->IsAlive());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
  
    // The ImageResource no longer has observers. The decoded image data should be
    // deleted by prune.
    observer->RemoveAsObserver();
    image_resource->Prune();
    EXPECT_FALSE(image_resource->IsAlive());
    EXPECT_TRUE(image_resource->GetContent()->HasImage());
    // TODO(hajimehoshi): Should check imageResource doesn't have decoded image
    // data.
  }
  
  TEST_F(ImageResourceTest, UpdateBitmapImages) {
    ImageResource* image_resource = ImageResource::CreateForTest(NullURL());
    image_resource->NotifyStartLoad();
  
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    // Send the image response.
  
    ResourceResponse resource_response(NullURL());
    resource_response.SetMimeType("image/jpeg");
    resource_response.SetExpectedContentLength(sizeof(kJpegImage));
    image_resource->ResponseReceived(resource_response);
    image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage),
                               sizeof(kJpegImage));
    image_resource->FinishForTest();
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
  }
  
  TEST_F(ImageResourceTest, SVGImage) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage,
                    strlen(kSvgImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
  }
  
  TEST_F(ImageResourceTest, SVGImageWithSubresource) {
    // SVG images block all subresources other than data urls. This test
    // explicitly asserts asynchronous loading of data url fonts, and will not
    // work when SyncLoadDataUrlFonts is enabled.
    // TODO(xiaochengh): Try other types of subresources encoded as a data url.
    ScopedSyncLoadDataUrlFontsForTest disabled_scope(false);
  
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/svg+xml",
                    kSvgImageWithSubresource, strlen(kSvgImageWithSubresource));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
  
    // At this point, image is (mostly) available but the loading is not yet
    // finished because of SVG's subresources, and thus ImageChanged() or
    // ImageNotifyFinished() are not called.
    EXPECT_EQ(ResourceStatus::kPending,
              image_resource->GetContent()->GetContentStatus());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height());
  
    // A new client added here shouldn't notified of finish.
    auto* observer2 = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
    EXPECT_EQ(1, observer2->ImageChangedCount());
    EXPECT_FALSE(observer2->ImageNotifyFinishedCalled());
  
    // After asynchronous tasks are executed, the loading of SVG document is
    // completed and ImageNotifyFinished() is called.
    test::RunPendingTasks();
    EXPECT_EQ(ResourceStatus::kCached,
              image_resource->GetContent()->GetContentStatus());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(2, observer2->ImageChangedCount());
    EXPECT_TRUE(observer2->ImageNotifyFinishedCalled());
    EXPECT_EQ(198, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(100, image_resource->GetContent()->GetImage()->height());
  
    GetMemoryCache()->EvictResources();
  }
  
  TEST_F(ImageResourceTest, SuccessfulRevalidationJpeg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ResourceResponse resource_response(url);
    resource_response.SetHttpStatusCode(304);
  
    image_resource->ResponseReceived(resource_response);
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, SuccessfulRevalidationSvg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage,
                    strlen(kSvgImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ResourceResponse resource_response(url);
    resource_response.SetHttpStatusCode(304);
    image_resource->ResponseReceived(resource_response);
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, FailedRevalidationJpegToJpeg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage2),
                    sizeof(kJpegImage2));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(4, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(50, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(50, image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, FailedRevalidationJpegToSvg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage,
                    strlen(kSvgImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(3, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, FailedRevalidationSvgToJpeg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage,
                    strlen(kSvgImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(3, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, FailedRevalidationSvgToSvg) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage,
                    strlen(kSvgImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(200, image_resource->GetContent()->GetImage()->height());
  
    image_resource->SetRevalidatingRequest(ResourceRequest(url));
    ReceiveResponse(image_resource, url, "image/svg+xml", kSvgImage2,
                    strlen(kSvgImage2));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(2, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_FALSE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(300, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(300, image_resource->GetContent()->GetImage()->height());
  }
  
  // Tests for pruning.
  
  TEST_F(ImageResourceTest, Prune) {
    KURL url("http://127.0.0.1:8000/foo");
    ImageResource* image_resource = ImageResource::CreateForTest(url);
  
    ReceiveResponse(image_resource, url, "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  
    EXPECT_FALSE(image_resource->IsAlive());
  
    image_resource->Prune();
  
    EXPECT_TRUE(image_resource->GetContent()->HasImage());
  
    blink::test::RunPendingTasks();
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(kJpegImageWidth, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(kJpegImageHeight,
              image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, CancelOnDecodeError) {
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    ResourceFetcher* fetcher = CreateFetcher();
    FetchParameters params =
        FetchParameters::CreateForTest(ResourceRequest(test_url));
    ImageResource* image_resource = ImageResource::Fetch(params, fetcher);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ResourceResponse resource_response(test_url);
    resource_response.SetMimeType("image/jpeg");
    resource_response.SetExpectedContentLength(18);
    image_resource->Loader()->DidReceiveResponse(
        WrappedResourceResponse(resource_response));
  
    EXPECT_EQ(0, observer->ImageChangedCount());
  
    image_resource->Loader()->DidReceiveData("notactuallyanimage", 18);
  
    EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(ResourceStatus::kDecodeError,
              observer->StatusOnImageNotifyFinished());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_FALSE(image_resource->IsLoading());
  }
  
  TEST_F(ImageResourceTest, DecodeErrorWithEmptyBody) {
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    ResourceFetcher* fetcher = CreateFetcher();
    FetchParameters params =
        FetchParameters::CreateForTest(ResourceRequest(test_url));
    ImageResource* image_resource = ImageResource::Fetch(params, fetcher);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ResourceResponse resource_response(test_url);
    resource_response.SetMimeType("image/jpeg");
    image_resource->Loader()->DidReceiveResponse(
        WrappedResourceResponse(resource_response));
  
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(0, observer->ImageChangedCount());
  
    image_resource->Loader()->DidFinishLoading(base::TimeTicks(), 0, 0, 0, false);
  
    EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(ResourceStatus::kDecodeError,
              observer->StatusOnImageNotifyFinished());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_FALSE(image_resource->IsLoading());
  }
  
  // Testing DecodeError that occurs in didFinishLoading().
  // This is similar to DecodeErrorWithEmptyBody, but with non-empty body.
  TEST_F(ImageResourceTest, PartialContentWithoutDimensions) {
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    ResourceRequest resource_request(test_url);
    resource_request.SetHttpHeaderField("range", "bytes=0-2");
    FetchParameters params =
        FetchParameters::CreateForTest(std::move(resource_request));
    ResourceFetcher* fetcher = CreateFetcher();
    ImageResource* image_resource = ImageResource::Fetch(params, fetcher);
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    ResourceResponse partial_response(test_url);
    partial_response.SetMimeType("image/jpeg");
    partial_response.SetExpectedContentLength(
        kJpegImageSubrangeWithoutDimensionsLength);
    partial_response.SetHttpStatusCode(206);
    partial_response.SetHttpHeaderField(
        "content-range",
        BuildContentRange(kJpegImageSubrangeWithoutDimensionsLength,
                          sizeof(kJpegImage)));
  
    image_resource->Loader()->DidReceiveResponse(
        WrappedResourceResponse(partial_response));
    image_resource->Loader()->DidReceiveData(
        reinterpret_cast<const char*>(kJpegImage),
        kJpegImageSubrangeWithoutDimensionsLength);
  
    EXPECT_EQ(ResourceStatus::kPending, image_resource->GetStatus());
    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(0, observer->ImageChangedCount());
  
    image_resource->Loader()->DidFinishLoading(
        base::TimeTicks(), kJpegImageSubrangeWithoutDimensionsLength,
        kJpegImageSubrangeWithoutDimensionsLength,
        kJpegImageSubrangeWithoutDimensionsLength, false);
  
    EXPECT_EQ(ResourceStatus::kDecodeError, image_resource->GetStatus());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_EQ(ResourceStatus::kDecodeError,
              observer->StatusOnImageNotifyFinished());
    EXPECT_EQ(1, observer->ImageChangedCount());
    EXPECT_FALSE(image_resource->IsLoading());
  }
  
  TEST_F(ImageResourceTest, PeriodicFlushTest) {
    ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
        platform;
  
    std::unique_ptr<DummyPageHolder> page_holder =
        std::make_unique<DummyPageHolder>(
            IntSize(800, 600), /*chrome_client=*/nullptr,
            MakeGarbageCollected<EmptyLocalFrameClient>());
  
    KURL test_url(kTestURL);
    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
  
    scoped_refptr<base::SingleThreadTaskRunner> task_runner =
        page_holder->GetFrame().GetTaskRunner(TaskType::kInternalTest);
    scoped_refptr<base::SingleThreadTaskRunner> unfreezable_task_runner =
        page_holder->GetFrame().GetTaskRunner(TaskType::kInternalTest);
    auto* context = MakeGarbageCollected<MockFetchContext>();
    auto& properties =
        MakeGarbageCollected<TestResourceFetcherProperties>()->MakeDetachable();
    auto* fetcher = MakeGarbageCollected<ResourceFetcher>(ResourceFetcherInit(
        properties, context, task_runner, unfreezable_task_runner,
        MakeGarbageCollected<TestLoaderFactory>(),
        page_holder->GetFrame().DomWindow(),
        nullptr /* back_forward_cache_loader_helper */));
    auto frame_scheduler = std::make_unique<scheduler::FakeFrameScheduler>();
    auto* scheduler = MakeGarbageCollected<ResourceLoadScheduler>(
        ResourceLoadScheduler::ThrottlingPolicy::kNormal,
        ResourceLoadScheduler::ThrottleOptionOverride::kNone, properties,
        frame_scheduler.get(), *MakeGarbageCollected<DetachableConsoleLogger>(),
        /*loading_behavior_observer=*/nullptr);
    ImageResource* image_resource = ImageResource::CreateForTest(test_url);
  
    // Ensure that |image_resource| has a loader.
    auto* loader =
        MakeGarbageCollected<ResourceLoader>(fetcher, scheduler, image_resource);
    ALLOW_UNUSED_LOCAL(loader);
  
    image_resource->NotifyStartLoad();
  
    auto* observer = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    // Send the image response.
    ResourceResponse resource_response(NullURL());
    resource_response.SetMimeType("image/jpeg");
    resource_response.SetExpectedContentLength(sizeof(kJpegImage2));
    image_resource->ResponseReceived(resource_response);
  
    // This is number is sufficiently large amount of bytes necessary for the
    // image to be created (since the size is known). This was determined by
    // appending one byte at a time (with flushes) until the image was decoded.
    size_t meaningful_image_size = 280;
    image_resource->AppendData(reinterpret_cast<const char*>(kJpegImage2),
                               meaningful_image_size);
    size_t bytes_sent = meaningful_image_size;
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    EXPECT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(1, observer->ImageChangedCount());
  
    platform->RunForPeriodSeconds(1.);
    platform->AdvanceClockSeconds(1.);
  
    // Sanity check that we created an image after appending |meaningfulImageSize|
    // bytes just once.
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_EQ(1, observer->ImageChangedCount());
  
    for (int flush_count = 1; flush_count <= 3; ++flush_count) {
      // For each of the iteration that appends data, we don't expect
      // |imageChangeCount()| to change, since the time is adjusted by 0.2001
      // seconds (it's greater than 0.2 to avoid double precision problems).
      // After 5 appends, we breach the flush interval and the flush count
      // increases.
      for (int i = 0; i < 5; ++i) {
        SCOPED_TRACE(i);
        image_resource->AppendData(
            reinterpret_cast<const char*>(kJpegImage2) + bytes_sent, 1);
  
        EXPECT_FALSE(image_resource->ErrorOccurred());
        ASSERT_TRUE(image_resource->GetContent()->HasImage());
        EXPECT_EQ(flush_count, observer->ImageChangedCount());
  
        ++bytes_sent;
        platform->RunForPeriodSeconds(0.2001);
      }
    }
  
    // Increasing time by a large number only causes one extra flush.
    platform->RunForPeriodSeconds(10.);
    platform->AdvanceClockSeconds(10.);
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(4, observer->ImageChangedCount());
  
    // Append the rest of the data and finish (which causes another flush).
    image_resource->AppendData(
        reinterpret_cast<const char*>(kJpegImage2) + bytes_sent,
        sizeof(kJpegImage2) - bytes_sent);
    image_resource->FinishForTest();
  
    EXPECT_FALSE(image_resource->ErrorOccurred());
    ASSERT_TRUE(image_resource->GetContent()->HasImage());
    EXPECT_FALSE(image_resource->GetContent()->GetImage()->IsNull());
    EXPECT_EQ(5, observer->ImageChangedCount());
    EXPECT_TRUE(observer->ImageNotifyFinishedCalled());
    EXPECT_TRUE(IsA<BitmapImage>(image_resource->GetContent()->GetImage()));
    EXPECT_EQ(50, image_resource->GetContent()->GetImage()->width());
    EXPECT_EQ(50, image_resource->GetContent()->GetImage()->height());
  }
  
  TEST_F(ImageResourceTest, DeferredInvalidation) {
    ImageResource* image_resource = ImageResource::CreateForTest(NullURL());
    auto* obs = MakeGarbageCollected<MockImageResourceObserver>(
        image_resource->GetContent());
  
    // Image loaded.
    ReceiveResponse(image_resource, NullURL(), "image/jpeg",
                    reinterpret_cast<const char*>(kJpegImage),
                    sizeof(kJpegImage));
    EXPECT_EQ(obs->ImageChangedCount(), 2);
    EXPECT_EQ(obs->Defer(), ImageResourceObserver::CanDeferInvalidation::kNo);
  
    // Image animated.
    static_cast<ImageObserver*>(image_resource->GetContent())
        ->Changed(image_resource->GetContent()->GetImage());
    EXPECT_EQ(obs->ImageChangedCount(), 3);
    EXPECT_EQ(obs->Defer(), ImageResourceObserver::CanDeferInvalidation::kYes);
  }
  
  // A lossy 2x2 WebP image.
  constexpr unsigned char kLossyWebPImage[] = {
      0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
      0x56, 0x50, 0x38, 0x20, 0x34, 0x00, 0x00, 0x00, 0xB0, 0x01, 0x00, 0x9D,
      0x01, 0x2A, 0x02, 0x00, 0x02, 0x00, 0x00, 0xC0, 0x12, 0x25, 0x00, 0x4E,
      0x80, 0x21, 0xDF, 0xC0, 0x5D, 0x80, 0x00, 0xFE, 0x9B, 0x87, 0xFA, 0x8F,
      0xF8, 0xA0, 0x1E, 0xD7, 0xC8, 0x70, 0x88, 0x0B, 0x6C, 0x54, 0x7F, 0xC0,
      0x7F, 0x12, 0xFE, 0xC0, 0xBC, 0x70, 0x65, 0xB6, 0xC1, 0x00, 0x00, 0x00};
  
  // A lossless 2x2 WebP image.
  constexpr unsigned char kLosslessWebPImage[] = {
      0x52, 0x49, 0x46, 0x46, 0x3A, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42,
      0x50, 0x56, 0x50, 0x38, 0x4C, 0x2E, 0x00, 0x00, 0x00, 0x2F, 0x01,
      0x40, 0x00, 0x00, 0x1F, 0x20, 0x10, 0x20, 0x72, 0xCC, 0x09, 0x13,
      0x27, 0x48, 0x40, 0x42, 0xB8, 0xE3, 0xB9, 0x95, 0x12, 0x12, 0x10,
      0x2B, 0x0C, 0xB4, 0xD8, 0x1C, 0x75, 0xFE, 0x03, 0xDB, 0xBA, 0x09,
      0x40, 0x26, 0x6D, 0x1B, 0x6A, 0xBB, 0x6B, 0x11, 0xFD, 0x8F, 0x1D};
  
  // An extended lossy 2x2 WebP image.
  constexpr unsigned char kExtendedWebPImage[] = {
      0x52, 0x49, 0x46, 0x46, 0x60, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
      0x56, 0x50, 0x38, 0x58, 0x0A, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
      0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x41, 0x4C, 0x50, 0x48, 0x05, 0x00,
      0x00, 0x00, 0x00, 0xFF, 0xFF, 0xE9, 0xAE, 0x00, 0x56, 0x50, 0x38, 0x20,
      0x34, 0x00, 0x00, 0x00, 0xB0, 0x01, 0x00, 0x9D, 0x01, 0x2A, 0x02, 0x00,
      0x02, 0x00, 0x00, 0xC0, 0x12, 0x25, 0x94, 0x02, 0x74, 0x01, 0x0E, 0xFE,
      0x02, 0xEC, 0x00, 0xFE, 0x9B, 0x87, 0xFA, 0x8F, 0xF8, 0xA0, 0x1E, 0xD7,
      0xC8, 0x70, 0x88, 0x0B, 0x6C, 0x54, 0x7A, 0xFB, 0xCA, 0x1D, 0x89, 0x90,
      0xDD, 0x27, 0xEA, 0x7F, 0x28, 0x00, 0x00, 0x00};
  
  TEST_F(ImageResourceTest, WebPSniffing) {
    KURL test_url(kTestURL);
  
    // Test lossy WebP image.
    ImageResource* image_resource = ImageResource::CreateForTest(test_url);
    image_resource->AppendData(reinterpret_cast<const char*>(kLossyWebPImage),
                               sizeof(kLossyWebPImage));
    EXPECT_EQ(1, image_resource->GetContent()->GetCompressionFormat());
  
    // Test lossless WebP image.
    image_resource = ImageResource::CreateForTest(test_url);
    image_resource->AppendData(reinterpret_cast<const char*>(kLosslessWebPImage),
                               sizeof(kLosslessWebPImage));
    EXPECT_EQ(2, image_resource->GetContent()->GetCompressionFormat());
  
    // Test extended WebP image.
    image_resource = ImageResource::CreateForTest(test_url);
    image_resource->AppendData(reinterpret_cast<const char*>(kExtendedWebPImage),
                               sizeof(kExtendedWebPImage));
    EXPECT_EQ(1, image_resource->GetContent()->GetCompressionFormat());
  }
  
  }  // namespace
  
  class ImageResourceCounterTest : public testing::Test {
   public:
    ImageResourceCounterTest() = default;
    ~ImageResourceCounterTest() override = default;
  
    void CreateImageResource(const char* url_part, bool ua_resource) {
      // Create a unique fake data url.
      String url = StringView("data:image/png;base64,") + url_part;
  
      // Setup the fetcher and request.
      ResourceFetcher* fetcher = CreateFetcher();
      KURL test_url(url);
      ResourceRequest request = ResourceRequest(test_url);
      FetchParameters fetch_params =
          FetchParameters::CreateForTest(std::move(request));
      scheduler::FakeTaskRunner* task_runner =
          static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
      task_runner->SetTime(1);
  
      // Mark it as coming from a UA stylesheet (if needed).
      if (ua_resource) {
        fetch_params.MutableOptions().initiator_info.name =
            fetch_initiator_type_names::kUacss;
      }
  
      // Fetch the ImageResource.
      ImageResource::Fetch(fetch_params, fetcher);
      task_runner->RunUntilIdle();
    }
  
    int GetResourceCount() const {
      return InstanceCounters::CounterValue(InstanceCounters::kResourceCounter);
    }
  
    int GetUACSSResourceCount() const {
      return InstanceCounters::CounterValue(
          InstanceCounters::kUACSSResourceCounter);
    }
  };
  
  TEST_F(ImageResourceCounterTest, InstanceCounters) {
    // Get the current resource count.
    int current_count = GetResourceCount();
    int current_ua_count = GetUACSSResourceCount();
  
    // Create a non-UA sourced image.
    CreateImageResource("a", false);
  
    // Check the instance counters have been updated.
    EXPECT_EQ(++current_count, GetResourceCount());
    EXPECT_EQ(current_ua_count, GetUACSSResourceCount());
  
    // Create another non-UA sourced image.
    CreateImageResource("b", false);
  
    // Check the instance counters have been updated.
    EXPECT_EQ(++current_count, GetResourceCount());
    EXPECT_EQ(current_ua_count, GetUACSSResourceCount());
  }
  
  TEST_F(ImageResourceCounterTest, InstanceCounters_UserAgent) {
    // Get the current resource count.
    int current_count = GetResourceCount();
    int current_ua_count = GetUACSSResourceCount();
  
    // Create a non-UA sourced image.
    CreateImageResource("c", false);
  
    // Check the instance counters have been updated.
    EXPECT_EQ(++current_count, GetResourceCount());
    EXPECT_EQ(current_ua_count, GetUACSSResourceCount());
  
    // Create a UA sourced image.
    CreateImageResource("d", true);
  
    // Check the instance counters have been updated.
    EXPECT_EQ(++current_count, GetResourceCount());
    EXPECT_EQ(++current_ua_count, GetUACSSResourceCount());
  }
  
  }  // namespace blink
  