/* * Copyright (C) 2011-2021 Apple Inc. All rights reserved. * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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 "config.h" #include "CSSCrossfadeValue.h" #include "AnimationUtilities.h" #include "CSSImageValue.h" #include "CachedImage.h" #include "CachedResourceLoader.h" #include "CrossfadeGeneratedImage.h" #include "RenderElement.h" #include "StyleBuilderState.h" #include "StyleCachedImage.h" namespace WebCore { static inline double blendFunc(double from, double to, const BlendingContext& context) { return blend(from, to, context); } static bool subimageKnownToBeOpaque(const CSSValue& value, const RenderElement& renderer) { if (is(value)) return downcast(value).knownToBeOpaque(renderer); if (is(value)) return downcast(value).knownToBeOpaque(renderer); ASSERT_NOT_REACHED(); return false; } inline CSSCrossfadeValue::SubimageObserver::SubimageObserver(CSSCrossfadeValue& owner) : m_owner(owner) { } void CSSCrossfadeValue::SubimageObserver::imageChanged(CachedImage*, const IntRect*) { m_owner.crossfadeChanged(); } inline CSSCrossfadeValue::CSSCrossfadeValue(Ref&& fromValue, Ref&& toValue, Ref&& percentageValue, bool prefixed) : CSSImageGeneratorValue(CrossfadeClass) , m_fromValue(WTFMove(fromValue)) , m_toValue(WTFMove(toValue)) , m_percentageValue(WTFMove(percentageValue)) , m_subimageObserver(*this) , m_isPrefixed(prefixed) { } Ref CSSCrossfadeValue::create(Ref&& fromValue, Ref&& toValue, Ref&& percentageValue, bool prefixed) { return adoptRef(*new CSSCrossfadeValue(WTFMove(fromValue), WTFMove(toValue), WTFMove(percentageValue), prefixed)); } CSSCrossfadeValue::~CSSCrossfadeValue() { if (m_cachedFromImage) m_cachedFromImage->removeClient(m_subimageObserver); if (m_cachedToImage) m_cachedToImage->removeClient(m_subimageObserver); } String CSSCrossfadeValue::customCSSText() const { return makeString(m_isPrefixed ? "-webkit-" : "", "cross-fade(", m_fromValue->cssText(), ", ", m_toValue->cssText(), ", ", m_percentageValue->cssText(), ')'); } FloatSize CSSCrossfadeValue::fixedSize(const RenderElement& renderer) { float percentage = m_percentageValue->floatValue(); float inversePercentage = 1 - percentage; // FIXME: Skip Content Security Policy check when cross fade is applied to an element in a user agent shadow tree. // See . auto options = CachedResourceLoader::defaultCachedResourceOptions(); auto& cachedResourceLoader = renderer.document().cachedResourceLoader(); auto* cachedFromImage = cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options); auto* cachedToImage = cachedImageForCSSValue(m_toValue, cachedResourceLoader, options); if (!cachedFromImage || !cachedToImage) return FloatSize(); FloatSize fromImageSize = cachedFromImage->imageForRenderer(&renderer)->size(); FloatSize toImageSize = cachedToImage->imageForRenderer(&renderer)->size(); // Rounding issues can cause transitions between images of equal size to return // a different fixed size; avoid performing the interpolation if the images are the same size. if (fromImageSize == toImageSize) return fromImageSize; return fromImageSize * inversePercentage + toImageSize * percentage; } bool CSSCrossfadeValue::isPending() const { return CSSImageGeneratorValue::subimageIsPending(m_fromValue) || CSSImageGeneratorValue::subimageIsPending(m_toValue); } bool CSSCrossfadeValue::knownToBeOpaque(const RenderElement& renderer) const { return subimageKnownToBeOpaque(m_fromValue, renderer) && subimageKnownToBeOpaque(m_toValue, renderer); } void CSSCrossfadeValue::loadSubimages(CachedResourceLoader& cachedResourceLoader, const ResourceLoaderOptions& options) { auto oldCachedFromImage = m_cachedFromImage; auto oldCachedToImage = m_cachedToImage; m_cachedFromImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options); m_cachedToImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_toValue, cachedResourceLoader, options); if (m_cachedFromImage != oldCachedFromImage) { if (oldCachedFromImage) oldCachedFromImage->removeClient(m_subimageObserver); if (m_cachedFromImage) m_cachedFromImage->addClient(m_subimageObserver); } if (m_cachedToImage != oldCachedToImage) { if (oldCachedToImage) oldCachedToImage->removeClient(m_subimageObserver); if (m_cachedToImage) m_cachedToImage->addClient(m_subimageObserver); } // FIXME: Unclear why this boolean adds any value; for now keeping it around to avoid changing semantics. m_subimagesAreReady = true; } Image* CSSCrossfadeValue::image(RenderElement& renderer, const FloatSize& size) { if (size.isEmpty()) return nullptr; // FIXME: Skip Content Security Policy check when cross fade is applied to an element in a user agent shadow tree. // See . auto options = CachedResourceLoader::defaultCachedResourceOptions(); auto& cachedResourceLoader = renderer.document().cachedResourceLoader(); auto* cachedFromImage = cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options); auto* cachedToImage = cachedImageForCSSValue(m_toValue, cachedResourceLoader, options); if (!cachedFromImage || !cachedToImage) return &Image::nullImage(); auto* fromImage = cachedFromImage->imageForRenderer(&renderer); auto* toImage = cachedToImage->imageForRenderer(&renderer); if (!fromImage || !toImage) return &Image::nullImage(); m_generatedImage = CrossfadeGeneratedImage::create(*fromImage, *toImage, m_percentageValue->floatValue(), fixedSize(renderer), size); return m_generatedImage.get(); } inline void CSSCrossfadeValue::crossfadeChanged() { if (!m_subimagesAreReady) return; for (auto& client : clients()) client.key->imageChanged(this); } bool CSSCrossfadeValue::traverseSubresources(const WTF::Function& handler) const { if (m_cachedFromImage && handler(*m_cachedFromImage)) return true; if (m_cachedToImage && handler(*m_cachedToImage)) return true; return false; } RefPtr CSSCrossfadeValue::blend(const CSSCrossfadeValue& from, const BlendingContext& context) const { ASSERT(equalInputImages(from)); if (!m_cachedToImage || !m_cachedFromImage) return nullptr; auto fromImageValue = CSSImageValue::create(*m_cachedFromImage); auto toImageValue = CSSImageValue::create(*m_cachedToImage); double fromPercentage = from.m_percentageValue->doubleValue(); if (from.m_percentageValue->isPercentage()) fromPercentage /= 100.0; double toPercentage = m_percentageValue->doubleValue(); if (m_percentageValue->isPercentage()) toPercentage /= 100.0; auto percentageValue = CSSPrimitiveValue::create(blendFunc(fromPercentage, toPercentage, context), CSSUnitType::CSS_NUMBER); return CSSCrossfadeValue::create(WTFMove(fromImageValue), WTFMove(toImageValue), WTFMove(percentageValue), from.isPrefixed() && isPrefixed()); } bool CSSCrossfadeValue::equals(const CSSCrossfadeValue& other) const { return equalInputImages(other) && compareCSSValue(m_percentageValue, other.m_percentageValue); } bool CSSCrossfadeValue::equalInputImages(const CSSCrossfadeValue& other) const { return compareCSSValue(m_fromValue, other.m_fromValue) && compareCSSValue(m_toValue, other.m_toValue); } Ref CSSCrossfadeValue::valueWithStylesResolved(Style::BuilderState& state) { auto fromValue = state.resolveImageStyles(m_fromValue.get()); auto toValue = state.resolveImageStyles(m_toValue.get()); if (fromValue.ptr() == m_fromValue.ptr() && toValue.ptr() == m_toValue.ptr()) return *this; return create(WTFMove(fromValue), WTFMove(toValue), Ref { m_percentageValue }, m_isPrefixed); } } // namespace WebCore