<!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>[284802] branches/safari-612-branch</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/284802">284802</a></dd>
<dt>Author</dt> <dd>alancoon@apple.com</dd>
<dt>Date</dt> <dd>2021-10-25 12:09:38 -0700 (Mon, 25 Oct 2021)</dd>
</dl>

<h3>Log Message</h3>
<pre>Cherry-pick <a href="http://trac.webkit.org/projects/webkit/changeset/283590">r283590</a>. rdar://problem/80837616

    ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
    https://bugs.webkit.org/show_bug.cgi?id=231235
    <rdar://80837616>

    Reviewed by Ryosuke Niwa.

    Source/WebCore:

    IntersectionObserver's JS callback stays alive as long as its JS wrapper and
    its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
    implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
    as the JS wrappers of observation / pending targets are alive. However, as per specification,
    we always need to dispatch an observation for an observation target, even if that target
    is not connected. Our code was already taking care of dispatching such observation. However,
    there was nothing keeping the observation target alive in this case and thus nothing keeping
    the JS callback alive either.

    To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
    which holds a strong ref to the observation target until the next time we call notify().
    This makes sure that the observation target (and its JS wrapper) stays alive long enough for
    us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
    return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
    IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.

    Tests: intersection-observer/observe-disconnected-target-crash.html
           intersection-observer/observe-disconnected-target.html

    * page/IntersectionObserver.cpp:
    (WebCore::IntersectionObserver::observe):
    (WebCore::IntersectionObserver::unobserve):
    (WebCore::IntersectionObserver::removeAllTargets):
    (WebCore::IntersectionObserver::notify):
    (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
    * page/IntersectionObserver.h:

    LayoutTests:

    Add layout test coverage both for the crash and the Web facing behavior.

    * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
    * intersection-observer/observe-disconnected-target-crash.html: Added.
    * intersection-observer/observe-disconnected-target-expected.txt: Added.
    * intersection-observer/observe-disconnected-target.html: Added.

    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283590 268f45cc-cd09-0410-ab3c-d52691b4dbfc</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#branchessafari612branchLayoutTestsChangeLog">branches/safari-612-branch/LayoutTests/ChangeLog</a></li>
<li><a href="#branchessafari612branchSourceWebCoreChangeLog">branches/safari-612-branch/Source/WebCore/ChangeLog</a></li>
<li><a href="#branchessafari612branchSourceWebCorepageIntersectionObservercpp">branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp</a></li>
<li><a href="#branchessafari612branchSourceWebCorepageIntersectionObserverh">branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetcrashexpectedtxt">branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt</a></li>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetcrashhtml">branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html</a></li>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetexpectedtxt">branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt</a></li>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargethtml">branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html</a></li>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservethendisconnecttargetexpectedtxt">branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt</a></li>
<li><a href="#branchessafari612branchLayoutTestsintersectionobserverobservethendisconnecttargethtml">branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="branchessafari612branchLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: branches/safari-612-branch/LayoutTests/ChangeLog (284801 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/ChangeLog 2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/LayoutTests/ChangeLog    2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -1,5 +1,71 @@
</span><span class="cx"> 2021-10-25  Null  <null@apple.com>
</span><span class="cx"> 
</span><ins>+        Cherry-pick r283590. rdar://problem/80837616
+
+    ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+    https://bugs.webkit.org/show_bug.cgi?id=231235
+    <rdar://80837616>
+    
+    Reviewed by Ryosuke Niwa.
+    
+    Source/WebCore:
+    
+    IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+    its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+    implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+    as the JS wrappers of observation / pending targets are alive. However, as per specification,
+    we always need to dispatch an observation for an observation target, even if that target
+    is not connected. Our code was already taking care of dispatching such observation. However,
+    there was nothing keeping the observation target alive in this case and thus nothing keeping
+    the JS callback alive either.
+    
+    To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+    which holds a strong ref to the observation target until the next time we call notify().
+    This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+    us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+    return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+    IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+    
+    Tests: intersection-observer/observe-disconnected-target-crash.html
+           intersection-observer/observe-disconnected-target.html
+    
+    * page/IntersectionObserver.cpp:
+    (WebCore::IntersectionObserver::observe):
+    (WebCore::IntersectionObserver::unobserve):
+    (WebCore::IntersectionObserver::removeAllTargets):
+    (WebCore::IntersectionObserver::notify):
+    (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+    * page/IntersectionObserver.h:
+    
+    LayoutTests:
+    
+    Add layout test coverage both for the crash and the Web facing behavior.
+    
+    * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+    * intersection-observer/observe-disconnected-target-crash.html: Added.
+    * intersection-observer/observe-disconnected-target-expected.txt: Added.
+    * intersection-observer/observe-disconnected-target.html: Added.
+    
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283590 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2021-10-05  Chris Dumez  <cdumez@apple.com>
+
+            ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+            https://bugs.webkit.org/show_bug.cgi?id=231235
+            <rdar://80837616>
+
+            Reviewed by Ryosuke Niwa.
+
+            Add layout test coverage both for the crash and the Web facing behavior.
+
+            * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+            * intersection-observer/observe-disconnected-target-crash.html: Added.
+            * intersection-observer/observe-disconnected-target-expected.txt: Added.
+            * intersection-observer/observe-disconnected-target.html: Added.
+
+2021-10-25  Null  <null@apple.com>
+
</ins><span class="cx">         Cherry-pick r282984. rdar://problem/77587429
</span><span class="cx"> 
</span><span class="cx">     Web Inspector: Graphics: add instrumentation for new `CanvasRenderingContext2DSettings`
</span></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetcrashexpectedtxt"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt                              (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt 2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1 @@
</span><ins>+This test passes if it doesn't crash.
</ins></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetcrashhtml"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html                              (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html 2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1,20 @@
</span><ins>+<p>This test passes if it doesn't crash.</p>
+<script>
+  if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+  }
+  onload = async () => {
+    for (let i = 0; i < 8; i++) {
+      document.createElement('div');
+    }
+    for (let i = 0; i < 8; i++) {
+      new IntersectionObserver(() => {});
+    }
+    let intersectionObserver = new IntersectionObserver(() => {});
+    await intersectionObserver.observe(document.createElement('div'));
+    new ImageData(1000, 10000);
+    if (window.testRunner)
+      testRunner.notifyDone();
+  };
+</script>
</ins></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargetexpectedtxt"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt                            (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt       2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+Tests that an observation is received when calling IntersectionObserver.observe() with a disconnected target.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS receivedObservations is false
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].isIntersecting is false
+PASS observations[0].intersectionRatio is 0.0
+PASS observations[0].target.foo is 1
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservedisconnectedtargethtml"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html                            (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html       2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1,34 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<body>
+<script src="../resources/js-test.js"></script>
+<script>
+  description("Tests that an observation is received when calling IntersectionObserver.observe() with a disconnected target.");
+  jsTestIsAsync = true;
+
+  let receivedObservations = false;
+  onload = () => {
+    let intersectionObserver = new IntersectionObserver((_observations) => {
+      gc();
+      observations = _observations;
+
+      shouldBeFalse("receivedObservations");
+      receivedObservations = true;
+
+      shouldBe("observations.length", "1");
+      shouldBeEqualToString("observations[0].target.tagName", "DIV");
+      shouldBeFalse("observations[0].isIntersecting");
+      shouldBe("observations[0].intersectionRatio", "0.0");
+      shouldBe("observations[0].target.foo", "1");
+      setTimeout(finishJSTest, 100);
+    });
+    let div = document.createElement('div');
+    div.foo = 1;
+    intersectionObserver.observe(div);
+    div = null;
+    gc();
+    setTimeout(gc, 0);
+  };
+</script>
+</body>
+</html>
</ins></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservethendisconnecttargetexpectedtxt"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt                         (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt    2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1,18 @@
</span><ins>+Tests that an observation is received after disconnecting a visible target
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].target.foo is 1
+PASS observations[0].isIntersecting is true
+* Removing target from document
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].target.foo is 1
+PASS observations[0].isIntersecting is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="branchessafari612branchLayoutTestsintersectionobserverobservethendisconnecttargethtml"></a>
<div class="addfile"><h4>Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html (0 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html                         (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html    2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -0,0 +1,43 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<body>
+<script src="../resources/js-test.js"></script>
+<div id="testDiv">test</div>
+<script>
+  description("Tests that an observation is received after disconnecting a visible target");
+  jsTestIsAsync = true;
+
+  let receivedInitialObservation = false;
+  onload = () => {
+    let intersectionObserver = new IntersectionObserver((_observations) => {
+      gc();
+      observations = _observations;
+
+      shouldBe("observations.length", "1");
+      shouldBeEqualToString("observations[0].target.tagName", "DIV");
+      shouldBe("observations[0].target.foo", "1");
+
+      if (!receivedInitialObservation) {
+          receivedInitialObservation = true;
+          shouldBeTrue("observations[0].isIntersecting");
+          setTimeout(() => {
+              debug("* Removing target from document");
+              document.getElementById("testDiv").remove();
+              gc();
+              setTimeout(gc, 0);
+          }, 0);
+      } else {
+          shouldBeFalse("observations[0].isIntersecting");
+          setTimeout(finishJSTest, 100);
+      }
+    });
+    let div = document.getElementById("testDiv");
+    div.foo = 1;
+    intersectionObserver.observe(div);
+    div = null;
+    gc();
+    setTimeout(gc, 0);
+  };
+</script>
+</body>
+</html>
</ins></span></pre></div>
<a id="branchessafari612branchSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: branches/safari-612-branch/Source/WebCore/ChangeLog (284801 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/Source/WebCore/ChangeLog      2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/ChangeLog 2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -1,5 +1,91 @@
</span><span class="cx"> 2021-10-25  Null  <null@apple.com>
</span><span class="cx"> 
</span><ins>+        Cherry-pick r283590. rdar://problem/80837616
+
+    ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+    https://bugs.webkit.org/show_bug.cgi?id=231235
+    <rdar://80837616>
+    
+    Reviewed by Ryosuke Niwa.
+    
+    Source/WebCore:
+    
+    IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+    its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+    implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+    as the JS wrappers of observation / pending targets are alive. However, as per specification,
+    we always need to dispatch an observation for an observation target, even if that target
+    is not connected. Our code was already taking care of dispatching such observation. However,
+    there was nothing keeping the observation target alive in this case and thus nothing keeping
+    the JS callback alive either.
+    
+    To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+    which holds a strong ref to the observation target until the next time we call notify().
+    This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+    us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+    return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+    IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+    
+    Tests: intersection-observer/observe-disconnected-target-crash.html
+           intersection-observer/observe-disconnected-target.html
+    
+    * page/IntersectionObserver.cpp:
+    (WebCore::IntersectionObserver::observe):
+    (WebCore::IntersectionObserver::unobserve):
+    (WebCore::IntersectionObserver::removeAllTargets):
+    (WebCore::IntersectionObserver::notify):
+    (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+    * page/IntersectionObserver.h:
+    
+    LayoutTests:
+    
+    Add layout test coverage both for the crash and the Web facing behavior.
+    
+    * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+    * intersection-observer/observe-disconnected-target-crash.html: Added.
+    * intersection-observer/observe-disconnected-target-expected.txt: Added.
+    * intersection-observer/observe-disconnected-target.html: Added.
+    
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283590 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2021-10-05  Chris Dumez  <cdumez@apple.com>
+
+            ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+            https://bugs.webkit.org/show_bug.cgi?id=231235
+            <rdar://80837616>
+
+            Reviewed by Ryosuke Niwa.
+
+            IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+            its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+            implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+            as the JS wrappers of observation / pending targets are alive. However, as per specification,
+            we always need to dispatch an observation for an observation target, even if that target
+            is not connected. Our code was already taking care of dispatching such observation. However,
+            there was nothing keeping the observation target alive in this case and thus nothing keeping
+            the JS callback alive either.
+
+            To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+            which holds a strong ref to the observation target until the next time we call notify().
+            This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+            us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+            return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+            IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+
+            Tests: intersection-observer/observe-disconnected-target-crash.html
+                   intersection-observer/observe-disconnected-target.html
+
+            * page/IntersectionObserver.cpp:
+            (WebCore::IntersectionObserver::observe):
+            (WebCore::IntersectionObserver::unobserve):
+            (WebCore::IntersectionObserver::removeAllTargets):
+            (WebCore::IntersectionObserver::notify):
+            (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+            * page/IntersectionObserver.h:
+
+2021-10-25  Null  <null@apple.com>
+
</ins><span class="cx">         Cherry-pick r282984. rdar://problem/77587429
</span><span class="cx"> 
</span><span class="cx">     Web Inspector: Graphics: add instrumentation for new `CanvasRenderingContext2DSettings`
</span></span></pre></div>
<a id="branchessafari612branchSourceWebCorepageIntersectionObservercpp"></a>
<div class="modfile"><h4>Modified: branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp (284801 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp  2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp     2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -170,6 +170,11 @@
</span><span class="cx">     bool hadObservationTargets = hasObservationTargets();
</span><span class="cx">     m_observationTargets.append(makeWeakPtr(target));
</span><span class="cx"> 
</span><ins>+    // Per the specification, we should dispatch at least one observation for the target. For this reason, we make sure to keep the
+    // target alive until this first observation. This, in turn, will keep the IntersectionObserver's JS wrapper alive via
+    // isReachableFromOpaqueRoots(), so the callback stays alive.
+    m_targetsWaitingForFirstObservation.append(target);
+
</ins><span class="cx">     auto* document = trackingDocument();
</span><span class="cx">     if (!hadObservationTargets)
</span><span class="cx">         document->addIntersectionObserver(*this);
</span><span class="lines">@@ -183,6 +188,7 @@
</span><span class="cx"> 
</span><span class="cx">     bool removed = m_observationTargets.removeFirst(&target);
</span><span class="cx">     ASSERT_UNUSED(removed, removed);
</span><ins>+    m_targetsWaitingForFirstObservation.removeFirstMatching([&](auto& pendingTarget) { return pendingTarget.ptr() == &target; });
</ins><span class="cx"> 
</span><span class="cx">     if (!hasObservationTargets()) {
</span><span class="cx">         if (auto* document = trackingDocument())
</span><span class="lines">@@ -208,6 +214,7 @@
</span><span class="cx"> void IntersectionObserver::targetDestroyed(Element& target)
</span><span class="cx"> {
</span><span class="cx">     m_observationTargets.removeFirst(&target);
</span><ins>+    m_targetsWaitingForFirstObservation.removeFirstMatching([&](auto& pendingTarget) { return pendingTarget.ptr() == &target; });
</ins><span class="cx">     if (!hasObservationTargets()) {
</span><span class="cx">         if (auto* document = trackingDocument())
</span><span class="cx">             document->removeIntersectionObserver(*this);
</span><span class="lines">@@ -233,6 +240,7 @@
</span><span class="cx">         ASSERT_UNUSED(removed, removed);
</span><span class="cx">     }
</span><span class="cx">     m_observationTargets.clear();
</span><ins>+    m_targetsWaitingForFirstObservation.clear();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void IntersectionObserver::rootDestroyed()
</span><span class="lines">@@ -274,6 +282,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     auto takenRecords = takeRecords();
</span><ins>+    auto targetsWaitingForFirstObservation = std::exchange(m_targetsWaitingForFirstObservation, { });
</ins><span class="cx"> 
</span><span class="cx">     // FIXME: The JSIntersectionObserver wrapper should be kept alive as long as the intersection observer can fire events.
</span><span class="cx">     ASSERT(m_callback->hasCallback());
</span><span class="lines">@@ -299,7 +308,7 @@
</span><span class="cx">         if (visitor.containsOpaqueRoot(target->opaqueRoot()))
</span><span class="cx">             return true;
</span><span class="cx">     }
</span><del>-    return false;
</del><ins>+    return !m_targetsWaitingForFirstObservation.isEmpty();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="branchessafari612branchSourceWebCorepageIntersectionObserverh"></a>
<div class="modfile"><h4>Modified: branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h (284801 => 284802)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h    2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h       2021-10-25 19:09:38 UTC (rev 284802)
</span><span class="lines">@@ -122,6 +122,7 @@
</span><span class="cx">     Vector<WeakPtr<Element>> m_observationTargets;
</span><span class="cx">     Vector<GCReachableRef<Element>> m_pendingTargets;
</span><span class="cx">     Vector<Ref<IntersectionObserverEntry>> m_queuedEntries;
</span><ins>+    Vector<GCReachableRef<Element>> m_targetsWaitingForFirstObservation;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>