<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[277481] trunk/Source/WebCore</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/277481">277481</a></dd>
<dt>Author</dt> <dd>drousso@apple.com</dd>
<dt>Date</dt> <dd>2021-05-13 21:44:10 -0700 (Thu, 13 May 2021)</dd>
</dl>

<h3>Log Message</h3>
<pre>[Modern Media Controls] REGRESSION(<a href="http://trac.webkit.org/projects/webkit/changeset/268308">r268308</a>) AirPlay briefly disappears and then reappears when hovering over controls
https://bugs.webkit.org/show_bug.cgi?id=225780
<rdar://problem/77984683>

Reviewed by Eric Carlson.

<a href="http://trac.webkit.org/projects/webkit/changeset/268308">r268308</a> adjusted `AVRoutePickerViewTargetPicker::isAvailable`, which is used to control
whether `AVRoutePickerViewTargetPicker` (which uses the `AVRouteDetectorMultipleRoutesDetectedDidChange`
notification and actually stops listening for it in `stopMonitoringPlaybackTargets`) or
`AVOutputDeviceMenuControllerTargetPicker` (which uses ObjC KVO and doesn't actually do
anything in `stopMonitoringPlaybackTargets` when the last JS `"webkitplaybacktargetavailabilitychanged"`
event listener is removed, meaning that WebKit never senda a new value to the WebProcess) is
used. When using `AVRoutePickerViewTargetPicker`, WebKit calls `-[AVRouteDetector setRouteDetectionEnabled:]`
whenever the first JS `"webkitplaybacktargetavailabilitychanged"` event listener is added
(with the argument `YES`) and the last JS `"webkitplaybacktargetavailabilitychanged"` event
listener is removed (with the argument `NO`). In the latter scenario (which is the case with
builtin media controls), `-[AVRouteDetector setRouteDetectionEnabled:]` will dispatch a
`AVRouteDetectorMultipleRoutesDetectedDidChange` notification and mark itself as not having
multiple routes (`-[AVRouteDetector multipleRoutesDetected]`). This work is done in the
UIProcess and the result is sent to the WebProcess, meaning that even though there are no
more JS event listeners WebKit still updates the cached state of whether multiple routes
exist. This means that the next time a JS event listener is added, WebKit will ask the
UIProcess to update (which will re-enable route detection, which will dispatch a `AVRouteDetectorMultipleRoutesDetectedDidChange`
notification) but then immediately dispatch a JS `"webkitplaybacktargetavailabilitychanged"`
event using the cached state. Once the request from the UIProcess comes back, WebKit will
then dispatch *another* JS "webkitplaybacktargetavailabilitychanged" event with the new
non-cached value.

* platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h:
* platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm:
(WebCore::AVRoutePickerViewTargetPicker::startingMonitoringPlaybackTargets):
(WebCore::AVRoutePickerViewTargetPicker::stopMonitoringPlaybackTargets):
(WebCore::AVRoutePickerViewTargetPicker::availableDevicesDidChange):
Add a flag that ignores the next `AVRouteDetectorMultipleRoutesDetectedDidChange`
notification since it's guaranteed to be `false` after `setRouteDetectionEnabled:NO`.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcAVRoutePickerViewTargetPickerh">trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcAVRoutePickerViewTargetPickermm">trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (277480 => 277481)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog   2021-05-14 04:39:57 UTC (rev 277480)
+++ trunk/Source/WebCore/ChangeLog      2021-05-14 04:44:10 UTC (rev 277481)
</span><span class="lines">@@ -1,3 +1,41 @@
</span><ins>+2021-05-13  Devin Rousso  <drousso@apple.com>
+
+        [Modern Media Controls] REGRESSION(r268308) AirPlay briefly disappears and then reappears when hovering over controls
+        https://bugs.webkit.org/show_bug.cgi?id=225780
+        <rdar://problem/77984683>
+
+        Reviewed by Eric Carlson.
+
+        r268308 adjusted `AVRoutePickerViewTargetPicker::isAvailable`, which is used to control
+        whether `AVRoutePickerViewTargetPicker` (which uses the `AVRouteDetectorMultipleRoutesDetectedDidChange`
+        notification and actually stops listening for it in `stopMonitoringPlaybackTargets`) or
+        `AVOutputDeviceMenuControllerTargetPicker` (which uses ObjC KVO and doesn't actually do
+        anything in `stopMonitoringPlaybackTargets` when the last JS `"webkitplaybacktargetavailabilitychanged"`
+        event listener is removed, meaning that WebKit never senda a new value to the WebProcess) is
+        used. When using `AVRoutePickerViewTargetPicker`, WebKit calls `-[AVRouteDetector setRouteDetectionEnabled:]`
+        whenever the first JS `"webkitplaybacktargetavailabilitychanged"` event listener is added
+        (with the argument `YES`) and the last JS `"webkitplaybacktargetavailabilitychanged"` event
+        listener is removed (with the argument `NO`). In the latter scenario (which is the case with
+        builtin media controls), `-[AVRouteDetector setRouteDetectionEnabled:]` will dispatch a
+        `AVRouteDetectorMultipleRoutesDetectedDidChange` notification and mark itself as not having
+        multiple routes (`-[AVRouteDetector multipleRoutesDetected]`). This work is done in the
+        UIProcess and the result is sent to the WebProcess, meaning that even though there are no
+        more JS event listeners WebKit still updates the cached state of whether multiple routes
+        exist. This means that the next time a JS event listener is added, WebKit will ask the
+        UIProcess to update (which will re-enable route detection, which will dispatch a `AVRouteDetectorMultipleRoutesDetectedDidChange`
+        notification) but then immediately dispatch a JS `"webkitplaybacktargetavailabilitychanged"`
+        event using the cached state. Once the request from the UIProcess comes back, WebKit will
+        then dispatch *another* JS "webkitplaybacktargetavailabilitychanged" event with the new
+        non-cached value.
+
+        * platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h:
+        * platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm:
+        (WebCore::AVRoutePickerViewTargetPicker::startingMonitoringPlaybackTargets):
+        (WebCore::AVRoutePickerViewTargetPicker::stopMonitoringPlaybackTargets):
+        (WebCore::AVRoutePickerViewTargetPicker::availableDevicesDidChange):
+        Add a flag that ignores the next `AVRouteDetectorMultipleRoutesDetectedDidChange`
+        notification since it's guaranteed to be `false` after `setRouteDetectionEnabled:NO`.
+
</ins><span class="cx"> 2021-05-13  Wenson Hsieh  <wenson_hsieh@apple.com>
</span><span class="cx"> 
</span><span class="cx">         [Cocoa] Plumb data detector results through some platform objects
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcAVRoutePickerViewTargetPickerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h (277480 => 277481)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h 2021-05-14 04:39:57 UTC (rev 277480)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.h    2021-05-14 04:44:10 UTC (rev 277481)
</span><span class="lines">@@ -66,6 +66,7 @@
</span><span class="cx">     RetainPtr<AVOutputContext> m_outputContext;
</span><span class="cx">     RetainPtr<WebAVRoutePickerViewHelper> m_routePickerViewDelegate;
</span><span class="cx">     bool m_hadActiveRoute { false };
</span><ins>+    bool m_ignoreNextMultipleRoutesDetectedDidChangeNotification { false };
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcAVRoutePickerViewTargetPickermm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm (277480 => 277481)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm        2021-05-14 04:39:57 UTC (rev 277480)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/AVRoutePickerViewTargetPicker.mm   2021-05-14 04:44:10 UTC (rev 277481)
</span><span class="lines">@@ -137,13 +137,24 @@
</span><span class="cx"> 
</span><span class="cx"> void AVRoutePickerViewTargetPicker::startingMonitoringPlaybackTargets()
</span><span class="cx"> {
</span><ins>+    m_ignoreNextMultipleRoutesDetectedDidChangeNotification = false;
+
</ins><span class="cx">     routeDetector().routeDetectionEnabled = YES;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void AVRoutePickerViewTargetPicker::stopMonitoringPlaybackTargets()
</span><span class="cx"> {
</span><del>-    if (m_routeDetector)
-        [m_routeDetector setRouteDetectionEnabled:NO];
</del><ins>+    if (!m_routeDetector)
+        return;
+
+    // `-[AVRouteDetector multipleRoutesDetected]` will always return `NO` if route detection is
+    // disabled and `-[AVRouteDetector setRouteDetectionEnabled:]` will always dispatch a
+    // `AVRouteDetectorMultipleRoutesDetectedDidChange` notification, so ignore the next one in
+    // order to prevent the cached value in the WebProcess from always being `false` when the last
+    // JS `"webkitplaybacktargetavailabilitychanged"` event listener is removed.
+    m_ignoreNextMultipleRoutesDetectedDidChangeNotification = true;
+
+    [m_routeDetector setRouteDetectionEnabled:NO];
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> bool AVRoutePickerViewTargetPicker::externalOutputDeviceAvailable()
</span><span class="lines">@@ -177,6 +188,11 @@
</span><span class="cx"> }
</span><span class="cx"> void AVRoutePickerViewTargetPicker::availableDevicesDidChange()
</span><span class="cx"> {
</span><ins>+    if (m_ignoreNextMultipleRoutesDetectedDidChangeNotification) {
+        m_ignoreNextMultipleRoutesDetectedDidChangeNotification = false;
+        return;
+    }
+
</ins><span class="cx">     if (client())
</span><span class="cx">         client()->availableDevicesChanged();
</span><span class="cx"> }
</span></span></pre>
</div>
</div>

</body>
</html>