blob: 15db87b1d98ba381ea951b1f34f8e600829825a8 [file] [log] [blame]
/*
* Copyright (C) 2021 Apple 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:
* 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. AND ITS 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 APPLE INC. OR ITS 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.
*/
#import "config.h"
#import "DataDetectorHighlight.h"
#if ENABLE(DATA_DETECTION) && PLATFORM(MAC)
#import "Chrome.h"
#import "ChromeClient.h"
#import "FloatRect.h"
#import "GraphicsContext.h"
#import "GraphicsLayer.h"
#import "GraphicsLayerCA.h"
#import "GraphicsLayerFactory.h"
#import "Page.h"
#import "PlatformCAAnimationCocoa.h"
#import "PlatformCALayer.h"
#import <QuartzCore/QuartzCore.h>
#import <wtf/Seconds.h>
#import <pal/mac/DataDetectorsSoftLink.h>
namespace WebCore {
constexpr Seconds highlightFadeAnimationDuration = 300_ms;
Ref<DataDetectorHighlight> DataDetectorHighlight::createForSelection(Page& page, DataDetectorHighlightClient& client, RetainPtr<DDHighlightRef>&& ddHighlight, SimpleRange&& range)
{
return adoptRef(*new DataDetectorHighlight(page, client, DataDetectorHighlight::Type::Selection, WTFMove(ddHighlight), WTFMove(range)));
}
Ref<DataDetectorHighlight> DataDetectorHighlight::createForTelephoneNumber(Page& page, DataDetectorHighlightClient& client, RetainPtr<DDHighlightRef>&& ddHighlight, SimpleRange&& range)
{
return adoptRef(*new DataDetectorHighlight(page, client, DataDetectorHighlight::Type::TelephoneNumber, WTFMove(ddHighlight), WTFMove(range)));
}
Ref<DataDetectorHighlight> DataDetectorHighlight::createForImageOverlay(Page& page, DataDetectorHighlightClient& client, RetainPtr<DDHighlightRef>&& ddHighlight, SimpleRange&& range)
{
return adoptRef(*new DataDetectorHighlight(page, client, DataDetectorHighlight::Type::ImageOverlay, WTFMove(ddHighlight), WTFMove(range)));
}
DataDetectorHighlight::DataDetectorHighlight(Page& page, DataDetectorHighlightClient& client, Type type, RetainPtr<DDHighlightRef>&& ddHighlight, SimpleRange&& range)
: m_client(client)
, m_page(page)
, m_range(WTFMove(range))
, m_graphicsLayer(GraphicsLayer::create(page.chrome().client().graphicsLayerFactory(), *this))
, m_type(type)
{
ASSERT(ddHighlight);
m_graphicsLayer->setDrawsContent(true);
setHighlight(ddHighlight.get());
// Set directly on the PlatformCALayer so that when we leave the 'from' value implicit
// in our animations, we get the right initial value regardless of flush timing.
downcast<GraphicsLayerCA>(layer()).platformCALayer()->setOpacity(0);
}
void DataDetectorHighlight::setHighlight(DDHighlightRef highlight)
{
if (!PAL::isDataDetectorsFrameworkAvailable())
return;
if (!m_client)
return;
m_highlight = highlight;
if (!m_highlight)
return;
CGRect highlightBoundingRect = PAL::softLink_DataDetectors_DDHighlightGetBoundingRect(m_highlight.get());
m_graphicsLayer->setPosition(FloatPoint(highlightBoundingRect.origin));
m_graphicsLayer->setSize(FloatSize(highlightBoundingRect.size));
m_graphicsLayer->setNeedsDisplay();
}
void DataDetectorHighlight::invalidate()
{
layer().removeFromParent();
m_client = nullptr;
m_page = nullptr;
}
void DataDetectorHighlight::notifyFlushRequired(const GraphicsLayer*)
{
if (!m_page)
return;
m_page->scheduleRenderingUpdate(RenderingUpdateStep::LayerFlush);
}
void DataDetectorHighlight::paintContents(const GraphicsLayer*, GraphicsContext& graphicsContext, const FloatRect&, GraphicsLayerPaintBehavior)
{
if (!PAL::isDataDetectorsFrameworkAvailable())
return;
// FIXME: This needs to be moved into GraphicsContext as a DisplayList-compatible drawing command.
if (!graphicsContext.hasPlatformContext()) {
ASSERT_NOT_REACHED();
return;
}
CGContextRef cgContext = graphicsContext.platformContext();
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
CGLayerRef highlightLayer = PAL::softLink_DataDetectors_DDHighlightGetLayerWithContext(highlight(), cgContext);
ALLOW_DEPRECATED_DECLARATIONS_END
CGRect highlightBoundingRect = PAL::softLink_DataDetectors_DDHighlightGetBoundingRect(highlight());
highlightBoundingRect.origin = CGPointZero;
CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
}
float DataDetectorHighlight::deviceScaleFactor() const
{
if (!m_page)
return 1;
return m_page->deviceScaleFactor();
}
void DataDetectorHighlight::fadeIn()
{
RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setDuration:highlightFadeAnimationDuration.seconds()];
[animation setFillMode:kCAFillModeForwards];
[animation setRemovedOnCompletion:false];
[animation setToValue:@1];
auto platformAnimation = PlatformCAAnimationCocoa::create(animation.get());
downcast<GraphicsLayerCA>(layer()).platformCALayer()->addAnimationForKey("FadeHighlightIn"_s, platformAnimation.get());
}
void DataDetectorHighlight::fadeOut()
{
RetainPtr<CABasicAnimation> animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setDuration:highlightFadeAnimationDuration.seconds()];
[animation setFillMode:kCAFillModeForwards];
[animation setRemovedOnCompletion:false];
[animation setToValue:@0];
[CATransaction begin];
[CATransaction setCompletionBlock:[protectedSelf = Ref { *this }]() mutable {
protectedSelf->didFinishFadeOutAnimation();
}];
auto platformAnimation = PlatformCAAnimationCocoa::create(animation.get());
downcast<GraphicsLayerCA>(layer()).platformCALayer()->addAnimationForKey("FadeHighlightOut"_s, platformAnimation.get());
[CATransaction commit];
}
void DataDetectorHighlight::didFinishFadeOutAnimation()
{
if (!m_client)
return;
if (m_client->activeHighlight() == this)
return;
layer().removeFromParent();
}
bool areEquivalent(const DataDetectorHighlight* a, const DataDetectorHighlight* b)
{
if (a == b)
return true;
if (!a || !b)
return false;
return a->type() == b->type() && a->range() == b->range();
}
} // namespace WebCore
#endif // ENABLE(DATA_DETECTION) && PLATFORM(MAC)