[ContentChangeObserver] Simplify content observation API by removing explicit DOMTime...
[WebKit-https.git] / Source / WebCore / page / ios / ContentChangeObserver.mm
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #import "config.h"
26 #import "ContentChangeObserver.h"
27
28 #if PLATFORM(IOS_FAMILY)
29 #import "Chrome.h"
30 #import "ChromeClient.h"
31 #import "DOMTimer.h"
32 #import "Logging.h"
33 #import "Page.h"
34 #import "WKContentObservationInternal.h"
35
36 namespace WebCore {
37
38 ContentChangeObserver::ContentChangeObserver(Page& page)
39     : m_page(page)
40 {
41 }
42
43 void ContentChangeObserver::registerDOMTimerForContentObservationIfNeeded(const DOMTimer& timer, Seconds timeout, bool singleShot)
44 {
45     if (!m_page.mainFrame().document())
46         return;
47     if (m_page.mainFrame().document()->activeDOMObjectsAreSuspended())
48         return;
49     if (timeout > 250_ms || !singleShot)
50         return;
51     if (!isObservingDOMTimerScheduling())
52         return;
53     setObservedContentChange(WKContentIndeterminateChange);
54     addObservedDOMTimer(timer);
55     LOG_WITH_STREAM(ContentObservation, stream << "registerDOMTimerForContentObservationIfNeeded: registed this timer: (" << &timer << ") and observe when it fires.");
56 }
57
58 void ContentChangeObserver::startObservingDOMTimerExecute(const DOMTimer& timer)
59 {
60     if (!containsObservedDOMTimer(timer))
61         return;
62     LOG_WITH_STREAM(ContentObservation, stream << "startObservingDOMTimerExecute: start observing (" << &timer << ") timer callback.");
63     startObservingContentChanges();
64     startObservingStyleRecalcScheduling();
65 }
66
67 void ContentChangeObserver::stopObservingDOMTimerExecute(const DOMTimer& timer)
68 {
69     if (!containsObservedDOMTimer(timer))
70         return;
71     removeObservedDOMTimer(timer);
72     stopObservingStyleRecalcScheduling();
73     stopObservingContentChanges();
74     auto observedContentChange = this->observedContentChange();
75     // Check if the timer callback triggered either a sync or async style update.
76     auto inDeterminedState = observedContentChange == WKContentVisibilityChange || (!countOfObservedDOMTimers() && observedContentChange == WKContentNoChange);  
77
78     LOG_WITH_STREAM(ContentObservation, stream << "stopObservingDOMTimerExecute: stop observing (" << &timer << ") timer callback.");
79     if (inDeterminedState) {
80         LOG_WITH_STREAM(ContentObservation, stream << "stopObservingDOMTimerExecute: (" << &timer << ") in determined state.");
81         m_page.chrome().client().observedContentChange(m_page.mainFrame());
82     } else if (observedContentChange == WKContentIndeterminateChange) {
83         // An async style recalc has been scheduled. Let's observe it.
84         LOG_WITH_STREAM(ContentObservation, stream << "stopObservingDOMTimerExecute: (" << &timer << ") wait until next style recalc fires.");
85         setShouldObserveNextStyleRecalc(true);
86     }
87 }
88
89 void ContentChangeObserver::startObservingStyleResolve()
90 {
91     if (!shouldObserveNextStyleRecalc())
92         return;
93     LOG(ContentObservation, "startObservingStyleResolve: start observing style resolve.");
94     startObservingContentChanges();
95 }
96
97 void ContentChangeObserver::stopObservingStyleResolve()
98 {
99     if (!shouldObserveNextStyleRecalc())
100         return;
101     LOG(ContentObservation, "stopObservingStyleResolve: stop observing style resolve");
102     setShouldObserveNextStyleRecalc(false);
103     auto inDeterminedState = observedContentChange() == WKContentVisibilityChange || !countOfObservedDOMTimers();
104     if (!inDeterminedState) {
105         LOG(ContentObservation, "stopObservingStyleResolve: can't decided it yet.");
106         return;
107     }
108     LOG(ContentObservation, "stopObservingStyleResolve: notify the pending synthetic click handler.");
109     m_page.chrome().client().observedContentChange(m_page.mainFrame());
110 }
111
112 void ContentChangeObserver::removeDOMTimer(const DOMTimer& timer)
113 {
114     if (!containsObservedDOMTimer(timer))
115         return;
116     removeObservedDOMTimer(timer);
117     LOG_WITH_STREAM(ContentObservation, stream << "removeDOMTimer: remove registered timer (" << &timer << ")");
118     if (countOfObservedDOMTimers())
119         return;
120     m_page.chrome().client().observedContentChange(m_page.mainFrame());
121 }
122
123 void ContentChangeObserver::startObservingContentChanges()
124 {
125     startObservingDOMTimerScheduling();
126     WKStartObservingContentChanges();
127 }
128
129 void ContentChangeObserver::stopObservingContentChanges()
130 {
131     stopObservingDOMTimerScheduling();
132     WKStopObservingContentChanges();
133 }
134
135 bool ContentChangeObserver::isObservingContentChanges()
136 {
137     return WKObservingContentChanges();
138 }
139
140 void ContentChangeObserver::startObservingDOMTimerScheduling()
141 {
142     WKStartObservingDOMTimerScheduling();
143 }
144
145 void ContentChangeObserver::stopObservingDOMTimerScheduling()
146 {
147     WKStopObservingDOMTimerScheduling();
148 }
149
150 bool ContentChangeObserver::isObservingDOMTimerScheduling()
151 {
152     return WKIsObservingDOMTimerScheduling();
153 }
154
155 void ContentChangeObserver::startObservingStyleRecalcScheduling()
156 {
157     WKStartObservingStyleRecalcScheduling();
158 }
159
160 void ContentChangeObserver::stopObservingStyleRecalcScheduling()
161 {
162     WKStopObservingStyleRecalcScheduling();
163 }
164
165 bool ContentChangeObserver::isObservingStyleRecalcScheduling()
166 {
167     return WKIsObservingStyleRecalcScheduling();
168 }
169
170 void ContentChangeObserver::setShouldObserveNextStyleRecalc(bool observe)
171 {
172     WKSetShouldObserveNextStyleRecalc(observe);
173 }
174
175 bool ContentChangeObserver::shouldObserveNextStyleRecalc()
176 {
177     return WKShouldObserveNextStyleRecalc();
178 }
179
180 WKContentChange ContentChangeObserver::observedContentChange()
181 {
182     return WKObservedContentChange();
183 }
184
185 unsigned ContentChangeObserver::countOfObservedDOMTimers()
186 {
187     return WebThreadCountOfObservedDOMTimers();
188 }
189
190 void ContentChangeObserver::clearObservedDOMTimers()
191 {
192     WebThreadClearObservedDOMTimers();
193 }
194
195 void ContentChangeObserver::setObservedContentChange(WKContentChange change)
196 {
197     WKSetObservedContentChange(change);
198 }
199
200 bool ContentChangeObserver::containsObservedDOMTimer(const DOMTimer& timer)
201 {
202     return WebThreadContainsObservedDOMTimer(const_cast<DOMTimer*>(&timer));
203 }
204
205 void ContentChangeObserver::addObservedDOMTimer(const DOMTimer& timer)
206 {
207     WebThreadAddObservedDOMTimer(const_cast<DOMTimer*>(&timer));
208 }
209
210 void ContentChangeObserver::removeObservedDOMTimer(const DOMTimer& timer)
211 {
212     WebThreadRemoveObservedDOMTimer(const_cast<DOMTimer*>(&timer));
213 }
214
215 static Visibility elementImplicitVisibility(const Element& element)
216 {
217     auto* renderer = element.renderer();
218     if (!renderer)
219         return Visibility::Visible;
220
221     auto& style = renderer->style();
222
223     auto width = style.width();
224     auto height = style.height();
225     if ((width.isFixed() && width.value() <= 0) || (height.isFixed() && height.value() <= 0))
226         return Visibility::Hidden;
227
228     auto top = style.top();
229     auto left = style.left();
230     if (left.isFixed() && width.isFixed() && -left.value() >= width.value())
231         return Visibility::Hidden;
232
233     if (top.isFixed() && height.isFixed() && -top.value() >= height.value())
234         return Visibility::Hidden;
235     return Visibility::Visible;
236 }
237
238 ContentChangeObserver::StyleChange::StyleChange(const Element& element, ContentChangeObserver& contentChangeObserver)
239     : m_element(element)
240     , m_contentChangeObserver(contentChangeObserver)
241     , m_previousDisplay(element.renderStyle() ? element.renderStyle()->display() : DisplayType::None)
242     , m_previousVisibility(element.renderStyle() ? element.renderStyle()->visibility() : Visibility::Hidden)
243     , m_previousImplicitVisibility(contentChangeObserver.isObservingContentChanges() && contentChangeObserver.observedContentChange() != WKContentVisibilityChange ? elementImplicitVisibility(element) : Visibility::Visible)
244 {
245 }
246
247 ContentChangeObserver::StyleChange::~StyleChange()
248 {
249     if (!m_contentChangeObserver.isObservingContentChanges())
250         return;
251
252     auto* style = m_element.renderStyle();
253     auto qualifiesForVisibilityCheck = [&] {
254         if (!style)
255             return false;
256         if (m_element.isInUserAgentShadowTree())
257             return false;
258         if (!const_cast<Element&>(m_element).willRespondToMouseClickEvents())
259             return false;
260         return true;
261     };
262
263     if (!qualifiesForVisibilityCheck())
264         return;
265
266     if ((m_previousDisplay == DisplayType::None && style->display() != DisplayType::None)
267         || (m_previousVisibility == Visibility::Hidden && style->visibility() != Visibility::Hidden)
268         || (m_previousImplicitVisibility == Visibility::Hidden && elementImplicitVisibility(m_element) == Visibility::Visible))
269         m_contentChangeObserver.setObservedContentChange(WKContentVisibilityChange);
270 }
271 }
272
273 #endif // PLATFORM(IOS_FAMILY)