+2014-08-10 Tim Horton <timothy_horton@apple.com>
+
+ Refactor ServiceOverlayController in preparation for fading between highlights
+ https://bugs.webkit.org/show_bug.cgi?id=135787
+ <rdar://problem/17935736>
+
+ Reviewed by Brady Eidson.
+
+ Rework ServicesOverlayController so that we always keep a set of generic
+ "potential highlights", which are refcounted Highlight objects and
+ wrap a DDHighlightRef, as well as a type (Selection or TelephoneNumber),
+ Range (only used in the case of TelephoneNumber), and potentially more
+ things in the future (like, say, fade state!).
+
+ We eagerly update the list of potential highlights when the selection or set
+ of detected telephone numbers changes, and use this information to install
+ or uninstall the page overlay as needed.
+
+ When we need to recompute the "active" highlight from this set (for example,
+ we need to handle a mouse event or paint the highlight), we look through
+ the set of potential highlights and decide. This moves the "active" highlight
+ decision logic into one small and confined place.
+
+ * WebProcess/WebPage/ServicesOverlayController.h:
+ (WebKit::ServicesOverlayController::Highlight):
+ Add the new aforementioned refcounted Highlight class.
+ Rename m_lastHoveredHighlightChangeTime to m_lastActiveHighlightChangeTime.
+ Make m_webPage a reference.
+ The rest is just added/removed/adjusted functions for the refactoring.
+
+ (WebKit::TelephoneNumberData::TelephoneNumberData): Deleted.
+ * WebProcess/WebPage/mac/ServicesOverlayController.mm:
+ (WebKit::ServicesOverlayController::Highlight::createForSelection):
+ (WebKit::ServicesOverlayController::Highlight::createForTelephoneNumber):
+ Create Highlights for the two different highlight types.
+
+ (WebKit::ServicesOverlayController::ServicesOverlayController):
+ (WebKit::ServicesOverlayController::willMoveToWebPage):
+ Our WebPage pointer is always valid because it owns us; don't clear it.
+ We need to keep it around so that we can uninstall the overlay and
+ install it again later, anyway.
+
+ (WebKit::ServicesOverlayController::selectionRectsDidChange):
+ (WebKit::ServicesOverlayController::selectedTelephoneNumberRangesChanged):
+ When selection rects or detected telephone numbers change, rebuild potential highlights.
+ This will have the side-effect of installing the overlay if needed.
+
+ (WebKit::ServicesOverlayController::mouseIsOverHighlight):
+ Make this function take a Highlight instead of a DDHighlightRef.
+
+ (WebKit::ServicesOverlayController::remainingTimeUntilHighlightShouldBeShown):
+ Make this function take a Highlight instead of a DDHighlightRef.
+
+ (WebKit::ServicesOverlayController::drawHighlight):
+ Make this function take a Highlight instead of a DDHighlightRef.
+ There's no reason to do the translation separately from the layer blit,
+ also allowing us to avoid the StateSaver.
+
+ (WebKit::ServicesOverlayController::drawRect):
+ drawRect now always paints the active highlight, instead of duplicating
+ logic about which highlight should be active.
+ Also, it will update the active highlight before painting.
+ We no longer need to re-determine whether the active highlight's phone
+ number range is still a valid phone number range, because we rebuild
+ the potential highlights whenever the set of phone number ranges changes.
+
+ (WebKit::ServicesOverlayController::clearActiveHighlight):
+ Mostly an adoption of new names.
+
+ (WebKit::ServicesOverlayController::removeAllPotentialHighlightsOfType):
+ Run through the list of potential highlights and remove any of the given type.
+ The two highlight building functions use this helper to clear the old ones before building.
+
+ (WebKit::ServicesOverlayController::buildPhoneNumberHighlights):
+ (WebKit::ServicesOverlayController::buildSelectionHighlight):
+ Rebuild the list of potential highlights, replacing all highlights of
+ the given type with new ones.
+
+ (WebKit::ServicesOverlayController::hasRelevantSelectionServices):
+ Factor out the code that decides whether our current selection is
+ viable for servicing based on whether we have plain-text and/or rich-text services.
+
+ (WebKit::ServicesOverlayController::didRebuildPotentialHighlights):
+ When rebuilding potential highlights, if we have no potential highlights at all,
+ uninstall the page overlay; we don't need mouse tracking and don't need to
+ paint anything. This improves memory use and compositing performance significantly,
+ where previously we were leaving the overlay up forever after creating it.
+
+ If we have either detected telephone numbers or relevant selection services,
+ create and install the overlay if it doesn't already exist.
+
+ (WebKit::ServicesOverlayController::createOverlayIfNeeded):
+ This just moved from elsehwere, except that it now uses FadeMode::DoNotFade.
+ It doesn't make sense to fade on install/uninstall (which happens even before hover)
+ but not on changing the active highlight; fading will be re-addressed in the next patch.
+
+ (WebKit::ServicesOverlayController::highlightsAreEquivalent):
+ Determine whether two highlights are equivalent. While we may have
+ created a new Highlight at rebuild time, if two telephone number
+ highlights have equivalent ranges, there's no need to 'transition' to the new one.
+
+ (WebKit::ServicesOverlayController::determineActiveHighlight):
+ Run through the list of services, and try to find one that is hovered.
+ We prefer telephone number highlights to selection highlights, and
+ we will never make a selection highlight active if it is both
+ not serviceable and there are no telephone numbers to show in the combined menu.
+ This is the centralized location for determination of which highlight
+ should be considered active. If the active highlight changed, update
+ the time since last change and cancel the mouse-down tracking.
+
+ (WebKit::ServicesOverlayController::mouseEvent):
+ Adjust some comments to be more explanatory.
+ A bunch of code moved out of here and into determineActiveHighlight.
+
+ (WebKit::ServicesOverlayController::handleClick):
+ Adjust to take a reference and use Highlight instead of DDHighlightRef.
+
2014-08-10 Timothy Hatcher <timothy@apple.com>
Web Inspector: new glyphs are visible on OS X 10.9 builds
#include "PageOverlay.h"
#include <WebCore/Range.h>
#include <WebCore/Timer.h>
+#include <wtf/RefCounted.h>
typedef void* DDHighlightRef;
class WebPage;
-typedef void* DDHighlightRef;
-
-struct TelephoneNumberData {
- TelephoneNumberData(RetainPtr<DDHighlightRef> highlight, PassRefPtr<WebCore::Range> range)
- : highlight(highlight)
- , range(range)
- {
- }
-
- RetainPtr<DDHighlightRef> highlight;
- RefPtr<WebCore::Range> range;
-};
-
class ServicesOverlayController : private PageOverlay::Client {
public:
ServicesOverlayController(WebPage&);
void selectionRectsDidChange(const Vector<WebCore::LayoutRect>&, const Vector<WebCore::GapRects>&, bool isTextOnly);
private:
- void createOverlayIfNeeded();
- void handleClick(const WebCore::IntPoint&, DDHighlightRef);
- void clearHighlightState();
-
+ class Highlight : public RefCounted<Highlight> {
+ WTF_MAKE_NONCOPYABLE(Highlight);
+ public:
+ static PassRefPtr<Highlight> createForSelection(RetainPtr<DDHighlightRef>);
+ static PassRefPtr<Highlight> createForTelephoneNumber(RetainPtr<DDHighlightRef>, PassRefPtr<WebCore::Range>);
+
+ DDHighlightRef ddHighlight() const { return m_ddHighlight.get(); }
+ WebCore::Range* range() const { return m_range.get(); }
+
+ enum class Type {
+ TelephoneNumber,
+ Selection
+ };
+ Type type() const { return m_type; }
+
+ private:
+ explicit Highlight(Type type, RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<WebCore::Range> range)
+ : m_ddHighlight(ddHighlight)
+ , m_range(range)
+ , m_type(type)
+ {
+ ASSERT(m_ddHighlight);
+ ASSERT(type != Type::TelephoneNumber || m_range);
+ }
+
+ RetainPtr<DDHighlightRef> m_ddHighlight;
+ RefPtr<WebCore::Range> m_range;
+ Type m_type;
+ };
+
+ // PageOverlay::Client
virtual void pageOverlayDestroyed(PageOverlay*) override;
virtual void willMoveToWebPage(PageOverlay*, WebPage*) override;
virtual void didMoveToWebPage(PageOverlay*, WebPage*) override;
virtual void drawRect(PageOverlay*, WebCore::GraphicsContext&, const WebCore::IntRect& dirtyRect) override;
virtual bool mouseEvent(PageOverlay*, const WebMouseEvent&) override;
- bool drawTelephoneNumberHighlightIfVisible(WebCore::GraphicsContext&, const WebCore::IntRect& dirtyRect);
- void drawSelectionHighlight(WebCore::GraphicsContext&, const WebCore::IntRect& dirtyRect);
- void drawHighlight(DDHighlightRef, WebCore::GraphicsContext&);
+ void createOverlayIfNeeded();
+ void handleClick(const WebCore::IntPoint&, Highlight&);
+
+ void drawHighlight(Highlight&, WebCore::GraphicsContext&);
+
+ void removeAllPotentialHighlightsOfType(Highlight::Type);
+ void buildPhoneNumberHighlights();
+ void buildSelectionHighlight();
+ void didRebuildPotentialHighlights();
- void establishHoveredTelephoneHighlight(bool& mouseIsOverButton);
- void maybeCreateSelectionHighlight();
+ void determineActiveHighlight(bool& mouseIsOverButton);
+ void clearActiveHighlight();
- void clearSelectionHighlight();
- void clearHoveredTelephoneNumberHighlight();
+ bool hasRelevantSelectionServices();
- bool mouseIsOverHighlight(DDHighlightRef, bool& mouseIsOverButton) const;
+ bool mouseIsOverHighlight(Highlight&, bool& mouseIsOverButton) const;
std::chrono::milliseconds remainingTimeUntilHighlightShouldBeShown() const;
void repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&);
- WebPage* m_webPage;
+ static bool highlightsAreEquivalent(const Highlight* a, const Highlight* b);
+
+ WebPage& m_webPage;
PageOverlay* m_servicesOverlay;
-
+
+ RefPtr<Highlight> m_activeHighlight;
+ HashSet<RefPtr<Highlight>> m_potentialHighlights;
+
+ Vector<RefPtr<WebCore::Range>> m_currentTelephoneNumberRanges;
Vector<WebCore::LayoutRect> m_currentSelectionRects;
- RetainPtr<DDHighlightRef> m_selectionHighlight;
bool m_isTextOnly;
- std::chrono::steady_clock::time_point m_lastSelectionChangeTime;
- std::chrono::steady_clock::time_point m_lastHoveredHighlightChangeTime;
- Vector<RefPtr<WebCore::Range>> m_currentTelephoneNumberRanges;
- Vector<RetainPtr<DDHighlightRef>> m_telephoneNumberHighlights;
- std::unique_ptr<TelephoneNumberData> m_hoveredTelephoneNumberData;
+ std::chrono::steady_clock::time_point m_lastSelectionChangeTime;
+ std::chrono::steady_clock::time_point m_lastActiveHighlightChangeTime;
- RetainPtr<DDHighlightRef> m_currentHoveredHighlight;
- RetainPtr<DDHighlightRef> m_currentMouseDownOnButtonHighlight;
+ RefPtr<Highlight> m_currentMouseDownOnButtonHighlight;
+ WebCore::IntPoint m_mousePosition;
WebCore::Timer<ServicesOverlayController> m_repaintHighlightTimer;
-
- WebCore::IntPoint m_mousePosition;
};
} // namespace WebKit
namespace WebKit {
+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForSelection(RetainPtr<DDHighlightRef> ddHighlight)
+{
+ return adoptRef(new Highlight(Type::Selection, ddHighlight, nullptr));
+}
+
+PassRefPtr<ServicesOverlayController::Highlight> ServicesOverlayController::Highlight::createForTelephoneNumber(RetainPtr<DDHighlightRef> ddHighlight, PassRefPtr<Range> range)
+{
+ return adoptRef(new Highlight(Type::TelephoneNumber, ddHighlight, range));
+}
+
static IntRect textQuadsToBoundingRectForRange(Range& range)
{
Vector<FloatQuad> textQuads;
}
ServicesOverlayController::ServicesOverlayController(WebPage& webPage)
- : m_webPage(&webPage)
+ : m_webPage(webPage)
, m_servicesOverlay(nullptr)
, m_isTextOnly(false)
, m_repaintHighlightTimer(this, &ServicesOverlayController::repaintHighlightTimerFired)
ASSERT(m_servicesOverlay);
m_servicesOverlay = nullptr;
-
- ASSERT(m_webPage);
- m_webPage = nullptr;
}
void ServicesOverlayController::didMoveToWebPage(PageOverlay*, WebPage*)
{
}
-void ServicesOverlayController::createOverlayIfNeeded()
-{
- if (m_servicesOverlay) {
- m_servicesOverlay->setNeedsDisplay();
- return;
- }
-
- if (m_currentTelephoneNumberRanges.isEmpty() && (!WebProcess::shared().hasSelectionServices() || m_currentSelectionRects.isEmpty()))
- return;
-
- RefPtr<PageOverlay> overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
- m_servicesOverlay = overlay.get();
- m_webPage->installPageOverlay(overlay.release(), PageOverlay::FadeMode::Fade);
- m_servicesOverlay->setNeedsDisplay();
-}
-
static const uint8_t AlignmentNone = 0;
static const uint8_t AlignmentLeft = 1 << 0;
static const uint8_t AlignmentRight = 1 << 1;
void ServicesOverlayController::selectionRectsDidChange(const Vector<LayoutRect>& rects, const Vector<GapRects>& gapRects, bool isTextOnly)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
- clearSelectionHighlight();
m_currentSelectionRects = rects;
m_isTextOnly = isTextOnly;
LOG(Services, "ServicesOverlayController - Selection rects changed - Now have %lu\n", rects.size());
- createOverlayIfNeeded();
+ buildSelectionHighlight();
#else
UNUSED_PARAM(rects);
#endif
#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED > 1090
LOG(Services, "ServicesOverlayController - Telephone number ranges changed - Had %lu, now have %lu\n", m_currentTelephoneNumberRanges.size(), ranges.size());
m_currentTelephoneNumberRanges = ranges;
- m_telephoneNumberHighlights.clear();
- m_telephoneNumberHighlights.resize(ranges.size());
- createOverlayIfNeeded();
+ buildPhoneNumberHighlights();
#else
UNUSED_PARAM(ranges);
#endif
}
-void ServicesOverlayController::clearHighlightState()
-{
- clearSelectionHighlight();
- clearHoveredTelephoneNumberHighlight();
-
- m_telephoneNumberHighlights.clear();
-}
-
-void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
-{
- if (m_currentSelectionRects.isEmpty() && m_currentTelephoneNumberRanges.isEmpty()) {
- clearHighlightState();
- return;
- }
-
- if (drawTelephoneNumberHighlightIfVisible(graphicsContext, dirtyRect))
- return;
-
- drawSelectionHighlight(graphicsContext, dirtyRect);
-}
-
-void ServicesOverlayController::drawSelectionHighlight(WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
-{
- // It's possible to end up drawing the selection highlight before we've actually received the selection rects.
- // If that happens we'll end up here again once we have the rects.
- if (m_currentSelectionRects.isEmpty())
- return;
-
- // If there are no appropriate installed selection services and we have no phone numbers detected, then we have nothing to draw.
- if ((!WebProcess::shared().hasSelectionServices() || (!WebProcess::shared().hasRichContentServices() && !m_isTextOnly)) && m_currentTelephoneNumberRanges.isEmpty())
- return;
-
- if (!m_selectionHighlight)
- maybeCreateSelectionHighlight();
-
- if (m_selectionHighlight)
- drawHighlight(m_selectionHighlight.get(), graphicsContext);
-}
-
-bool ServicesOverlayController::drawTelephoneNumberHighlightIfVisible(WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
-{
- // Make sure the hovered telephone number highlight is still hovered.
- if (m_hoveredTelephoneNumberData) {
- Boolean onButton;
- if (!DDHighlightPointIsOnHighlight(m_hoveredTelephoneNumberData->highlight.get(), (CGPoint)m_mousePosition, &onButton))
- clearHoveredTelephoneNumberHighlight();
-
- bool foundMatchingRange = false;
-
- // Make sure the hovered highlight still corresponds to a current telephone number range.
- for (auto& range : m_currentTelephoneNumberRanges) {
- if (areRangesEqual(range.get(), m_hoveredTelephoneNumberData->range.get())) {
- foundMatchingRange = true;
- break;
- }
- }
-
- if (!foundMatchingRange)
- clearHoveredTelephoneNumberHighlight();
- }
-
- // Found out which - if any - telephone number is hovered.
- if (!m_hoveredTelephoneNumberData) {
- bool mouseIsOverButton;
- establishHoveredTelephoneHighlight(mouseIsOverButton);
- }
-
- // If a telephone number is actually hovered, draw it.
- if (m_hoveredTelephoneNumberData) {
- drawHighlight(m_hoveredTelephoneNumberData->highlight.get(), graphicsContext);
- return true;
- }
-
- return false;
-}
-
-bool ServicesOverlayController::mouseIsOverHighlight(DDHighlightRef highlight, bool& mouseIsOverButton) const
+bool ServicesOverlayController::mouseIsOverHighlight(Highlight& highlight, bool& mouseIsOverButton) const
{
Boolean onButton;
- bool hovered = DDHighlightPointIsOnHighlight(highlight, (CGPoint)m_mousePosition, &onButton);
+ bool hovered = DDHighlightPointIsOnHighlight(highlight.ddHighlight(), (CGPoint)m_mousePosition, &onButton);
mouseIsOverButton = onButton;
return hovered;
}
{
// Highlight hysteresis is only for selection services, because telephone number highlights are already much more stable
// by virtue of being expanded to include the entire telephone number.
- if (m_hoveredTelephoneNumberData)
+ if (m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
return std::chrono::milliseconds::zero();
std::chrono::steady_clock::duration minimumTimeUntilHighlightShouldBeShown = 200_ms;
auto now = std::chrono::steady_clock::now();
auto timeSinceLastSelectionChange = now - m_lastSelectionChangeTime;
- auto timeSinceMouseOverSelection = now - m_lastHoveredHighlightChangeTime;
+ auto timeSinceHighlightBecameActive = now - m_lastActiveHighlightChangeTime;
- return std::chrono::duration_cast<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceMouseOverSelection));
+ return std::chrono::duration_cast<std::chrono::milliseconds>(std::max(minimumTimeUntilHighlightShouldBeShown - timeSinceLastSelectionChange, minimumTimeUntilHighlightShouldBeShown - timeSinceHighlightBecameActive));
}
void ServicesOverlayController::repaintHighlightTimerFired(WebCore::Timer<ServicesOverlayController>&)
m_servicesOverlay->setNeedsDisplay();
}
-void ServicesOverlayController::drawHighlight(DDHighlightRef highlight, WebCore::GraphicsContext& graphicsContext)
+void ServicesOverlayController::drawHighlight(Highlight& highlight, WebCore::GraphicsContext& graphicsContext)
{
- ASSERT(highlight);
-
bool mouseIsOverButton;
if (!mouseIsOverHighlight(highlight, mouseIsOverButton)) {
LOG(Services, "ServicesOverlayController::drawHighlight - Mouse is not over highlight, so drawing nothing");
CGContextRef cgContext = graphicsContext.platformContext();
- CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(highlight, cgContext);
- CGRect highlightBoundingRect = DDHighlightGetBoundingRect(highlight);
-
- GraphicsContextStateSaver stateSaver(graphicsContext);
-
- graphicsContext.translate(toFloatSize(highlightBoundingRect.origin));
+ CGLayerRef highlightLayer = DDHighlightGetLayerWithContext(highlight.ddHighlight(), cgContext);
+ CGRect highlightBoundingRect = DDHighlightGetBoundingRect(highlight.ddHighlight());
- CGRect highlightDrawRect = highlightBoundingRect;
- highlightDrawRect.origin.x = 0;
- highlightDrawRect.origin.y = 0;
-
- CGContextDrawLayerInRect(cgContext, highlightDrawRect, highlightLayer);
+ CGContextDrawLayerInRect(cgContext, highlightBoundingRect, highlightLayer);
}
-void ServicesOverlayController::clearSelectionHighlight()
+void ServicesOverlayController::drawRect(PageOverlay* overlay, WebCore::GraphicsContext& graphicsContext, const WebCore::IntRect& dirtyRect)
{
- if (!m_selectionHighlight)
- return;
+ bool mouseIsOverButton;
+ determineActiveHighlight(mouseIsOverButton);
- if (m_currentHoveredHighlight == m_selectionHighlight)
- m_currentHoveredHighlight = nullptr;
- if (m_currentMouseDownOnButtonHighlight == m_selectionHighlight)
- m_currentMouseDownOnButtonHighlight = nullptr;
- m_selectionHighlight = nullptr;
+ if (m_activeHighlight)
+ drawHighlight(*m_activeHighlight, graphicsContext);
}
-void ServicesOverlayController::clearHoveredTelephoneNumberHighlight()
+void ServicesOverlayController::clearActiveHighlight()
{
- if (!m_hoveredTelephoneNumberData)
+ if (!m_activeHighlight)
return;
- if (m_currentHoveredHighlight == m_hoveredTelephoneNumberData->highlight)
- m_currentHoveredHighlight = nullptr;
- if (m_currentMouseDownOnButtonHighlight == m_hoveredTelephoneNumberData->highlight)
+ if (m_currentMouseDownOnButtonHighlight == m_activeHighlight)
m_currentMouseDownOnButtonHighlight = nullptr;
- m_hoveredTelephoneNumberData = nullptr;
+ m_activeHighlight = nullptr;
}
-void ServicesOverlayController::establishHoveredTelephoneHighlight(bool& mouseIsOverButton)
+void ServicesOverlayController::removeAllPotentialHighlightsOfType(Highlight::Type type)
{
- ASSERT(m_currentTelephoneNumberRanges.size() == m_telephoneNumberHighlights.size());
+ Vector<RefPtr<Highlight>> highlightsToRemove;
+ for (auto& highlight : m_potentialHighlights) {
+ if (highlight->type() == type)
+ highlightsToRemove.append(highlight);
+ }
- for (unsigned i = 0; i < m_currentTelephoneNumberRanges.size(); ++i) {
- if (!m_telephoneNumberHighlights[i]) {
- // FIXME: This will choke if the range wraps around the edge of the view.
- // What should we do in that case?
- IntRect rect = textQuadsToBoundingRectForRange(*m_currentTelephoneNumberRanges[i]);
-
- // Convert to the main document's coordinate space.
- // FIXME: It's a little crazy to call contentsToWindow and then windowToContents in order to get the right coordinate space.
- // We should consider adding conversion functions to ScrollView for contentsToDocument(). Right now, contentsToRootView() is
- // not equivalent to what we need when you have a topContentInset or a header banner.
- FrameView* viewForRange = m_currentTelephoneNumberRanges[i]->ownerDocument().view();
- if (!viewForRange)
- continue;
- FrameView& mainFrameView = *m_webPage->corePage()->mainFrame().view();
- rect.setLocation(mainFrameView.windowToContents(viewForRange->contentsToWindow(rect.location())));
+ while (!highlightsToRemove.isEmpty())
+ m_potentialHighlights.remove(highlightsToRemove.takeLast());
+}
- CGRect cgRect = rect;
- m_telephoneNumberHighlights[i] = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
- }
+void ServicesOverlayController::buildPhoneNumberHighlights()
+{
+ removeAllPotentialHighlightsOfType(Highlight::Type::TelephoneNumber);
- if (!mouseIsOverHighlight(m_telephoneNumberHighlights[i].get(), mouseIsOverButton))
+ for (unsigned i = 0; i < m_currentTelephoneNumberRanges.size(); ++i) {
+ // FIXME: This will choke if the range wraps around the edge of the view.
+ // What should we do in that case?
+ IntRect rect = textQuadsToBoundingRectForRange(*m_currentTelephoneNumberRanges[i]);
+
+ // Convert to the main document's coordinate space.
+ // FIXME: It's a little crazy to call contentsToWindow and then windowToContents in order to get the right coordinate space.
+ // We should consider adding conversion functions to ScrollView for contentsToDocument(). Right now, contentsToRootView() is
+ // not equivalent to what we need when you have a topContentInset or a header banner.
+ FrameView* viewForRange = m_currentTelephoneNumberRanges[i]->ownerDocument().view();
+ if (!viewForRange)
continue;
+ FrameView& mainFrameView = *m_webPage.corePage()->mainFrame().view();
+ rect.setLocation(mainFrameView.windowToContents(viewForRange->contentsToWindow(rect.location())));
- if (!m_hoveredTelephoneNumberData || m_hoveredTelephoneNumberData->highlight != m_telephoneNumberHighlights[i])
- m_hoveredTelephoneNumberData = std::make_unique<TelephoneNumberData>(m_telephoneNumberHighlights[i], m_currentTelephoneNumberRanges[i]);
+ CGRect cgRect = rect;
+ RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, &cgRect, 1, mainFrameView.visibleContentRect(), DDHighlightOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
- m_servicesOverlay->setNeedsDisplay();
- return;
+ m_potentialHighlights.add(Highlight::createForTelephoneNumber(ddHighlight, m_currentTelephoneNumberRanges[i]));
}
- clearHoveredTelephoneNumberHighlight();
- mouseIsOverButton = false;
+ didRebuildPotentialHighlights();
}
-void ServicesOverlayController::maybeCreateSelectionHighlight()
+void ServicesOverlayController::buildSelectionHighlight()
{
- ASSERT(!m_selectionHighlight);
- ASSERT(m_servicesOverlay);
+ removeAllPotentialHighlightsOfType(Highlight::Type::Selection);
Vector<CGRect> cgRects;
cgRects.reserveCapacity(m_currentSelectionRects.size());
cgRects.append((CGRect)pixelSnappedIntRect(rect));
if (!cgRects.isEmpty()) {
- CGRect visibleRect = m_webPage->corePage()->mainFrame().view()->visibleContentRect();
- m_selectionHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
+ CGRect visibleRect = m_webPage.corePage()->mainFrame().view()->visibleContentRect();
+ RetainPtr<DDHighlightRef> ddHighlight = adoptCF(DDHighlightCreateWithRectsInVisibleRectWithStyleAndDirection(nullptr, cgRects.begin(), cgRects.size(), visibleRect, DDHighlightNoOutlineWithArrow, YES, NSWritingDirectionNatural, NO, YES));
+
+ m_potentialHighlights.add(Highlight::createForSelection(ddHighlight));
+ }
+ didRebuildPotentialHighlights();
+}
+
+bool ServicesOverlayController::hasRelevantSelectionServices()
+{
+ return (m_isTextOnly && WebProcess::shared().hasSelectionServices()) || WebProcess::shared().hasRichContentServices();
+}
+
+void ServicesOverlayController::didRebuildPotentialHighlights()
+{
+ if (m_potentialHighlights.isEmpty()) {
+ if (m_servicesOverlay)
+ m_webPage.uninstallPageOverlay(m_servicesOverlay);
+ return;
+ }
+
+ if (m_currentTelephoneNumberRanges.isEmpty() && !hasRelevantSelectionServices())
+ return;
+
+ createOverlayIfNeeded();
+}
+
+void ServicesOverlayController::createOverlayIfNeeded()
+{
+ if (m_servicesOverlay) {
m_servicesOverlay->setNeedsDisplay();
+ return;
}
+
+ RefPtr<PageOverlay> overlay = PageOverlay::create(this, PageOverlay::OverlayType::Document);
+ m_servicesOverlay = overlay.get();
+ m_webPage.installPageOverlay(overlay.release(), PageOverlay::FadeMode::DoNotFade);
+ m_servicesOverlay->setNeedsDisplay();
}
-bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& event)
+bool ServicesOverlayController::highlightsAreEquivalent(const Highlight* a, const Highlight* b)
{
- m_mousePosition = m_webPage->corePage()->mainFrame().view()->rootViewToContents(event.position());
+ if (a == b)
+ return true;
- DDHighlightRef oldHoveredHighlight = m_currentHoveredHighlight.get();
+ if (!a || !b)
+ return false;
- bool mouseIsOverButton = false;
- establishHoveredTelephoneHighlight(mouseIsOverButton);
- if (m_hoveredTelephoneNumberData) {
- ASSERT(m_hoveredTelephoneNumberData->highlight);
- m_currentHoveredHighlight = m_hoveredTelephoneNumberData->highlight;
- } else {
- if (!m_selectionHighlight)
- maybeCreateSelectionHighlight();
+ if (a->type() == Highlight::Type::TelephoneNumber && b->type() == Highlight::Type::TelephoneNumber && areRangesEqual(a->range(), b->range()))
+ return true;
- if (m_selectionHighlight && mouseIsOverHighlight(m_selectionHighlight.get(), mouseIsOverButton))
- m_currentHoveredHighlight = m_selectionHighlight;
- else
- m_currentHoveredHighlight = nullptr;
+ return false;
+}
+
+void ServicesOverlayController::determineActiveHighlight(bool& mouseIsOverActiveHighlightButton)
+{
+ mouseIsOverActiveHighlightButton = false;
+
+ RefPtr<Highlight> oldActiveHighlight = m_activeHighlight.release();
+
+ for (auto& highlight : m_potentialHighlights) {
+ if (highlight->type() == Highlight::Type::Selection) {
+ // If we've already found a new active highlight, and it's
+ // a telephone number highlight, prefer that over this selection highlight.
+ if (m_activeHighlight && m_activeHighlight->type() == Highlight::Type::TelephoneNumber)
+ continue;
+
+ // If this highlight has no compatible services, it can't be active, unless we have telephone number highlights to show in the combined menu.
+ if (m_currentTelephoneNumberRanges.isEmpty() && !hasRelevantSelectionServices())
+ continue;
+ }
+
+ // If this highlight isn't hovered, it can't be active.
+ bool mouseIsOverButton;
+ if (!mouseIsOverHighlight(*highlight, mouseIsOverButton))
+ continue;
+
+ m_activeHighlight = highlight;
+ mouseIsOverActiveHighlightButton = mouseIsOverButton;
}
- if (oldHoveredHighlight != m_currentHoveredHighlight) {
- m_lastHoveredHighlightChangeTime = std::chrono::steady_clock::now();
+ if (!highlightsAreEquivalent(oldActiveHighlight.get(), m_activeHighlight.get())) {
+ m_lastActiveHighlightChangeTime = std::chrono::steady_clock::now();
m_servicesOverlay->setNeedsDisplay();
+ m_currentMouseDownOnButtonHighlight = nullptr;
}
+}
+
+bool ServicesOverlayController::mouseEvent(PageOverlay*, const WebMouseEvent& event)
+{
+ m_mousePosition = m_webPage.corePage()->mainFrame().view()->rootViewToContents(event.position());
+
+ bool mouseIsOverActiveHighlightButton = false;
+ determineActiveHighlight(mouseIsOverActiveHighlightButton);
- // If this event has nothing to do with the left button, it clears the current mouse down tracking and we're done processing it.
+ // Cancel the potential click if any button other than the left button changes state, and ignore the event.
if (event.button() != WebMouseEvent::LeftButton) {
m_currentMouseDownOnButtonHighlight = nullptr;
return false;
}
- // Check and see if the mouse went up and we have a current mouse down highlight button.
+ // If the mouse lifted while still over the highlight button that it went down on, then that is a click.
if (event.type() == WebEvent::MouseUp) {
- RetainPtr<DDHighlightRef> mouseDownHighlight = WTF::move(m_currentMouseDownOnButtonHighlight);
+ RefPtr<Highlight> mouseDownHighlight = m_currentMouseDownOnButtonHighlight;
+ m_currentMouseDownOnButtonHighlight = nullptr;
- // If the mouse lifted while still over the highlight button that it went down on, then that is a click.
- if (mouseIsOverButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) {
- handleClick(m_mousePosition, mouseDownHighlight.get());
+ if (mouseIsOverActiveHighlightButton && mouseDownHighlight && remainingTimeUntilHighlightShouldBeShown() <= std::chrono::steady_clock::duration::zero()) {
+ handleClick(m_mousePosition, *mouseDownHighlight);
return true;
}
return false;
}
- // Check and see if the mouse moved within the confines of the DD highlight button.
+ // If the mouse moved outside of the button tracking a potential click, stop tracking the click.
if (event.type() == WebEvent::MouseMove) {
- // Moving with the mouse button down is okay as long as the mouse never leaves the highlight button.
- if (m_currentMouseDownOnButtonHighlight && mouseIsOverButton)
+ if (m_currentMouseDownOnButtonHighlight && mouseIsOverActiveHighlightButton)
return true;
m_currentMouseDownOnButtonHighlight = nullptr;
return false;
}
- // Check and see if the mouse went down over a DD highlight button.
+ // If the mouse went down over the active highlight's button, track this as a potential click.
if (event.type() == WebEvent::MouseDown) {
- if (m_currentHoveredHighlight && mouseIsOverButton) {
- m_currentMouseDownOnButtonHighlight = m_currentHoveredHighlight;
+ if (m_activeHighlight && mouseIsOverActiveHighlightButton) {
+ m_currentMouseDownOnButtonHighlight = m_activeHighlight;
m_servicesOverlay->setNeedsDisplay();
return true;
}
return false;
}
-
+
return false;
}
-void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, DDHighlightRef highlight)
+void ServicesOverlayController::handleClick(const WebCore::IntPoint& clickPoint, Highlight& highlight)
{
- ASSERT(highlight);
-
- FrameView* frameView = m_webPage->mainFrameView();
+ FrameView* frameView = m_webPage.mainFrameView();
if (!frameView)
return;
IntPoint windowPoint = frameView->contentsToWindow(clickPoint);
- if (highlight == m_selectionHighlight) {
+ if (highlight.type() == Highlight::Type::Selection) {
Vector<String> selectedTelephoneNumbers;
selectedTelephoneNumbers.reserveCapacity(m_currentTelephoneNumberRanges.size());
for (auto& range : m_currentTelephoneNumberRanges)
selectedTelephoneNumbers.append(range->text());
- m_webPage->handleSelectionServiceClick(m_webPage->corePage()->mainFrame().selection(), selectedTelephoneNumbers, windowPoint);
- } else if (m_hoveredTelephoneNumberData && m_hoveredTelephoneNumberData->highlight == highlight)
- m_webPage->handleTelephoneNumberClick(m_hoveredTelephoneNumberData->range->text(), windowPoint);
- else
- ASSERT_NOT_REACHED();
+ m_webPage.handleSelectionServiceClick(m_webPage.corePage()->mainFrame().selection(), selectedTelephoneNumbers, windowPoint);
+ } else if (highlight.type() == Highlight::Type::TelephoneNumber)
+ m_webPage.handleTelephoneNumberClick(highlight.range()->text(), windowPoint);
}
-
+
} // namespace WebKit
#endif // #if ENABLE(SERVICE_CONTROLS) || ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(MAC)