为什么 SpeechSynthesisUtterance 有时不会在基于 Chromium 的浏览器中触发 'end' 事件?

Why does a SpeechSynthesisUtterance sometimes not fire an 'end' event in Chromium-based browsers?

在 Chrome (v72, W10) 和 Opera 中,以下片段 偶尔会 does not seem to run 附加的 end 侦听器SpeechSynthesisUtterance,50 次中可能有 1 次是 运行。 (抱歉,在这个的原始版本中,它可以更容易地重现 - 现在,在按钮点击时创建话语看起来使错误变得更加罕见)

button.onclick = () => {
  console.log('start script');
  button.disabled = true;
  const utt = new SpeechSynthesisUtterance('e');
  utt.addEventListener('end', () => {
    console.log('end event triggered');
  });

  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err', err)
  });

  speechSynthesis.speak(utt);
  setTimeout(() => {
    console.log('finished?');
  }, 1500);
};
<button id="button">click</button>

据我所知,如果 end 事件曾经激活,它将 总是 在给定的页面加载中激活,这就是我禁用按钮的原因上面的片段。 (您必须多次重新运行 代码片段才能看到问题)

如果您 运行 在 Chrome(W10 上为 72)中禁用自动播放限制,则可以更轻松地重现它。 (转到 chrome://flags/,将 自动播放策略 更改为 不需要用户手势 )。

(不幸的是,在 Opera 中,它似乎与第一个片段中的重现同样困难)

console.log('start script');
function say(text) {
  const utt = new SpeechSynthesisUtterance(text);
  utt.addEventListener('end', () => console.log('end: ' + text));
  
  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err on ' + text + ', ', err)
  });
  
  speechSynthesis.speak(utt);
}

say('foo');
say('bar');

据我所知,Firefox (56) 没有这个问题 - 在其中,end 侦听器始终正常触发。

我是否以某种方式没有充分正确地附加监听器,或者这是 Chromium 的错误?

Edit/Update:@Ouroborus指出这确实是一个open Chromium bug


我启动了 Sawbuck 并开始尝试重现它。当问题发生时,我一直看到 gc activity 在 'start script' 和 'finished?' 日志之间发生。

成功案例:

失败示例:

因此,似乎 gc 进程正在干扰正在传递的 end 事件。

为了进一步检验这一理论,我开始 chrome 使用 --js-flags="--expose-gc" 标志启用 v8 gc 功能,允许强制垃圾收集。

如果我修改您的测试代码并在 console.log('start script') 之前添加 window.gc(),我将无法再重现该问题(>50 次尝试)。这可能是因为它 reduces/eliminates 在语音发声期间发生 gc 的机会。

看来您 may be able to prevent SpeechSynthesisUtterance 对象未被 console.log-ing 进行 gc。这似乎确实导致了事件的一致传递。显然,如果您正在创建大量这些对象,那么阻止它们的收集可能并不理想:

button.onclick = () => {
  console.log('start script');
  button.disabled = true;
  const utt = new SpeechSynthesisUtterance('e');
  
  // Prevent garbage collection of utt object
  console.log(utt);

  utt.addEventListener('end', () => {
    console.log('end event triggered');
  });

  // just for debugging completeness, no errors seem to be thrown though
  utt.addEventListener('error', (err) => {
    console.log('err', err)
  });

  speechSynthesis.speak(utt);
  setTimeout(() => {
    console.log('finished?');
  }, 1500);
};
<button id="button">click</button>