| 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 |