OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "core/html/AutoplayUmaHelper.h" | |
6 | |
7 #include "core/dom/Document.h" | |
8 #include "core/dom/ElementVisibilityObserver.h" | |
9 #include "core/events/Event.h" | |
10 #include "core/frame/Settings.h" | |
11 #include "core/html/HTMLMediaElement.h" | |
12 #include "platform/Histogram.h" | |
13 #include "public/platform/Platform.h" | |
14 #include "wtf/CurrentTime.h" | |
15 | |
16 namespace blink { | |
17 | |
18 namespace { | |
19 | |
20 const int32_t maxOffscreenDurationUmaMS = 60 * 60 * 1000; | |
21 const int32_t offscreenDurationUmaBucketCount = 50; | |
22 | |
23 } // namespace | |
24 | |
25 AutoplayUmaHelper* AutoplayUmaHelper::create(HTMLMediaElement* element) { | |
26 return new AutoplayUmaHelper(element); | |
27 } | |
28 | |
29 AutoplayUmaHelper::AutoplayUmaHelper(HTMLMediaElement* element) | |
30 : EventListener(CPPEventListenerType), | |
31 ContextLifecycleObserver(nullptr), | |
32 m_element(element), | |
33 m_mutedVideoPlayMethodVisibilityObserver(nullptr), | |
34 m_mutedVideoAutoplayOffscreenStartTimeMS(0), | |
35 m_mutedVideoAutoplayOffscreenDurationMS(0), | |
36 m_isVisible(false), | |
37 m_mutedVideoOffscreenDurationVisibilityObserver(nullptr) {} | |
38 | |
39 AutoplayUmaHelper::~AutoplayUmaHelper() = default; | |
40 | |
41 bool AutoplayUmaHelper::operator==(const EventListener& other) const { | |
42 return this == &other; | |
43 } | |
44 | |
45 void AutoplayUmaHelper::onAutoplayInitiated(AutoplaySource source) { | |
46 DEFINE_STATIC_LOCAL(EnumerationHistogram, videoHistogram, | |
47 ("Media.Video.Autoplay", | |
48 static_cast<int>(AutoplaySource::NumberOfUmaSources))); | |
49 DEFINE_STATIC_LOCAL(EnumerationHistogram, mutedVideoHistogram, | |
50 ("Media.Video.Autoplay.Muted", | |
51 static_cast<int>(AutoplaySource::NumberOfUmaSources))); | |
52 DEFINE_STATIC_LOCAL(EnumerationHistogram, audioHistogram, | |
53 ("Media.Audio.Autoplay", | |
54 static_cast<int>(AutoplaySource::NumberOfUmaSources))); | |
55 DEFINE_STATIC_LOCAL( | |
56 EnumerationHistogram, blockedMutedVideoHistogram, | |
57 ("Media.Video.Autoplay.Muted.Blocked", AutoplayBlockedReasonMax)); | |
58 | |
59 // Autoplay already initiated | |
60 if (m_sources.count(source)) | |
61 return; | |
62 | |
63 m_sources.insert(source); | |
64 | |
65 // Record the source. | |
66 if (m_element->isHTMLVideoElement()) { | |
67 videoHistogram.count(static_cast<int>(source)); | |
68 if (m_element->muted()) | |
69 mutedVideoHistogram.count(static_cast<int>(source)); | |
70 } else { | |
71 audioHistogram.count(static_cast<int>(source)); | |
72 } | |
73 | |
74 // Record dual source. | |
75 if (m_sources.size() == | |
76 static_cast<size_t>(AutoplaySource::NumberOfSources)) { | |
77 if (m_element->isHTMLVideoElement()) { | |
78 videoHistogram.count(static_cast<int>(AutoplaySource::DualSource)); | |
79 if (m_element->muted()) | |
80 mutedVideoHistogram.count(static_cast<int>(AutoplaySource::DualSource)); | |
81 } else { | |
82 audioHistogram.count(static_cast<int>(AutoplaySource::DualSource)); | |
83 } | |
84 } | |
85 | |
86 // Record the child frame and top-level frame URLs for autoplay muted videos | |
87 // by attribute. | |
88 if (m_element->isHTMLVideoElement() && m_element->muted()) { | |
89 if (m_sources.size() == | |
90 static_cast<size_t>(AutoplaySource::NumberOfSources)) { | |
91 Platform::current()->recordRapporURL( | |
92 "Media.Video.Autoplay.Muted.DualSource.Frame", | |
93 m_element->document().url()); | |
94 } else if (source == AutoplaySource::Attribute) { | |
95 Platform::current()->recordRapporURL( | |
96 "Media.Video.Autoplay.Muted.Attribute.Frame", | |
97 m_element->document().url()); | |
98 } else { | |
99 DCHECK(source == AutoplaySource::Method); | |
100 Platform::current()->recordRapporURL( | |
101 "Media.Video.Autoplay.Muted.PlayMethod.Frame", | |
102 m_element->document().url()); | |
103 } | |
104 } | |
105 | |
106 // Record if it will be blocked by Data Saver or Autoplay setting. | |
107 if (m_element->isHTMLVideoElement() && m_element->muted() && | |
108 RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) { | |
109 bool dataSaverEnabled = | |
110 m_element->document().settings() && | |
111 m_element->document().settings()->getDataSaverEnabled(); | |
112 bool blockedBySetting = !m_element->isAutoplayAllowedPerSettings(); | |
113 | |
114 if (dataSaverEnabled && blockedBySetting) { | |
115 blockedMutedVideoHistogram.count( | |
116 AutoplayBlockedReasonDataSaverAndSetting); | |
117 } else if (dataSaverEnabled) { | |
118 blockedMutedVideoHistogram.count(AutoplayBlockedReasonDataSaver); | |
119 } else if (blockedBySetting) { | |
120 blockedMutedVideoHistogram.count(AutoplayBlockedReasonSetting); | |
121 } | |
122 } | |
123 | |
124 m_element->addEventListener(EventTypeNames::playing, this, false); | |
125 } | |
126 | |
127 void AutoplayUmaHelper::recordCrossOriginAutoplayResult( | |
128 CrossOriginAutoplayResult result) { | |
129 DEFINE_STATIC_LOCAL( | |
130 EnumerationHistogram, autoplayResultHistogram, | |
131 ("Media.Autoplay.CrossOrigin.Result", | |
132 static_cast<int>(CrossOriginAutoplayResult::NumberOfResults))); | |
133 | |
134 if (!m_element->isHTMLVideoElement()) | |
135 return; | |
136 if (!m_element->isInCrossOriginFrame()) | |
137 return; | |
138 | |
139 // Record each metric only once per element, since the metric focuses on the | |
140 // site distribution. If a page calls play() multiple times, it will be | |
141 // recorded only once. | |
142 if (m_recordedCrossOriginAutoplayResults.count(result)) | |
143 return; | |
144 | |
145 switch (result) { | |
146 case CrossOriginAutoplayResult::AutoplayAllowed: | |
147 // Record metric | |
148 Platform::current()->recordRapporURL( | |
149 "Media.Autoplay.CrossOrigin.Allowed.ChildFrame", | |
150 m_element->document().url()); | |
151 Platform::current()->recordRapporURL( | |
152 "Media.Autoplay.CrossOrigin.Allowed.TopLevelFrame", | |
153 m_element->document().topDocument().url()); | |
154 autoplayResultHistogram.count(static_cast<int>(result)); | |
155 m_recordedCrossOriginAutoplayResults.insert(result); | |
156 break; | |
157 case CrossOriginAutoplayResult::AutoplayBlocked: | |
158 Platform::current()->recordRapporURL( | |
159 "Media.Autoplay.CrossOrigin.Blocked.ChildFrame", | |
160 m_element->document().url()); | |
161 Platform::current()->recordRapporURL( | |
162 "Media.Autoplay.CrossOrigin.Blocked.TopLevelFrame", | |
163 m_element->document().topDocument().url()); | |
164 autoplayResultHistogram.count(static_cast<int>(result)); | |
165 m_recordedCrossOriginAutoplayResults.insert(result); | |
166 break; | |
167 case CrossOriginAutoplayResult::PlayedWithGesture: | |
168 // Record this metric only when the video has been blocked from autoplay | |
169 // previously. This is to record the sites having videos that are blocked | |
170 // to autoplay but the user starts the playback by gesture. | |
171 if (!m_recordedCrossOriginAutoplayResults.count( | |
172 CrossOriginAutoplayResult::AutoplayBlocked)) { | |
173 return; | |
174 } | |
175 Platform::current()->recordRapporURL( | |
176 "Media.Autoplay.CrossOrigin.PlayedWithGestureAfterBlock.ChildFrame", | |
177 m_element->document().url()); | |
178 Platform::current()->recordRapporURL( | |
179 "Media.Autoplay.CrossOrigin.PlayedWithGestureAfterBlock." | |
180 "TopLevelFrame", | |
181 m_element->document().topDocument().url()); | |
182 autoplayResultHistogram.count(static_cast<int>(result)); | |
183 m_recordedCrossOriginAutoplayResults.insert(result); | |
184 break; | |
185 case CrossOriginAutoplayResult::UserPaused: | |
186 if (!shouldRecordUserPausedAutoplayingCrossOriginVideo()) | |
187 return; | |
188 if (m_element->ended() || m_element->seeking()) | |
189 return; | |
190 Platform::current()->recordRapporURL( | |
191 "Media.Autoplay.CrossOrigin.UserPausedAutoplayingVideo.ChildFrame", | |
192 m_element->document().url()); | |
193 Platform::current()->recordRapporURL( | |
194 "Media.Autoplay.CrossOrigin.UserPausedAutoplayingVideo." | |
195 "TopLevelFrame", | |
196 m_element->document().topDocument().url()); | |
197 autoplayResultHistogram.count(static_cast<int>(result)); | |
198 m_recordedCrossOriginAutoplayResults.insert(result); | |
199 break; | |
200 default: | |
201 NOTREACHED(); | |
202 } | |
203 } | |
204 | |
205 void AutoplayUmaHelper::recordAutoplayUnmuteStatus( | |
206 AutoplayUnmuteActionStatus status) { | |
207 DEFINE_STATIC_LOCAL( | |
208 EnumerationHistogram, autoplayUnmuteHistogram, | |
209 ("Media.Video.Autoplay.Muted.UnmuteAction", | |
210 static_cast<int>(AutoplayUnmuteActionStatus::NumberOfStatus))); | |
211 | |
212 autoplayUnmuteHistogram.count(static_cast<int>(status)); | |
213 } | |
214 | |
215 void AutoplayUmaHelper::didMoveToNewDocument(Document& oldDocument) { | |
216 if (!shouldListenToContextDestroyed()) | |
217 return; | |
218 | |
219 setContext(&m_element->document()); | |
220 } | |
221 | |
222 void AutoplayUmaHelper::onVisibilityChangedForMutedVideoPlayMethodBecomeVisible( | |
223 bool isVisible) { | |
224 if (!isVisible || !m_mutedVideoPlayMethodVisibilityObserver) | |
225 return; | |
226 | |
227 maybeStopRecordingMutedVideoPlayMethodBecomeVisible(true); | |
228 } | |
229 | |
230 void AutoplayUmaHelper::onVisibilityChangedForMutedVideoOffscreenDuration( | |
231 bool isVisible) { | |
232 if (isVisible == m_isVisible) | |
233 return; | |
234 | |
235 if (isVisible) | |
236 m_mutedVideoAutoplayOffscreenDurationMS += | |
237 static_cast<int64_t>(monotonicallyIncreasingTimeMS()) - | |
238 m_mutedVideoAutoplayOffscreenStartTimeMS; | |
239 else | |
240 m_mutedVideoAutoplayOffscreenStartTimeMS = | |
241 static_cast<int64_t>(monotonicallyIncreasingTimeMS()); | |
242 | |
243 m_isVisible = isVisible; | |
244 } | |
245 | |
246 void AutoplayUmaHelper::handleEvent(ExecutionContext* executionContext, | |
247 Event* event) { | |
248 if (event->type() == EventTypeNames::playing) | |
249 handlePlayingEvent(); | |
250 else if (event->type() == EventTypeNames::pause) | |
251 handlePauseEvent(); | |
252 else | |
253 NOTREACHED(); | |
254 } | |
255 | |
256 void AutoplayUmaHelper::handlePlayingEvent() { | |
257 maybeStartRecordingMutedVideoPlayMethodBecomeVisible(); | |
258 maybeStartRecordingMutedVideoOffscreenDuration(); | |
259 | |
260 m_element->removeEventListener(EventTypeNames::playing, this, false); | |
261 } | |
262 | |
263 void AutoplayUmaHelper::handlePauseEvent() { | |
264 maybeStopRecordingMutedVideoOffscreenDuration(); | |
265 maybeRecordUserPausedAutoplayingCrossOriginVideo(); | |
266 } | |
267 | |
268 void AutoplayUmaHelper::contextDestroyed(ExecutionContext*) { | |
269 handleContextDestroyed(); | |
270 } | |
271 | |
272 void AutoplayUmaHelper::handleContextDestroyed() { | |
273 maybeStopRecordingMutedVideoPlayMethodBecomeVisible(false); | |
274 maybeStopRecordingMutedVideoOffscreenDuration(); | |
275 } | |
276 | |
277 void AutoplayUmaHelper::maybeStartRecordingMutedVideoPlayMethodBecomeVisible() { | |
278 if (!m_sources.count(AutoplaySource::Method) || | |
279 !m_element->isHTMLVideoElement() || !m_element->muted()) | |
280 return; | |
281 | |
282 m_mutedVideoPlayMethodVisibilityObserver = new ElementVisibilityObserver( | |
283 m_element, | |
284 WTF::bind(&AutoplayUmaHelper:: | |
285 onVisibilityChangedForMutedVideoPlayMethodBecomeVisible, | |
286 wrapWeakPersistent(this))); | |
287 m_mutedVideoPlayMethodVisibilityObserver->start(); | |
288 setContext(&m_element->document()); | |
289 } | |
290 | |
291 void AutoplayUmaHelper::maybeStopRecordingMutedVideoPlayMethodBecomeVisible( | |
292 bool visible) { | |
293 if (!m_mutedVideoPlayMethodVisibilityObserver) | |
294 return; | |
295 | |
296 DEFINE_STATIC_LOCAL(BooleanHistogram, histogram, | |
297 ("Media.Video.Autoplay.Muted.PlayMethod.BecomesVisible")); | |
298 | |
299 histogram.count(visible); | |
300 m_mutedVideoPlayMethodVisibilityObserver->stop(); | |
301 m_mutedVideoPlayMethodVisibilityObserver = nullptr; | |
302 maybeUnregisterContextDestroyedObserver(); | |
303 } | |
304 | |
305 void AutoplayUmaHelper::maybeStartRecordingMutedVideoOffscreenDuration() { | |
306 if (!m_element->isHTMLVideoElement() || !m_element->muted() || | |
307 !m_sources.count(AutoplaySource::Method)) | |
308 return; | |
309 | |
310 // Start recording muted video playing offscreen duration. | |
311 m_mutedVideoAutoplayOffscreenStartTimeMS = | |
312 static_cast<int64_t>(monotonicallyIncreasingTimeMS()); | |
313 m_isVisible = false; | |
314 m_mutedVideoOffscreenDurationVisibilityObserver = | |
315 new ElementVisibilityObserver( | |
316 m_element, | |
317 WTF::bind(&AutoplayUmaHelper:: | |
318 onVisibilityChangedForMutedVideoOffscreenDuration, | |
319 wrapWeakPersistent(this))); | |
320 m_mutedVideoOffscreenDurationVisibilityObserver->start(); | |
321 m_element->addEventListener(EventTypeNames::pause, this, false); | |
322 setContext(&m_element->document()); | |
323 } | |
324 | |
325 void AutoplayUmaHelper::maybeStopRecordingMutedVideoOffscreenDuration() { | |
326 if (!m_mutedVideoOffscreenDurationVisibilityObserver) | |
327 return; | |
328 | |
329 if (!m_isVisible) | |
330 m_mutedVideoAutoplayOffscreenDurationMS += | |
331 static_cast<int64_t>(monotonicallyIncreasingTimeMS()) - | |
332 m_mutedVideoAutoplayOffscreenStartTimeMS; | |
333 | |
334 // Since histograms uses int32_t, the duration needs to be limited to | |
335 // std::numeric_limits<int32_t>::max(). | |
336 int32_t boundedTime = static_cast<int32_t>( | |
337 std::min<int64_t>(m_mutedVideoAutoplayOffscreenDurationMS, | |
338 std::numeric_limits<int32_t>::max())); | |
339 | |
340 DCHECK(m_sources.count(AutoplaySource::Method)); | |
341 | |
342 DEFINE_STATIC_LOCAL( | |
343 CustomCountHistogram, durationHistogram, | |
344 ("Media.Video.Autoplay.Muted.PlayMethod.OffscreenDuration", 1, | |
345 maxOffscreenDurationUmaMS, offscreenDurationUmaBucketCount)); | |
346 durationHistogram.count(boundedTime); | |
347 | |
348 m_mutedVideoOffscreenDurationVisibilityObserver->stop(); | |
349 m_mutedVideoOffscreenDurationVisibilityObserver = nullptr; | |
350 m_mutedVideoAutoplayOffscreenDurationMS = 0; | |
351 maybeUnregisterMediaElementPauseListener(); | |
352 maybeUnregisterContextDestroyedObserver(); | |
353 } | |
354 | |
355 void AutoplayUmaHelper::maybeRecordUserPausedAutoplayingCrossOriginVideo() { | |
356 recordCrossOriginAutoplayResult(CrossOriginAutoplayResult::UserPaused); | |
357 maybeUnregisterMediaElementPauseListener(); | |
358 } | |
359 | |
360 void AutoplayUmaHelper::maybeUnregisterContextDestroyedObserver() { | |
361 if (!shouldListenToContextDestroyed()) { | |
362 setContext(nullptr); | |
363 } | |
364 } | |
365 | |
366 void AutoplayUmaHelper::maybeUnregisterMediaElementPauseListener() { | |
367 if (m_mutedVideoOffscreenDurationVisibilityObserver) | |
368 return; | |
369 if (shouldRecordUserPausedAutoplayingCrossOriginVideo()) | |
370 return; | |
371 m_element->removeEventListener(EventTypeNames::pause, this, false); | |
372 } | |
373 | |
374 bool AutoplayUmaHelper::shouldListenToContextDestroyed() const { | |
375 return m_mutedVideoPlayMethodVisibilityObserver || | |
376 m_mutedVideoOffscreenDurationVisibilityObserver; | |
377 } | |
378 | |
379 bool AutoplayUmaHelper::shouldRecordUserPausedAutoplayingCrossOriginVideo() | |
380 const { | |
381 return m_element->isInCrossOriginFrame() && m_element->isHTMLVideoElement() && | |
382 !m_sources.empty() && | |
383 !m_recordedCrossOriginAutoplayResults.count( | |
384 CrossOriginAutoplayResult::UserPaused); | |
385 } | |
386 | |
387 DEFINE_TRACE(AutoplayUmaHelper) { | |
388 EventListener::trace(visitor); | |
389 ContextLifecycleObserver::trace(visitor); | |
390 visitor->trace(m_element); | |
391 visitor->trace(m_mutedVideoPlayMethodVisibilityObserver); | |
392 visitor->trace(m_mutedVideoOffscreenDurationVisibilityObserver); | |
393 } | |
394 | |
395 } // namespace blink | |
OLD | NEW |