TEST_F(ImageQualityControllerTest, LowQualityFilterForLiveResize)
{
    MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
    controller()->setTimer(mockTimer);
    setBodyInnerHTML("<img src='myimage'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);

    // Start a resize
    document().frame()->view()->willStartLiveResize();
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(2, 2)));

    document().frame()->view()->willEndLiveResize();

    // End of live resize, but timer has not fired. Therefore paint at non-low quality.
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(3, 3)));

    // Start another resize
    document().frame()->view()->willStartLiveResize();
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(3, 3)));

    // While still in resize, expire the timer.
    document().frame()->view()->willEndLiveResize();

    mockTimer->fire();
    // End of live resize, and timer has fired. Therefore paint at non-low quality, even though the size has changed.
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
TEST_F(ImageQualityControllerTest, DontKickTheAnimationTimerWhenPaintingAtTheSameSize)
{
    MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
    controller()->setTimer(mockTimer);
    setBodyInnerHTML("<img src='myimage'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);

    // Paint once. This will kick off a timer to see if we resize it during that timer's execution.
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(2, 2)));

    // Go into low-quality mode now that the size changed.
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(3, 3)));

    // Stay in low-quality mode since the size changed again.
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));

    mockTimer->stop();
    EXPECT_FALSE(mockTimer->isActive());
    // Painted at the same size, so even though timer is still executing, don't go to low quality.
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
    // Check that the timer was not kicked. It should not have been, since the image was painted at the same size as last time.
    EXPECT_FALSE(mockTimer->isActive());
}
Esempio n. 3
0
std::unique_ptr<Shape> ShapeOutsideInfo::createShapeForImage(
    StyleImage* styleImage,
    float shapeImageThreshold,
    WritingMode writingMode,
    float margin) const {
  const LayoutSize& imageSize =
      styleImage->imageSize(m_layoutBox, m_layoutBox.style()->effectiveZoom(),
                            m_referenceBoxLogicalSize);

  const LayoutRect& marginRect =
      getShapeImageMarginRect(m_layoutBox, m_referenceBoxLogicalSize);
  const LayoutRect& imageRect =
      (m_layoutBox.isLayoutImage())
          ? toLayoutImage(m_layoutBox).replacedContentRect()
          : LayoutRect(LayoutPoint(), imageSize);

  if (!isValidRasterShapeRect(marginRect) ||
      !isValidRasterShapeRect(imageRect)) {
    m_layoutBox.document().addConsoleMessage(
        ConsoleMessage::create(RenderingMessageSource, ErrorMessageLevel,
                               "The shape-outside image is too large."));
    return Shape::createEmptyRasterShape(writingMode, margin);
  }

  ASSERT(!styleImage->isPendingImage());
  RefPtr<Image> image =
      styleImage->image(m_layoutBox, flooredIntSize(imageSize),
                        m_layoutBox.style()->effectiveZoom());

  return Shape::createRasterShape(image.get(), shapeImageThreshold, imageRect,
                                  marginRect, writingMode, margin);
}
Esempio n. 4
0
void LayoutReplaced::computeIntrinsicSizingInfoForReplacedContent(
    LayoutReplaced* contentLayoutObject,
    IntrinsicSizingInfo& intrinsicSizingInfo) const {
    if (contentLayoutObject) {
        contentLayoutObject->computeIntrinsicSizingInfo(intrinsicSizingInfo);

        // Handle zoom & vertical writing modes here, as the embedded document
        // doesn't know about them.
        intrinsicSizingInfo.size.scale(style()->effectiveZoom());
        if (isLayoutImage())
            intrinsicSizingInfo.size.scale(
                toLayoutImage(this)->imageDevicePixelRatio());

        // Update our intrinsic size to match what the content layoutObject has
        // computed, so that when we constrain the size below, the correct intrinsic
        // size will be obtained for comparison against min and max widths.
        if (!intrinsicSizingInfo.aspectRatio.isEmpty() &&
                !intrinsicSizingInfo.size.isEmpty())
            m_intrinsicSize = LayoutSize(intrinsicSizingInfo.size);

        if (!isHorizontalWritingMode())
            intrinsicSizingInfo.transpose();
    } else {
        computeIntrinsicSizingInfo(intrinsicSizingInfo);
        if (!intrinsicSizingInfo.aspectRatio.isEmpty() &&
                !intrinsicSizingInfo.size.isEmpty())
            m_intrinsicSize =
                LayoutSize(isHorizontalWritingMode()
                           ? intrinsicSizingInfo.size
                           : intrinsicSizingInfo.size.transposedSize());
    }
}
TEST_F(ImageQualityControllerTest, MediumQualityFilterForUnscaledImage)
{
    setBodyInnerHTML("<img src='myimage'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(1, 1)));
}
TEST_F(ImageQualityControllerTest, ImageMaybeAnimated)
{
    setBodyInnerHTML("<img src='myimage'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageAnimated> testImage = adoptRef(new TestImageAnimated);
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), nullptr, LayoutSize()));
}
TEST_F(ImageQualityControllerTest, LowQualityFilterForContrast)
{
    setBodyInnerHTML("<img src='myimage' style='image-rendering: -webkit-optimize-contrast'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageWithContrast> testImage = adoptRef(new TestImageWithContrast);
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize()));
}
LayoutSize LayoutImageResource::getImageSize(float multiplier, ImageResource::SizeType type) const
{
    if (!m_cachedImage)
        return LayoutSize();
    LayoutSize size = m_cachedImage->imageSizeForLayoutObject(m_renderer, multiplier, type);
    if (m_renderer && m_renderer->isLayoutImage())
        size.scale(toLayoutImage(m_renderer)->imageDevicePixelRatio());
    return size;
}
Esempio n. 9
0
LayoutSize LayoutImageResource::imageSize(float multiplier) const
{
    if (!m_cachedImage)
        return LayoutSize();
    LayoutSize size = m_cachedImage->imageSize(LayoutObject::shouldRespectImageOrientation(m_layoutObject), multiplier);
    if (m_layoutObject && m_layoutObject->isLayoutImage() && size.width() && size.height())
        size.scale(toLayoutImage(m_layoutObject)->imageDevicePixelRatio());
    return size;
}
LayoutSize LayoutImageResource::getImageSize(float multiplier, ImageResource::SizeType type) const
{
    if (!m_cachedImage)
        return LayoutSize();
    LayoutSize size = m_cachedImage->imageSizeForLayoutObject(m_layoutObject, multiplier, type);
    if (m_layoutObject && m_layoutObject->isLayoutImage() && size.width() && size.height())
        size.scale(toLayoutImage(m_layoutObject)->imageDevicePixelRatio());
    return size;
}
Esempio n. 11
0
static ImageResourceContent* getImageResourceContent(Element* element) {
  // Attempt to pull ImageResourceContent from element
  ASSERT(element);
  LayoutObject* layoutObject = element->layoutObject();
  if (!layoutObject || !layoutObject->isImage())
    return 0;

  LayoutImage* image = toLayoutImage(layoutObject);
  if (image->cachedImage() && !image->cachedImage()->errorOccurred())
    return image->cachedImage();

  return 0;
}
Esempio n. 12
0
void HTMLAreaElement::setFocus(bool shouldBeFocused)
{
    if (focused() == shouldBeFocused)
        return;

    HTMLAnchorElement::setFocus(shouldBeFocused);

    HTMLImageElement* imageElement = this->imageElement();
    if (!imageElement)
        return;

    LayoutObject* layoutObject = imageElement->layoutObject();
    if (!layoutObject || !layoutObject->isImage())
        return;

    toLayoutImage(layoutObject)->areaElementFocusChanged(this);
}
Esempio n. 13
0
void LayoutReplaced::computeAspectRatioInformationForLayoutBox(LayoutBox* contentLayoutObject, FloatSize& constrainedSize, double& intrinsicRatio) const
{
    FloatSize intrinsicSize;
    if (contentLayoutObject) {
        contentLayoutObject->computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio);

        // Handle zoom & vertical writing modes here, as the embedded document doesn't know about them.
        intrinsicSize.scale(style()->effectiveZoom());
        if (isLayoutImage())
            intrinsicSize.scale(toLayoutImage(this)->imageDevicePixelRatio());

        // Update our intrinsic size to match what the content layoutObject has computed, so that when we
        // constrain the size below, the correct intrinsic size will be obtained for comparison against
        // min and max widths.
        if (intrinsicRatio && !intrinsicSize.isEmpty())
            m_intrinsicSize = LayoutSize(intrinsicSize);

        if (!isHorizontalWritingMode()) {
            if (intrinsicRatio)
                intrinsicRatio = 1 / intrinsicRatio;
            intrinsicSize = intrinsicSize.transposedSize();
        }
    } else {
        computeIntrinsicRatioInformation(intrinsicSize, intrinsicRatio);
        if (intrinsicRatio && !intrinsicSize.isEmpty())
            m_intrinsicSize = LayoutSize(isHorizontalWritingMode() ? intrinsicSize : intrinsicSize.transposedSize());
    }

    // Now constrain the intrinsic size along each axis according to minimum and maximum width/heights along the
    // opposite axis. So for example a maximum width that shrinks our width will result in the height we compute here
    // having to shrink in order to preserve the aspect ratio. Because we compute these values independently along
    // each axis, the final returned size may in fact not preserve the aspect ratio.
    // FIXME: In the long term, it might be better to just return this code more to the way it used to be before this
    // function was added, since all it has done is make the code more unclear.
    constrainedSize = intrinsicSize;
    if (intrinsicRatio && !intrinsicSize.isEmpty() && style()->logicalWidth().isAuto() && style()->logicalHeight().isAuto()) {
        // We can't multiply or divide by 'intrinsicRatio' here, it breaks tests, like fast/images/zoomed-img-size.html, which
        // can only be fixed once subpixel precision is available for things like intrinsicWidth/Height - which include zoom!
        constrainedSize.setWidth(LayoutBox::computeReplacedLogicalHeight() * intrinsicSize.width() / intrinsicSize.height());
        constrainedSize.setHeight(LayoutBox::computeReplacedLogicalWidth() * intrinsicSize.height() / intrinsicSize.width());
    }
}
Esempio n. 14
0
PassOwnPtr<Shape> ShapeOutsideInfo::createShapeForImage(StyleImage* styleImage, float shapeImageThreshold, WritingMode writingMode, float margin) const
{
    const IntSize& imageSize = m_layoutBox.calculateImageIntrinsicDimensions(styleImage, roundedIntSize(m_referenceBoxLogicalSize), LayoutImage::ScaleByEffectiveZoom);
    styleImage->setContainerSizeForLayoutObject(&m_layoutBox, imageSize, m_layoutBox.style()->effectiveZoom());

    const LayoutRect& marginRect = getShapeImageMarginRect(m_layoutBox, m_referenceBoxLogicalSize);
    const LayoutRect& imageRect = (m_layoutBox.isLayoutImage())
        ? toLayoutImage(m_layoutBox).replacedContentRect()
        : LayoutRect(LayoutPoint(), LayoutSize(imageSize));

    if (!isValidRasterShapeRect(marginRect) || !isValidRasterShapeRect(imageRect)) {
        m_layoutBox.document().addConsoleMessage(ConsoleMessage::create(RenderingMessageSource, ErrorMessageLevel, "The shape-outside image is too large."));
        return Shape::createEmptyRasterShape(writingMode, margin);
    }

    ASSERT(!styleImage->isPendingImage());
    RefPtr<Image> image = styleImage->image(const_cast<LayoutBox*>(&m_layoutBox), imageSize);

    return Shape::createRasterShape(image.get(), shapeImageThreshold, imageRect, marginRect, writingMode, margin);
}
Esempio n. 15
0
LayoutImageResource* ImageLoader::layoutImageResource() {
    LayoutObject* layoutObject = m_element->layoutObject();

    if (!layoutObject)
        return 0;

    // We don't return style generated image because it doesn't belong to the
    // ImageLoader. See <https://bugs.webkit.org/show_bug.cgi?id=42840>
    if (layoutObject->isImage() &&
            !static_cast<LayoutImage*>(layoutObject)->isGeneratedContent())
        return toLayoutImage(layoutObject)->imageResource();

    if (layoutObject->isSVGImage())
        return toLayoutSVGImage(layoutObject)->imageResource();

    if (layoutObject->isVideo())
        return toLayoutVideo(layoutObject)->imageResource();

    return 0;
}
TEST_F(ImageQualityControllerTest, LowQualityFilterForResizingImage)
{
    MockTimer* mockTimer = new MockTimer(controller(), &ImageQualityController::highQualityRepaintTimerFired);
    controller()->setTimer(mockTimer);
    setBodyInnerHTML("<img src='myimage'></img>");
    LayoutImage* img = toLayoutImage(document().body()->firstChild()->layoutObject());

    RefPtr<TestImageLowQuality> testImage = adoptRef(new TestImageLowQuality);
    OwnPtr<PaintController> paintController = PaintController::create();
    GraphicsContext context(*paintController);

    // Paint once. This will kick off a timer to see if we resize it during that timer's execution.
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(2, 2)));

    // Go into low-quality mode now that the size changed.
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(3, 3)));

    // Stay in low-quality mode since the size changed again.
    EXPECT_EQ(InterpolationLow, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));

    mockTimer->fire();
    // The timer fired before painting at another size, so this doesn't count as animation. Therefore not painting at low quality.
    EXPECT_EQ(InterpolationMedium, controller()->chooseInterpolationQuality(*img, testImage.get(), testImage.get(), LayoutSize(4, 4)));
}
void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior)
{
    // FIXME: According to
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55
    // When "update image" is called due to environment changes and the load fails, onerror should not be called.
    // That is currently not the case.
    //
    // We don't need to call clearLoader here: Either we were called from the
    // task, or our caller updateFromElement cleared the task's loader (and set
    // m_pendingTask to null).
    m_pendingTask.clear();
    // Make sure to only decrement the count when we exit this function
    OwnPtr<IncrementLoadEventDelayCount> loadDelayCounter;
    loadDelayCounter.swap(m_loadDelayCounter);

    Document& document = m_element->document();
    if (!document.isActive())
        return;

    AtomicString imageSourceURL = m_element->imageSourceURL();
    KURL url = imageSourceToKURL(imageSourceURL);
    ResourcePtr<ImageResource> newImage = 0;
    RefPtrWillBeRawPtr<Element> protectElement(m_element.get());
    if (!url.isNull()) {
        // Unlike raw <img>, we block mixed content inside of <picture> or <img srcset>.
        ResourceLoaderOptions resourceLoaderOptions = ResourceFetcher::defaultResourceOptions();
        ResourceRequest resourceRequest(url);
        resourceRequest.setFetchCredentialsMode(WebURLRequest::FetchCredentialsModeSameOrigin);
        if (updateBehavior == UpdateForcedReload) {
            resourceRequest.setCachePolicy(ResourceRequestCachePolicy::ReloadBypassingCache);
            // ImageLoader defers the load of images when in an ImageDocument. Don't defer this load on a forced reload.
            m_loadingImageDocument = false;
        }
        if (isHTMLPictureElement(element()->parentNode()) || !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull())
            resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet);
        FetchRequest request(resourceRequest, element()->localName(), resourceLoaderOptions);
        configureRequest(request, bypassBehavior, *m_element, document.clientHintsPreferences());

        // Prevent the immediate creation of a ResourceLoader (and therefore a network
        // request) for ImageDocument loads. In this case, the image contents have already
        // been requested as a main resource and ImageDocumentParser will take care of
        // funneling the main resource bytes into the ImageResource.
        if (m_loadingImageDocument) {
            request.setDefer(FetchRequest::DeferredByClient);
            request.setContentSecurityCheck(DoNotCheckContentSecurityPolicy);
        }

        newImage = ImageResource::fetch(request, document.fetcher());
        if (m_loadingImageDocument && newImage)
            newImage->setLoading(true);

        if (!newImage && !pageIsBeingDismissed(&document)) {
            crossSiteOrCSPViolationOccurred(imageSourceURL);
            dispatchErrorEvent();
        } else {
            clearFailedLoadURL();
        }
    } else {
        if (!imageSourceURL.isNull()) {
            // Fire an error event if the url string is not empty, but the KURL is.
            dispatchErrorEvent();
        }
        noImageResourceToLoad();
    }

    ImageResource* oldImage = m_image.get();
    if (updateBehavior == UpdateSizeChanged && m_element->layoutObject() && m_element->layoutObject()->isImage() && newImage == oldImage) {
        toLayoutImage(m_element->layoutObject())->intrinsicSizeChanged();
    } else {
        if (newImage != oldImage)
            sourceImageChanged();

        if (m_hasPendingLoadEvent) {
            loadEventSender().cancelEvent(this);
            m_hasPendingLoadEvent = false;
        }

        // Cancel error events that belong to the previous load, which is now cancelled by changing the src attribute.
        // If newImage is null and m_hasPendingErrorEvent is true, we know the error event has been just posted by
        // this load and we should not cancel the event.
        // FIXME: If both previous load and this one got blocked with an error, we can receive one error event instead of two.
        if (m_hasPendingErrorEvent && newImage) {
            errorEventSender().cancelEvent(this);
            m_hasPendingErrorEvent = false;
        }

        m_image = newImage;
        m_hasPendingLoadEvent = newImage;
        m_imageComplete = !newImage;

        updateLayoutObject();
        // If newImage exists and is cached, addClient() will result in the load event
        // being queued to fire. Ensure this happens after beforeload is dispatched.
        if (newImage)
            newImage->addClient(this);

        if (oldImage)
            oldImage->removeClient(this);
    }

    if (LayoutImageResource* imageResource = layoutImageResource())
        imageResource->resetAnimation();

    // Only consider updating the protection ref-count of the Element immediately before returning
    // from this function as doing so might result in the destruction of this ImageLoader.
    updatedHasPendingEvent();
}
Esempio n. 18
0
void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior,
                                      UpdateFromElementBehavior updateBehavior,
                                      const KURL& url,
                                      ReferrerPolicy referrerPolicy) {
    // FIXME: According to
    // http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55
    // When "update image" is called due to environment changes and the load
    // fails, onerror should not be called. That is currently not the case.
    //
    // We don't need to call clearLoader here: Either we were called from the
    // task, or our caller updateFromElement cleared the task's loader (and set
    // m_pendingTask to null).
    m_pendingTask.reset();
    // Make sure to only decrement the count when we exit this function
    std::unique_ptr<IncrementLoadEventDelayCount> loadDelayCounter;
    loadDelayCounter.swap(m_loadDelayCounter);

    Document& document = m_element->document();
    if (!document.isActive())
        return;

    AtomicString imageSourceURL = m_element->imageSourceURL();
    ImageResource* newImage = nullptr;
    if (!url.isNull()) {
        // Unlike raw <img>, we block mixed content inside of <picture> or
        // <img srcset>.
        ResourceLoaderOptions resourceLoaderOptions =
            ResourceFetcher::defaultResourceOptions();
        ResourceRequest resourceRequest(url);
        if (updateBehavior == UpdateForcedReload) {
            resourceRequest.setCachePolicy(WebCachePolicy::BypassingCache);
            resourceRequest.setLoFiState(WebURLRequest::LoFiOff);
        }

        if (referrerPolicy != ReferrerPolicyDefault) {
            resourceRequest.setHTTPReferrer(SecurityPolicy::generateReferrer(
                                                referrerPolicy, url, document.outgoingReferrer()));
        }

        if (isHTMLPictureElement(element()->parentNode()) ||
                !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull())
            resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet);
        FetchRequest request(resourceRequest, element()->localName(),
                             resourceLoaderOptions);
        configureRequest(request, bypassBehavior, *m_element,
                         document.clientHintsPreferences());

        newImage = ImageResource::fetch(request, document.fetcher());

        if (!newImage && !pageIsBeingDismissed(&document)) {
            crossSiteOrCSPViolationOccurred(imageSourceURL);
            dispatchErrorEvent();
        } else {
            clearFailedLoadURL();
        }
    } else {
        if (!imageSourceURL.isNull()) {
            // Fire an error event if the url string is not empty, but the KURL is.
            dispatchErrorEvent();
        }
        noImageResourceToLoad();
    }

    ImageResource* oldImage = m_image.get();
    if (updateBehavior == UpdateSizeChanged && m_element->layoutObject() &&
            m_element->layoutObject()->isImage() && newImage == oldImage) {
        toLayoutImage(m_element->layoutObject())->intrinsicSizeChanged();
    } else {
        if (m_hasPendingLoadEvent) {
            loadEventSender().cancelEvent(this);
            m_hasPendingLoadEvent = false;
        }

        // Cancel error events that belong to the previous load, which is now
        // cancelled by changing the src attribute. If newImage is null and
        // m_hasPendingErrorEvent is true, we know the error event has been just
        // posted by this load and we should not cancel the event.
        // FIXME: If both previous load and this one got blocked with an error, we
        // can receive one error event instead of two.
        if (m_hasPendingErrorEvent && newImage) {
            errorEventSender().cancelEvent(this);
            m_hasPendingErrorEvent = false;
        }

        m_image = newImage;
        m_hasPendingLoadEvent = newImage;
        m_imageComplete = !newImage;

        updateLayoutObject();
        // If newImage exists and is cached, addObserver() will result in the load
        // event being queued to fire. Ensure this happens after beforeload is
        // dispatched.
        if (newImage) {
            newImage->addObserver(this);
        }
        if (oldImage) {
            oldImage->removeObserver(this);
        }
    }

    if (LayoutImageResource* imageResource = layoutImageResource())
        imageResource->resetAnimation();

    // Only consider updating the protection ref-count of the Element immediately
    // before returning from this function as doing so might result in the
    // destruction of this ImageLoader.
    updatedHasPendingEvent();
}