Bug 1181073 - Relax setTimeout throttling for background tabs using web audio, r=bkelly
☠☠ backed out by a32fdbf877e0 ☠ ☠
authorMichael Layzell <michael@thelayzells.com>
Fri, 06 May 2016 15:08:58 -0400
changeset 298153 e96398029a1c6c0683466f9b0f2afa214be79a75
parent 298152 e12e09384b27f84b34145ced24af38607c378b87
child 298154 4bcefd153a9e515ddbde1961d9b26126ba689f04
push id30273
push userkwierso@gmail.com
push dateFri, 20 May 2016 21:08:12 +0000
treeherdermozilla-central@c403ac05b8f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1181073
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1181073 - Relax setTimeout throttling for background tabs using web audio, r=bkelly
dom/base/nsGlobalWindow.cpp
dom/media/webaudio/AudioContext.cpp
dom/media/webaudio/AudioContext.h
dom/media/webaudio/moz.build
dom/media/webaudio/test/browser.ini
dom/media/webaudio/test/browser_bug1181073.js
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -301,16 +301,26 @@ int32_t gTimeoutCnt                     
 // The default shortest interval/timeout we permit
 #define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
 #define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms
 static int32_t gMinTimeoutValue;
 static int32_t gMinBackgroundTimeoutValue;
 inline int32_t
 nsGlobalWindow::DOMMinTimeoutValue() const {
   bool isBackground = !mOuterWindow || mOuterWindow->IsBackground();
+  if (isBackground) {
+    // Don't use the background timeout value when there are audio contexts with
+    // active nodes, so that background audio can keep running smoothly.
+    for (const AudioContext* ctx : mAudioContexts) {
+      if (ctx->ActiveNodeCount() > 0) {
+        isBackground = false;
+        break;
+      }
+    }
+  }
   return
     std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -654,16 +654,22 @@ AudioContext::UpdatePannerSource()
 }
 
 uint32_t
 AudioContext::MaxChannelCount() const
 {
   return mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels();
 }
 
+uint32_t
+AudioContext::ActiveNodeCount() const
+{
+  return mActiveNodes.Count();
+}
+
 MediaStreamGraph*
 AudioContext::Graph() const
 {
   return Destination()->Stream()->Graph();
 }
 
 MediaStream*
 AudioContext::DestinationStream() const
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -289,16 +289,18 @@ public:
   void UnregisterActiveNode(AudioNode* aNode);
 
   void UnregisterAudioBufferSourceNode(AudioBufferSourceNode* aNode);
   void UnregisterPannerNode(PannerNode* aNode);
   void UpdatePannerSource();
 
   uint32_t MaxChannelCount() const;
 
+  uint32_t ActiveNodeCount() const;
+
   void Mute() const;
   void Unmute() const;
 
   JSObject* GetGlobalJSObject() const;
 
   AudioChannel MozAudioChannelType() const;
 
   AudioChannel TestAudioChannelInAudioNodeStream();
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -10,16 +10,20 @@ with Files('*'):
 DIRS += ['blink']
 
 TEST_DIRS += ['compiledtest']
 
 MOCHITEST_MANIFESTS += [
     'test/mochitest.ini',
 ]
 
+BROWSER_CHROME_MANIFESTS += [
+    'test/browser.ini',
+]
+
 TEST_HARNESS_FILES.testing.mochitest.tests.dom.media.webaudio.test.blink += [
     'test/blink/audio-testing.js',
     'test/blink/convolution-testing.js',
     'test/blink/panner-model-testing.js',
 ]
 
 EXPORTS += [
     'AlignedTArray.h',
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser.ini
@@ -0,0 +1,1 @@
+[browser_bug1181073.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/browser_bug1181073.js
@@ -0,0 +1,65 @@
+add_task(function*() {
+  // Make the min_background_timeout_value very high to avoid problems on slow machines
+  yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+    'set': [['dom.min_background_timeout_value', 3000]]
+  }, resolve));
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com");
+  let browser = gBrowser.selectedBrowser;
+
+  // Make the tab a background tab, so that setInterval will be throttled.
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+  let time = yield ContentTask.spawn(browser, null, function () {
+    return new Promise(resolve => {
+      let start = content.performance.now();
+      let id = content.window.setInterval(function() {
+        let end = content.performance.now();
+        content.window.clearInterval(id);
+        resolve(end - start);
+      }, 0);
+    });
+  });
+
+  ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)");
+
+  time = yield ContentTask.spawn(browser, null, function () {
+    return new Promise(resolve => {
+      // Start playing audio, save it on the window so it doesn't get GCed
+      let audioCtx = content.window.audioCtx = new content.window.AudioContext();
+      let oscillator = audioCtx.createOscillator();
+      oscillator.type = 'square';
+      oscillator.frequency.value = 3000;
+      oscillator.start();
+
+      let start = content.performance.now();
+      let id = content.window.setInterval(function() {
+        let end = content.performance.now();
+        content.window.clearInterval(id);
+        oscillator.stop();
+        resolve(end - start);
+      }, 0);
+    });
+  });
+
+  ok(time < 1000, "Interval is not throttled with audio playing (" + time + " ms)");
+
+  // Destroy the oscillator, but not the audio context
+  Cu.forceGC();
+
+  time = yield ContentTask.spawn(browser, null, function () {
+    return new Promise(resolve => {
+      let start = content.performance.now();
+      let id = content.window.setInterval(function() {
+        let end = content.performance.now();
+        content.window.clearInterval(id);
+        resolve(end - start);
+      }, 0);
+    });
+  });
+
+  ok(time > 2000, "Interval is throttled with audio stopped (" + time + " ms)");
+
+  while (gBrowser.tabs.length > 1)
+    gBrowser.removeCurrentTab();
+});