| /* |
| * Copyright (C) 2019 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. |
| */ |
| |
| #include "config.h" |
| #include "ContentChangeObserver.h" |
| |
| #if PLATFORM(IOS_FAMILY) |
| #include "Chrome.h" |
| #include "ChromeClient.h" |
| #include "DOMTimer.h" |
| #include "Document.h" |
| #include "HTMLIFrameElement.h" |
| #include "HTMLImageElement.h" |
| #include "Logging.h" |
| #include "NodeRenderStyle.h" |
| #include "Page.h" |
| #include "RenderDescendantIterator.h" |
| #include "Settings.h" |
| |
| namespace WebCore { |
| |
| static const Seconds maximumDelayForTimers { 400_ms }; |
| static const Seconds maximumDelayForTransitions { 300_ms }; |
| |
| bool ContentChangeObserver::isVisuallyHidden(const Node& node) |
| { |
| if (!node.renderStyle()) |
| return true; |
| |
| auto& style = *node.renderStyle(); |
| if (style.display() == DisplayType::None) |
| return true; |
| |
| if (style.visibility() == Visibility::Hidden) |
| return true; |
| |
| if (!style.opacity()) |
| return true; |
| |
| auto width = style.logicalWidth(); |
| auto height = style.logicalHeight(); |
| if ((width.isFixed() && !width.value()) || (height.isFixed() && !height.value())) |
| return true; |
| |
| auto top = style.logicalTop(); |
| auto left = style.logicalLeft(); |
| // FIXME: This is trying to check if the element is outside of the viewport. This is incorrect for many reasons. |
| if (left.isFixed() && width.isFixed() && -left.value() >= width.value()) |
| return true; |
| if (top.isFixed() && height.isFixed() && -top.value() >= height.value()) |
| return true; |
| |
| // It's a common technique used to position content offscreen. |
| if (style.hasOutOfFlowPosition() && left.isFixed() && left.value() <= -999) |
| return true; |
| |
| // FIXME: Check for other cases like zero height with overflow hidden. |
| auto maxHeight = style.maxHeight(); |
| if (maxHeight.isFixed() && !maxHeight.value()) |
| return true; |
| |
| // Special case opacity, because a descendant with non-zero opacity should still be considered hidden when one of its ancetors has opacity: 0; |
| // YouTube.com has this setup with the bottom control bar. |
| constexpr static unsigned numberOfAncestorsToCheckForOpacity = 4; |
| unsigned i = 0; |
| for (auto* parent = node.parentNode(); parent && i < numberOfAncestorsToCheckForOpacity; parent = parent->parentNode(), ++i) { |
| if (!parent->renderStyle() || !parent->renderStyle()->opacity()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ContentChangeObserver::isConsideredVisible(const Node& node) |
| { |
| if (isVisuallyHidden(node)) |
| return false; |
| |
| auto& style = *node.renderStyle(); |
| auto width = style.logicalWidth(); |
| // 1px width or height content is not considered visible. |
| if (width.isFixed() && width.value() <= 1) |
| return false; |
| |
| auto height = style.logicalHeight(); |
| if (height.isFixed() && height.value() <= 1) |
| return false; |
| |
| return true; |
| } |
| |
| enum class ElementHadRenderer { No, Yes }; |
| static bool isConsideredClickable(const Element& newlyVisibleElement, ElementHadRenderer hadRenderer) |
| { |
| auto& element = const_cast<Element&>(newlyVisibleElement); |
| if (element.isInUserAgentShadowTree()) |
| return false; |
| |
| if (is<HTMLIFrameElement>(element)) |
| return true; |
| |
| if (is<HTMLImageElement>(element)) { |
| // This is required to avoid HTMLImageElement's touch callout override logic. See rdar://problem/48937767. |
| return element.Element::willRespondToMouseClickEvents(); |
| } |
| |
| auto willRespondToMouseClickEvents = element.willRespondToMouseClickEvents(); |
| if (hadRenderer == ElementHadRenderer::No || willRespondToMouseClickEvents) |
| return willRespondToMouseClickEvents; |
| // In case when the visible content already had renderers it's not sufficient to check the "newly visible" element only since it might just be the container for the clickable content. |
| for (auto& descendant : descendantsOfType<RenderElement>(*element.renderer())) { |
| if (!descendant.element()) |
| continue; |
| if (descendant.element()->willRespondToMouseClickEvents()) |
| return true; |
| } |
| return false; |
| } |
| ContentChangeObserver::ContentChangeObserver(Document& document) |
| : m_document(document) |
| , m_contentObservationTimer([this] { completeDurationBasedContentObservation(); }) |
| { |
| } |
| |
| static void willNotProceedWithClick(Frame& mainFrame) |
| { |
| for (auto* frame = &mainFrame; frame; frame = frame->tree().traverseNext()) { |
| if (auto* document = frame->document()) |
| document->contentChangeObserver().willNotProceedWithClick(); |
| } |
| } |
| |
| void ContentChangeObserver::didRecognizeLongPress(Frame& mainFrame) |
| { |
| LOG(ContentObservation, "didRecognizeLongPress: cancel ongoing content change observing."); |
| WebCore::willNotProceedWithClick(mainFrame); |
| } |
| |
| void ContentChangeObserver::didPreventDefaultForEvent(Frame& mainFrame) |
| { |
| LOG(ContentObservation, "didPreventDefaultForEvent: cancel ongoing content change observing."); |
| WebCore::willNotProceedWithClick(mainFrame); |
| } |
| |
| void ContentChangeObserver::startContentObservationForDuration(Seconds duration) |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| ASSERT(!hasVisibleChangeState()); |
| LOG_WITH_STREAM(ContentObservation, stream << "startContentObservationForDuration: start observing the content for " << duration.milliseconds() << "ms"); |
| adjustObservedState(Event::StartedFixedObservationTimeWindow); |
| m_contentObservationTimer.startOneShot(duration); |
| } |
| |
| void ContentChangeObserver::completeDurationBasedContentObservation() |
| { |
| LOG_WITH_STREAM(ContentObservation, stream << "completeDurationBasedContentObservation: complete duration based content observing "); |
| adjustObservedState(Event::EndedFixedObservationTimeWindow); |
| } |
| |
| void ContentChangeObserver::didAddTransition(const Element& element, const Animation& transition) |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| if (hasVisibleChangeState()) |
| return; |
| if (!isObservingTransitions()) |
| return; |
| if (!transition.isDurationSet() || !transition.isPropertySet()) |
| return; |
| if (!isObservedPropertyForTransition(transition.property())) |
| return; |
| auto transitionEnd = Seconds { transition.duration() + std::max<double>(0, transition.isDelaySet() ? transition.delay() : 0) }; |
| if (transitionEnd > maximumDelayForTransitions) |
| return; |
| if (!isVisuallyHidden(element)) |
| return; |
| // In case of multiple transitions, the first tranistion wins (and it has to produce a visible content change in order to show up as hover). |
| if (m_elementsWithTransition.contains(&element)) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "didAddTransition: transition created on " << &element << " (" << transitionEnd.milliseconds() << "ms)."); |
| |
| m_elementsWithTransition.add(&element); |
| adjustObservedState(Event::AddedTransition); |
| } |
| |
| void ContentChangeObserver::didFinishTransition(const Element& element, CSSPropertyID propertyID) |
| { |
| if (!isObservedPropertyForTransition(propertyID)) |
| return; |
| if (!m_elementsWithTransition.take(&element)) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "didFinishTransition: transition finished (" << &element << ")."); |
| |
| // isConsideredClickable may trigger style update through Node::computeEditability. Let's adjust the state in the next runloop. |
| callOnMainThread([weakThis = makeWeakPtr(*this), targetElement = makeWeakPtr(element)] { |
| if (!weakThis || !targetElement) |
| return; |
| if (isVisuallyHidden(*targetElement)) { |
| weakThis->adjustObservedState(Event::EndedTransitionButFinalStyleIsNotDefiniteYet); |
| return; |
| } |
| weakThis->adjustObservedState(isConsideredClickable(*targetElement, ElementHadRenderer::Yes) ? Event::CompletedTransitionWithClickableContent : Event::CompletedTransitionWithoutClickableContent); |
| }); |
| } |
| |
| void ContentChangeObserver::didRemoveTransition(const Element& element, CSSPropertyID propertyID) |
| { |
| if (!isObservedPropertyForTransition(propertyID)) |
| return; |
| if (!m_elementsWithTransition.take(&element)) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "didRemoveTransition: transition got interrupted (" << &element << ")."); |
| |
| adjustObservedState(Event::CanceledTransition); |
| } |
| |
| void ContentChangeObserver::didInstallDOMTimer(const DOMTimer& timer, Seconds timeout, bool singleShot) |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| if (m_document.activeDOMObjectsAreSuspended()) |
| return; |
| if (timeout > maximumDelayForTimers || !singleShot) |
| return; |
| if (!isObservingDOMTimerScheduling()) |
| return; |
| if (hasVisibleChangeState()) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "didInstallDOMTimer: register this timer: (" << &timer << ") and observe when it fires."); |
| |
| registerDOMTimer(timer); |
| adjustObservedState(Event::InstalledDOMTimer); |
| } |
| |
| void ContentChangeObserver::didRemoveDOMTimer(const DOMTimer& timer) |
| { |
| if (!containsObservedDOMTimer(timer)) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "removeDOMTimer: remove registered timer (" << &timer << ")"); |
| |
| unregisterDOMTimer(timer); |
| adjustObservedState(Event::RemovedDOMTimer); |
| } |
| |
| void ContentChangeObserver::willNotProceedWithClick() |
| { |
| LOG(ContentObservation, "willNotProceedWithClick: click will not happen."); |
| adjustObservedState(Event::WillNotProceedWithClick); |
| } |
| |
| void ContentChangeObserver::domTimerExecuteDidStart(const DOMTimer& timer) |
| { |
| if (!containsObservedDOMTimer(timer)) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "startObservingDOMTimerExecute: start observing (" << &timer << ") timer callback."); |
| |
| m_observedDomTimerIsBeingExecuted = true; |
| adjustObservedState(Event::StartedDOMTimerExecution); |
| } |
| |
| void ContentChangeObserver::domTimerExecuteDidFinish(const DOMTimer& timer) |
| { |
| if (!m_observedDomTimerIsBeingExecuted) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "stopObservingDOMTimerExecute: stop observing (" << &timer << ") timer callback."); |
| |
| m_observedDomTimerIsBeingExecuted = false; |
| unregisterDOMTimer(timer); |
| adjustObservedState(Event::EndedDOMTimerExecution); |
| } |
| |
| void ContentChangeObserver::styleRecalcDidStart() |
| { |
| if (!isWaitingForStyleRecalc()) |
| return; |
| LOG(ContentObservation, "startObservingStyleRecalc: start observing style recalc."); |
| |
| m_isInObservedStyleRecalc = true; |
| adjustObservedState(Event::StartedStyleRecalc); |
| } |
| |
| void ContentChangeObserver::styleRecalcDidFinish() |
| { |
| if (!m_isInObservedStyleRecalc) |
| return; |
| LOG(ContentObservation, "stopObservingStyleRecalc: stop observing style recalc"); |
| |
| m_isInObservedStyleRecalc = false; |
| adjustObservedState(Event::EndedStyleRecalc); |
| } |
| |
| void ContentChangeObserver::renderTreeUpdateDidStart() |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| if (!isObservingContentChanges()) |
| return; |
| |
| LOG(ContentObservation, "renderTreeUpdateDidStart: RenderTree update started"); |
| m_isInObservedRenderTreeUpdate = true; |
| m_elementsWithDestroyedVisibleRenderer.clear(); |
| } |
| |
| void ContentChangeObserver::renderTreeUpdateDidFinish() |
| { |
| if (!m_isInObservedRenderTreeUpdate) |
| return; |
| |
| LOG(ContentObservation, "renderTreeUpdateDidStart: RenderTree update finished"); |
| m_isInObservedRenderTreeUpdate = false; |
| m_elementsWithDestroyedVisibleRenderer.clear(); |
| } |
| |
| void ContentChangeObserver::stopObservingPendingActivities() |
| { |
| setShouldObserveNextStyleRecalc(false); |
| setShouldObserveDOMTimerScheduling(false); |
| setShouldObserveTransitions(false); |
| clearObservedDOMTimers(); |
| clearObservedTransitions(); |
| } |
| |
| void ContentChangeObserver::reset() |
| { |
| stopObservingPendingActivities(); |
| setHasNoChangeState(); |
| setIsBetweenTouchEndAndMouseMoved(false); |
| |
| m_touchEventIsBeingDispatched = false; |
| m_isInObservedStyleRecalc = false; |
| m_isInObservedRenderTreeUpdate = false; |
| m_observedDomTimerIsBeingExecuted = false; |
| m_mouseMovedEventIsBeingDispatched = false; |
| |
| m_contentObservationTimer.stop(); |
| m_elementsWithDestroyedVisibleRenderer.clear(); |
| resetHiddenTouchTarget(); |
| } |
| |
| void ContentChangeObserver::didSuspendActiveDOMObjects() |
| { |
| LOG(ContentObservation, "didSuspendActiveDOMObjects"); |
| reset(); |
| } |
| |
| void ContentChangeObserver::willDetachPage() |
| { |
| LOG(ContentObservation, "willDetachPage"); |
| reset(); |
| } |
| |
| void ContentChangeObserver::willDestroyRenderer(const Element& element) |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| if (!m_isInObservedRenderTreeUpdate) |
| return; |
| if (hasVisibleChangeState()) |
| return; |
| LOG_WITH_STREAM(ContentObservation, stream << "willDestroyRenderer element: " << &element); |
| |
| if (!isVisuallyHidden(element)) |
| m_elementsWithDestroyedVisibleRenderer.add(&element); |
| } |
| |
| void ContentChangeObserver::contentVisibilityDidChange() |
| { |
| LOG(ContentObservation, "contentVisibilityDidChange: visible content change did happen."); |
| adjustObservedState(Event::ContentVisibilityChanged); |
| } |
| |
| void ContentChangeObserver::touchEventDidStart(PlatformEvent::Type eventType) |
| { |
| #if ENABLE(TOUCH_EVENTS) |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| if (eventType != PlatformEvent::Type::TouchStart) |
| return; |
| LOG(ContentObservation, "touchEventDidStart: touch start event started."); |
| m_touchEventIsBeingDispatched = true; |
| adjustObservedState(Event::StartedTouchStartEventDispatching); |
| #else |
| UNUSED_PARAM(eventType); |
| #endif |
| } |
| |
| void ContentChangeObserver::touchEventDidFinish() |
| { |
| #if ENABLE(TOUCH_EVENTS) |
| if (!m_touchEventIsBeingDispatched) |
| return; |
| ASSERT(m_document.settings().contentChangeObserverEnabled()); |
| LOG(ContentObservation, "touchEventDidFinish: touch start event finished."); |
| m_touchEventIsBeingDispatched = false; |
| adjustObservedState(Event::EndedTouchStartEventDispatching); |
| #endif |
| } |
| |
| void ContentChangeObserver::mouseMovedDidStart() |
| { |
| if (!m_document.settings().contentChangeObserverEnabled()) |
| return; |
| LOG(ContentObservation, "mouseMovedDidStart: mouseMoved started."); |
| m_mouseMovedEventIsBeingDispatched = true; |
| adjustObservedState(Event::StartedMouseMovedEventDispatching); |
| } |
| |
| void ContentChangeObserver::mouseMovedDidFinish() |
| { |
| if (!m_mouseMovedEventIsBeingDispatched) |
| return; |
| ASSERT(m_document.settings().contentChangeObserverEnabled()); |
| LOG(ContentObservation, "mouseMovedDidFinish: mouseMoved finished."); |
| adjustObservedState(Event::EndedMouseMovedEventDispatching); |
| m_mouseMovedEventIsBeingDispatched = false; |
| } |
| |
| void ContentChangeObserver::setShouldObserveNextStyleRecalc(bool shouldObserve) |
| { |
| if (shouldObserve) |
| LOG(ContentObservation, "Wait until next style recalc fires."); |
| m_isWaitingForStyleRecalc = shouldObserve; |
| } |
| |
| bool ContentChangeObserver::hasDeterminateState() const |
| { |
| if (hasVisibleChangeState()) |
| return true; |
| return observedContentChange() == WKContentNoChange && !hasPendingActivity(); |
| } |
| |
| void ContentChangeObserver::adjustObservedState(Event event) |
| { |
| auto resetToStartObserving = [&] { |
| setHasNoChangeState(); |
| clearObservedDOMTimers(); |
| clearObservedTransitions(); |
| setIsBetweenTouchEndAndMouseMoved(false); |
| setShouldObserveNextStyleRecalc(false); |
| setShouldObserveDOMTimerScheduling(false); |
| setShouldObserveTransitions(false); |
| ASSERT(!m_isInObservedStyleRecalc); |
| ASSERT(!m_observedDomTimerIsBeingExecuted); |
| }; |
| |
| auto adjustStateAndNotifyContentChangeIfNeeded = [&] { |
| // Demote to "no change" when there's no pending activity anymore. |
| if (observedContentChange() == WKContentIndeterminateChange && !hasPendingActivity()) |
| setHasNoChangeState(); |
| |
| // Do not notify the client unless we couldn't make the decision synchronously. |
| if (m_mouseMovedEventIsBeingDispatched) { |
| LOG(ContentObservation, "adjustStateAndNotifyContentChangeIfNeeded: in mouseMoved call. No need to notify the client."); |
| return; |
| } |
| if (isBetweenTouchEndAndMouseMoved()) { |
| LOG(ContentObservation, "adjustStateAndNotifyContentChangeIfNeeded: Not reached mouseMoved yet. No need to notify the client."); |
| return; |
| } |
| if (!hasDeterminateState()) { |
| LOG(ContentObservation, "adjustStateAndNotifyContentChangeIfNeeded: not in a determined state yet."); |
| return; |
| } |
| LOG_WITH_STREAM(ContentObservation, stream << "adjustStateAndNotifyContentChangeIfNeeded: sending observedContentChange ->" << observedContentChange()); |
| ASSERT(m_document.page()); |
| ASSERT(m_document.frame()); |
| m_document.page()->chrome().client().observedContentChange(*m_document.frame()); |
| }; |
| |
| switch (event) { |
| case Event::StartedTouchStartEventDispatching: |
| resetToStartObserving(); |
| setShouldObserveDOMTimerScheduling(true); |
| setShouldObserveTransitions(true); |
| break; |
| case Event::EndedTouchStartEventDispatching: |
| setShouldObserveDOMTimerScheduling(false); |
| setShouldObserveTransitions(false); |
| setIsBetweenTouchEndAndMouseMoved(true); |
| break; |
| case Event::WillNotProceedWithClick: |
| reset(); |
| break; |
| case Event::StartedMouseMovedEventDispatching: |
| ASSERT(!m_document.hasPendingStyleRecalc()); |
| if (!isBetweenTouchEndAndMouseMoved()) |
| resetToStartObserving(); |
| setIsBetweenTouchEndAndMouseMoved(false); |
| setShouldObserveDOMTimerScheduling(!hasVisibleChangeState()); |
| setShouldObserveTransitions(!hasVisibleChangeState()); |
| break; |
| case Event::EndedMouseMovedEventDispatching: |
| setShouldObserveDOMTimerScheduling(false); |
| setShouldObserveTransitions(false); |
| break; |
| case Event::StartedStyleRecalc: |
| setShouldObserveNextStyleRecalc(false); |
| FALLTHROUGH; |
| case Event::StartedDOMTimerExecution: |
| ASSERT(isObservationTimeWindowActive() || observedContentChange() == WKContentIndeterminateChange); |
| break; |
| case Event::InstalledDOMTimer: |
| case Event::StartedFixedObservationTimeWindow: |
| case Event::AddedTransition: |
| ASSERT(!hasVisibleChangeState()); |
| setHasIndeterminateState(); |
| break; |
| case Event::EndedDOMTimerExecution: |
| setShouldObserveNextStyleRecalc(m_document.hasPendingStyleRecalc()); |
| FALLTHROUGH; |
| case Event::EndedStyleRecalc: |
| case Event::RemovedDOMTimer: |
| case Event::CanceledTransition: |
| if (!isObservationTimeWindowActive()) |
| adjustStateAndNotifyContentChangeIfNeeded(); |
| break; |
| case Event::EndedTransitionButFinalStyleIsNotDefiniteYet: |
| // onAnimationEnd can be called while in the middle of resolving the document (synchronously) or |
| // asynchronously right before the style update is issued. It also means we don't know whether this animation ends up producing visible content yet. |
| if (m_document.inStyleRecalc()) { |
| // We need to start observing this style change synchronously. |
| m_isInObservedStyleRecalc = true; |
| } else |
| setShouldObserveNextStyleRecalc(true); |
| break; |
| case Event::CompletedTransitionWithClickableContent: |
| // Set visibility flag on and report visible change synchronously or asynchronously depending whether we are in the middle of style recalc. |
| contentVisibilityDidChange(); |
| FALLTHROUGH; |
| case Event::CompletedTransitionWithoutClickableContent: |
| if (m_document.inStyleRecalc()) |
| m_isInObservedStyleRecalc = true; |
| else if (!isObservationTimeWindowActive()) |
| adjustStateAndNotifyContentChangeIfNeeded(); |
| break; |
| case Event::EndedFixedObservationTimeWindow: |
| adjustStateAndNotifyContentChangeIfNeeded(); |
| break; |
| case Event::ContentVisibilityChanged: |
| setHasVisibleChangeState(); |
| // Stop pending activities. We don't need to observe them anymore. |
| stopObservingPendingActivities(); |
| break; |
| } |
| } |
| |
| bool ContentChangeObserver::shouldObserveVisibilityChangeForElement(const Element& element) |
| { |
| return isObservingContentChanges() && !hasVisibleChangeState() && !visibleRendererWasDestroyed(element); |
| } |
| |
| ContentChangeObserver::StyleChangeScope::StyleChangeScope(Document& document, const Element& element) |
| : m_contentChangeObserver(document.contentChangeObserver()) |
| , m_element(element) |
| , m_hadRenderer(element.renderer()) |
| { |
| if (m_contentChangeObserver.shouldObserveVisibilityChangeForElement(element)) |
| m_wasHidden = isVisuallyHidden(m_element); |
| } |
| |
| ContentChangeObserver::StyleChangeScope::~StyleChangeScope() |
| { |
| auto changedFromHiddenToVisible = [&] { |
| return m_wasHidden && isConsideredVisible(m_element); |
| }; |
| |
| if (changedFromHiddenToVisible() && isConsideredClickable(m_element, m_hadRenderer ? ElementHadRenderer::Yes : ElementHadRenderer::No)) |
| m_contentChangeObserver.contentVisibilityDidChange(); |
| } |
| |
| #if ENABLE(TOUCH_EVENTS) |
| ContentChangeObserver::TouchEventScope::TouchEventScope(Document& document, PlatformEvent::Type eventType) |
| : m_contentChangeObserver(document.contentChangeObserver()) |
| { |
| m_contentChangeObserver.touchEventDidStart(eventType); |
| } |
| |
| ContentChangeObserver::TouchEventScope::~TouchEventScope() |
| { |
| m_contentChangeObserver.touchEventDidFinish(); |
| } |
| #endif |
| |
| ContentChangeObserver::MouseMovedScope::MouseMovedScope(Document& document) |
| : m_contentChangeObserver(document.contentChangeObserver()) |
| { |
| m_contentChangeObserver.mouseMovedDidStart(); |
| } |
| |
| ContentChangeObserver::MouseMovedScope::~MouseMovedScope() |
| { |
| m_contentChangeObserver.mouseMovedDidFinish(); |
| m_contentChangeObserver.resetHiddenTouchTarget(); |
| } |
| |
| ContentChangeObserver::StyleRecalcScope::StyleRecalcScope(Document& document) |
| : m_contentChangeObserver(document.contentChangeObserver()) |
| { |
| m_contentChangeObserver.styleRecalcDidStart(); |
| } |
| |
| ContentChangeObserver::StyleRecalcScope::~StyleRecalcScope() |
| { |
| m_contentChangeObserver.styleRecalcDidFinish(); |
| } |
| |
| ContentChangeObserver::DOMTimerScope::DOMTimerScope(Document* document, const DOMTimer& domTimer) |
| : m_contentChangeObserver(document ? &document->contentChangeObserver() : nullptr) |
| , m_domTimer(domTimer) |
| { |
| if (m_contentChangeObserver) |
| m_contentChangeObserver->domTimerExecuteDidStart(m_domTimer); |
| } |
| |
| ContentChangeObserver::DOMTimerScope::~DOMTimerScope() |
| { |
| if (m_contentChangeObserver) |
| m_contentChangeObserver->domTimerExecuteDidFinish(m_domTimer); |
| } |
| |
| ContentChangeObserver::RenderTreeUpdateScope::RenderTreeUpdateScope(Document& document) |
| : m_contentChangeObserver(document.contentChangeObserver()) |
| { |
| m_contentChangeObserver.renderTreeUpdateDidStart(); |
| } |
| |
| ContentChangeObserver::RenderTreeUpdateScope::~RenderTreeUpdateScope() |
| { |
| m_contentChangeObserver.renderTreeUpdateDidFinish(); |
| } |
| |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |