6223f7db2abb10a39438f4317802a155e31d15dd
[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::didInstallDOMTimer(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 << "didInstallDOMTimer: register 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::didScheduleStyleRecalc()
90 {
91     if (!isObservingStyleRecalcScheduling())
92         return;
93     LOG(ContentObservation, "didScheduleStyleRecalc: register this style recalc schedule and observe when it fires.");
94     setObservedContentChange(WKContentIndeterminateChange);
95 }
96
97 void ContentChangeObserver::startObservingStyleResolve()
98 {
99     if (!shouldObserveNextStyleRecalc())
100         return;
101     LOG(ContentObservation, "startObservingStyleResolve: start observing style resolve.");
102     startObservingContentChanges();
103 }
104
105 void ContentChangeObserver::stopObservingStyleResolve()
106 {
107     if (!shouldObserveNextStyleRecalc())
108         return;
109     LOG(ContentObservation, "stopObservingStyleResolve: stop observing style resolve");
110     setShouldObserveNextStyleRecalc(false);
111     auto inDeterminedState = observedContentChange() == WKContentVisibilityChange || !countOfObservedDOMTimers();
112     if (!inDeterminedState) {
113         LOG(ContentObservation, "stopObservingStyleResolve: can't decided it yet.");
114         return;
115     }
116     LOG(ContentObservation, "stopObservingStyleResolve: notify the pending synthetic click handler.");
117     m_page.chrome().client().observedContentChange(m_page.mainFrame());
118 }
119
120 void ContentChangeObserver::removeDOMTimer(const DOMTimer& timer)
121 {
122     if (!containsObservedDOMTimer(timer))
123         return;
124     removeObservedDOMTimer(timer);
125     LOG_WITH_STREAM(ContentObservation, stream << "removeDOMTimer: remove registered timer (" << &timer << ")");
126     if (countOfObservedDOMTimers())
127         return;
128     m_page.chrome().client().observedContentChange(m_page.mainFrame());
129 }
130
131 void ContentChangeObserver::startObservingContentChanges()
132 {
133     startObservingDOMTimerScheduling();
134     WKStartObservingContentChanges();
135 }
136
137 void ContentChangeObserver::stopObservingContentChanges()
138 {
139     stopObservingDOMTimerScheduling();
140     WKStopObservingContentChanges();
141 }
142
143 bool ContentChangeObserver::isObservingContentChanges()
144 {
145     return WKObservingContentChanges();
146 }
147
148 void ContentChangeObserver::startObservingDOMTimerScheduling()
149 {
150     WKStartObservingDOMTimerScheduling();
151 }
152
153 void ContentChangeObserver::stopObservingDOMTimerScheduling()
154 {
155     WKStopObservingDOMTimerScheduling();
156 }
157
158 bool ContentChangeObserver::isObservingDOMTimerScheduling()
159 {
160     return WKIsObservingDOMTimerScheduling();
161 }
162
163 void ContentChangeObserver::startObservingStyleRecalcScheduling()
164 {
165     WKStartObservingStyleRecalcScheduling();
166 }
167
168 void ContentChangeObserver::stopObservingStyleRecalcScheduling()
169 {
170     WKStopObservingStyleRecalcScheduling();
171 }
172
173 bool ContentChangeObserver::isObservingStyleRecalcScheduling()
174 {
175     return WKIsObservingStyleRecalcScheduling();
176 }
177
178 void ContentChangeObserver::setShouldObserveNextStyleRecalc(bool observe)
179 {
180     WKSetShouldObserveNextStyleRecalc(observe);
181 }
182
183 bool ContentChangeObserver::shouldObserveNextStyleRecalc()
184 {
185     return WKShouldObserveNextStyleRecalc();
186 }
187
188 WKContentChange ContentChangeObserver::observedContentChange()
189 {
190     return WKObservedContentChange();
191 }
192
193 unsigned ContentChangeObserver::countOfObservedDOMTimers()
194 {
195     return WebThreadCountOfObservedDOMTimers();
196 }
197
198 void ContentChangeObserver::clearObservedDOMTimers()
199 {
200     WebThreadClearObservedDOMTimers();
201 }
202
203 void ContentChangeObserver::setObservedContentChange(WKContentChange change)
204 {
205     WKSetObservedContentChange(change);
206 }
207
208 bool ContentChangeObserver::containsObservedDOMTimer(const DOMTimer& timer)
209 {
210     return WebThreadContainsObservedDOMTimer(const_cast<DOMTimer*>(&timer));
211 }
212
213 void ContentChangeObserver::addObservedDOMTimer(const DOMTimer& timer)
214 {
215     WebThreadAddObservedDOMTimer(const_cast<DOMTimer*>(&timer));
216 }
217
218 void ContentChangeObserver::removeObservedDOMTimer(const DOMTimer& timer)
219 {
220     WebThreadRemoveObservedDOMTimer(const_cast<DOMTimer*>(&timer));
221 }
222
223 static Visibility elementImplicitVisibility(const Element& element)
224 {
225     auto* renderer = element.renderer();
226     if (!renderer)
227         return Visibility::Visible;
228
229     auto& style = renderer->style();
230
231     auto width = style.width();
232     auto height = style.height();
233     if ((width.isFixed() && width.value() <= 0) || (height.isFixed() && height.value() <= 0))
234         return Visibility::Hidden;
235
236     auto top = style.top();
237     auto left = style.left();
238     if (left.isFixed() && width.isFixed() && -left.value() >= width.value())
239         return Visibility::Hidden;
240
241     if (top.isFixed() && height.isFixed() && -top.value() >= height.value())
242         return Visibility::Hidden;
243     return Visibility::Visible;
244 }
245
246 ContentChangeObserver::StyleChange::StyleChange(const Element& element, ContentChangeObserver& contentChangeObserver)
247     : m_element(element)
248     , m_contentChangeObserver(contentChangeObserver)
249     , m_previousDisplay(element.renderStyle() ? element.renderStyle()->display() : DisplayType::None)
250     , m_previousVisibility(element.renderStyle() ? element.renderStyle()->visibility() : Visibility::Hidden)
251     , m_previousImplicitVisibility(contentChangeObserver.isObservingContentChanges() && contentChangeObserver.observedContentChange() != WKContentVisibilityChange ? elementImplicitVisibility(element) : Visibility::Visible)
252 {
253 }
254
255 ContentChangeObserver::StyleChange::~StyleChange()
256 {
257     if (!m_contentChangeObserver.isObservingContentChanges())
258         return;
259
260     auto* style = m_element.renderStyle();
261     auto qualifiesForVisibilityCheck = [&] {
262         if (!style)
263             return false;
264         if (m_element.isInUserAgentShadowTree())
265             return false;
266         if (!const_cast<Element&>(m_element).willRespondToMouseClickEvents())
267             return false;
268         return true;
269     };
270
271     if (!qualifiesForVisibilityCheck())
272         return;
273
274     if ((m_previousDisplay == DisplayType::None && style->display() != DisplayType::None)
275         || (m_previousVisibility == Visibility::Hidden && style->visibility() != Visibility::Hidden)
276         || (m_previousImplicitVisibility == Visibility::Hidden && elementImplicitVisibility(m_element) == Visibility::Visible))
277         m_contentChangeObserver.setObservedContentChange(WKContentVisibilityChange);
278 }
279 }
280
281 #endif // PLATFORM(IOS_FAMILY)