Merge mozilla-central into services-central
authorGregory Szorc <gps@mozilla.com>
Tue, 05 Feb 2013 22:40:34 -0800
changeset 121005 bc108d2ce8d17a560e2fc2affa6867ca9418448d
parent 121004 65e0a461f1eff292c87ba4c4336c0571172524c7 (current diff)
parent 120920 8ac096b24cb0ed3d8c3b4a546aac9dc221f36092 (diff)
child 121006 ed29092600501b9b2798c6a9c0ee643eed8281ef
child 121025 04e13fc9dbff9391f9a42ab2f084a1f4f13e0729
push id22524
push userryanvm@gmail.com
push dateWed, 06 Feb 2013 13:23:43 +0000
treeherdermozilla-inbound@470187171cf1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
bc108d2ce8d1 / 21.0a1 / 20130206031027 / files
nightly linux64
bc108d2ce8d1 / 21.0a1 / 20130206031027 / files
nightly mac
bc108d2ce8d1 / 21.0a1 / 20130206031027 / files
nightly win32
bc108d2ce8d1 / 21.0a1 / 20130206031027 / files
nightly win64
bc108d2ce8d1 / 21.0a1 / 20130206031027 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central into services-central
content/media/AudioEventTimeline.h
content/media/webaudio/AudioEventTimeline.h
docshell/test/bug94514-postpage.html
docshell/test/test_bug94514.html
testing/mozbase/README
toolkit/components/telemetry/Histograms.json
new file mode 100644
index 0000000000000000000000000000000000000000..826e5340843246732f7001c836f4a3ec37681c86
GIT binary patch
literal 3409
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
new file mode 100644
index 0000000000000000000000000000000000000000..980e787310a9a1391715ba8d3b9bd9bfbc2b5b5c
GIT binary patch
literal 3382
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
M07*qoM6N<$f>WkX<^TWy
index 0bd1e60e6a3e2d23f43bfbba3e5c86afa71acf00..6daf7cf7188b00fd28c1c8c3a54673b262036a77
GIT binary patch
literal 3217
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
D&kXdV
index e377d321ce03505c9127296eab43b80d3b39f6df..c7837f8229c03de1fcd0c02ef3aa78573e0a69b8
GIT binary patch
literal 3042
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
index a8482eb4dd7ebaf168eda6a8ca82e77efeffb930..fd64f969724698d9694b4effceb3896aaeaa56ac
GIT binary patch
literal 3318
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
A?*IS*
index 49c60505f46523e7c3b0a0e5f052c7b450f26c78..b965b73d55b725a4b69c2a5620b761e7435f5ab1
GIT binary patch
literal 3967
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
Z9RQGDd}f+!o@W36002ovPDHLkV1m^HVb1^n
index 4dbb94f98f0acf7f1a3df73ff3fbf4eb9345f2bf..5de342bda53b604fe2413c8e769e7bbf14e70dc0
GIT binary patch
literal 3259
zc$@*d3`FyZP)<h;3K|Lk000e1NJLTq001fg001fo1^@s6#ly*400009a7bBm000XU
z000XU0RWnu7ytkYPiaF#P*7-ZbZ>KLZ*U+<Lqi~Na&Km7Y-Iodc-oy)XH-+^7Crag
z^g>IBfRsybQWXdwQbLP>6p<z>Aqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uh<iVD~V
z<RPMtgQJLw%KPDaqifc@_vX$1wbwr9tn;0-&j-K=43<bUQ8j=JsX`tR;Dg7+#^K~H
zK!FM*Z~zbpvt%K2{UZSY_<lS*D<Z%Lz5oGu(+dayz)hRLFdT>f59&ghTmgWD0l;*T
zI7<kC6aYYajzXpYKt=(8otP$50H6c_V9R4-;{Z@C0AMG7=F<Rxo%or10RUT+Ar%3j
zkpLhQWr#!oXgdI`&sK^>09Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p
z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-<?i
z0%4j!F2Z@488U%158(66005wo6%pWr^Zj_v4zAA5HjcIqUoGmt2LB>rV&neh&#Q1i
z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_<lS*MWK+n+1cgf
z<k(8YLR(?VSAG6x!e78w{cQPuJpA|d;J)G{fihizM+Erb!p!tcr5w+a34~(Y=8s4G
zw+sLL9n&JjNn*KJDiq^U5^;`1nvC-@r6P$!k}1U{(*I=Q-z@tBKHoI}uxdU5dyy@u
zU1J0GOD7Ombim^G008p4Z^6_k2m^p<gW=D2|L;HjN1!DDfM!XOaR2~bL?kX$%CkSm
z2mk;?pn)o|K^yeJ7%adB9Ki+L!3+FgHiSYX#KJ-lLJDMn9CBbOtb#%)hRv`YDqt_v
zKpix|QD}yfa1JiQRk#j4a1Z)n2%f<xynzV>LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW
zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_Ifq<Ex{*7`05XF7hP+2Hl!3BQJ=6@fL%FCo
z8iYoo3(#bAF`ADSpqtQgv>H8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X
zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ<AYmRsNLWl*PS{AOARHt#5!wki2?K;t
z!Y3k=s7tgax)J%r7-BLphge7~Bi0g+6E6^Zh(p9TBoc{3GAFr^0!gu?RMHaCM$&Fl
zBk3%un>0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4
z<uv66WtcKSRim0x-Ke2d5jBrmLam{;Qm;{ms1r1GnmNsb7D-E`t)i9F8fX`2_i3-_
zbh;7Ul^#x)&{xvS=|||7=mYe33=M`AgU5(xC>fg=2N-7=cNnjjOr{yriy6mMFgG#l
znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U
zt5vF<Q0r40Q)j6=sE4X&sBct1q<&fbi3VB2Ov6t@q*0);U*o*SAPZv|vv@2aYYnT0
zb%8a+Cb7-ge0D0knEf5Qi#@8Tp*ce{N;6lpQuCB%KL_KOarm5cP6_8Ir<e17iry6O
zDdH&`rZh~sF=bq9s+O0QSgS~@QL9Jmy*94xr=6y~MY~!1fet~(N+(<=M`w@D1)b+p
z*;C!83a1uLJv#NSE~;y#8=<>IcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya?
z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y
zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB
zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt
z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a<fJbF^|4I#xQ~n$Dc=
zKYhjYmgz5NSkDm8*fZm{6U!;YX`NG>(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C
z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB
zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe
zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0
z?2xS?_ve_-k<Mujg;0Lz*3buG=3$G&ehepthlN*$KaOySSQ^nWmo<0M+(UEUMEXRQ
zMBbZcF;6+KElM>iKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$
z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4
z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu
zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu
z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E
ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw
zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX
z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i&
z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01
z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R
z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw
zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD
zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3|
zawq-H%e&ckC+@AhPrP6BK<z=<L*0kfKU@CX*zeqbYQT4(^U>T#_XdT7&;F71j}Joy
zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z
zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot<a{81DF0~rvGr5Xr~8u`lav1h
z1DNytV>2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F}
z0005zNkl<Zc-rjPJ4?e*6u|NSZR->1AgD`4v<`xU;!xZibg<}Vb<)vYT?G9Yeg_2y
z5kc{RI$0$kI68@n6+w`4QK*mRI`}}Ev?ghqDCA6mn~-15CFh=dg;I+D5F!3a1keB)
zKwXN6)f<Hn<^!6v`kqeU+%S@eeqWX<m0o1D=M#8BB3f(mp+ji0(eJ760}g;VdOi$v
z6^y5VE%I;MboEyG1?>a&fMVH70n32mWPg?Ov$!8n>&T;>MP|njfIF|DPLLNs)=Ow<
z<kI?(6C{AJ+C3S{lO~hER6&UwEQ(Z02Xutciy=!{oIy3&0New$w(_miE9u>ZZ=@!(
zm9C?Wj*veysmZLJ$GurXzmRhi`%aMFKwUsq9_kXZvQQ(k`R}!PMr0eXY1)+d1~o_S
zp;Yr{tM><>#gNd1(nEIoU$>wSK+ZDKx2;Gb`iP!S@si7Ufux+NAQY~nI^5y~(s@QT
znF3Y-o%HTPL7j}CRF(g`(4R>2wfmIJ<p_FS`Um|T`4O@9G`H7DH?bktkrl$)$>^v7
z`CDfmYfqE88s_Es#>>Bcn!}AX<|l8gZXW<>?Koy;tec+xfpVaBJA47u15Y+jN+1yx
tKn<GAIXNj2!HIJK4WI!ufR+{S0|0{?If~?5rz!vd002ovPDHLkV1lJ(7X<(S
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -832,16 +832,17 @@ var WebappsHelper = {
       case "webapps-launch":
         DOMApplicationRegistry.getManifestFor(json.origin, function(aManifest) {
           if (!aManifest)
             return;
 
           let manifest = new ManifestHelper(aManifest, json.origin);
           shell.sendChromeEvent({
             "type": "webapps-launch",
+            "timestamp": json.timestamp,
             "url": manifest.fullLaunchPath(json.startPoint),
             "manifestURL": json.manifestURL
           });
         });
         break;
       case "webapps-ask-install":
         let id = this.registerInstaller(json);
         shell.sendChromeEvent({
--- a/b2g/chrome/content/touchcontrols.css
+++ b/b2g/chrome/content/touchcontrols.css
@@ -22,46 +22,59 @@
   width: 100%;
 }
 
 .controlsSpacer {
   display: none;
   -moz-box-flex: 0;
 }
 
+.fullscreenButton,
 .playButton,
 .muteButton {
   -moz-appearance: none;
   min-height: 42px;
   min-width: 42px;
   border: none !important;
 }
 
+.fullscreenButton {
+  background: url("chrome://browser/content/images/fullscreen-hdpi.png") no-repeat center;
+}
+
+.fullscreenButton[fullscreened="true"] {
+  background: url("chrome://browser/content/images/exitfullscreen-hdpi.png") no-repeat center;
+}
+
 .playButton {
-  -moz-transform: translateX(21px);
   background: url("chrome://browser/content/images/pause-hdpi.png") no-repeat center;
 }
 
+/*
+ * Normally the button bar has fullscreen spacer play spacer mute, but if
+ * this is an audio control rather than a video control, the fullscreen button
+ * is hidden by videocontrols.xml, and that alters the position of the
+ * play button.  This workaround moves it back to center.
+ */
+.controlBar.audio .playButton {
+  transform: translateX(28px);
+}
+
 .playButton[paused="true"] {
   background: url("chrome://browser/content/images/play-hdpi.png") no-repeat center;
 }
 
 .muteButton {
   background: url("chrome://browser/content/images/mute-hdpi.png") no-repeat center;
 }
 
 .muteButton[muted="true"] {
   background: url("chrome://browser/content/images/unmute-hdpi.png") no-repeat center;
 }
 
-/* This button is hidden until bug 704229 is fixed. */
-.fullscreenButton {
-  display: none;
-}
-
 /* bars */
 .scrubberStack {
   width: 100%;
   min-height: 32px;
   max-height: 32px;
   padding: 0px 8px;
   margin: 0px;
 }
@@ -73,16 +86,17 @@
 .scrubber,
 .scrubber .scale-slider,
 .scrubber .scale-thumb {
   -moz-appearance: none;
   border: none;
   padding: 0px;
   margin: 0px;
   background-color: transparent;
+  border-radius: 3px;
 }
 
 .bufferBar {
   border: 1px solid #5e6166;
 }
 
 .bufferBar,
 .progressBar {
@@ -163,27 +177,24 @@
   -moz-transition-delay: 750ms;
 }
 
 .statusOverlay[fadeout] {
   opacity: 0;
 }
 
 .volumeStack,
+.controlBar[firstshow="true"] .fullscreenButton,
 .controlBar[firstshow="true"] .muteButton,
 .controlBar[firstshow="true"] .scrubberStack,
 .controlBar[firstshow="true"] .durationBox,
 .timeLabel {
   display: none;
 }
 
-.controlBar[firstshow="true"] .playButton {
-  -moz-transform: none;
-}
-
 /* Error description formatting */
 .errorLabel {
   font-family: Helvetica, Arial, sans-serif;
   font-size: 11px;
   color: #bbb;
   text-shadow:
     -1px -1px 0 #000,
     1px -1px 0 #000,
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -36,10 +36,12 @@ chrome.jar:
   content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
   content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
   content/images/errorpage-warning.png (content/images/errorpage-warning.png)
   content/images/scrubber-hdpi.png     (content/images/scrubber-hdpi.png)
   content/images/unmute-hdpi.png       (content/images/unmute-hdpi.png)
   content/images/pause-hdpi.png        (content/images/pause-hdpi.png)
   content/images/play-hdpi.png         (content/images/play-hdpi.png)
   content/images/mute-hdpi.png         (content/images/mute-hdpi.png)
+  content/images/fullscreen-hdpi.png     (content/images/fullscreen-hdpi.png)
+  content/images/exitfullscreen-hdpi.png (content/images/exitfullscreen-hdpi.png)
   content/images/throbber.png          (content/images/throbber.png)
   content/images/error.png             (content/images/error.png)
--- a/b2g/config/panda/releng-pandaboard.tt
+++ b/b2g/config/panda/releng-pandaboard.tt
@@ -1,8 +1,8 @@
 [
 {
-"size": 676768064,
-"digest": "d51092cc586934f21b84e62447f49d32548cd3f222a092f2b7b8c04ff59231c97969140e0b1d897c22bcb12b8bf6a9c8b24e55891896c7eeb9bc0f190980813d",
+"size": 678628568,
+"digest": "e0b98a75831313171781d91ab4c3b8ae66e3af7fcec433a33d75fbc647cb3bba311f790ccb9cd9c8be8c5549210b759d6b85afe7395ada74c2c319a29556fd8e",
 "algorithm": "sha512",
 "filename": "gonk.tar.xz"
 }
 ]
--- a/b2g/test/emulator.manifest
+++ b/b2g/test/emulator.manifest
@@ -1,6 +1,6 @@
 [{
-"size": 631436376,
-"digest": "cea07d65a39a244961f183711b14d054c90690b69a79d89a3783f9a634f9ace7f6e70033e963a4f58ca8482b3aec8d4c5d3227cc7a0bc61e6afeccf2acc1a789",
+"size": 608318343,
+"digest": "9ab6487eccf44b0781cc96c2af9ba497f720a8d289bde40e29417f9db82788d6c8653c7dafa7443069f5898635eef45fa048ee99c03a9d0113c019a2f80f5aa8",
 "algorithm": "sha512",
 "filename": "emulator.zip"
-}]
\ No newline at end of file
+}]
--- a/browser/base/content/test/browser_sanitize-timespans.js
+++ b/browser/base/content/test/browser_sanitize-timespans.js
@@ -1,28 +1,29 @@
 // Bug 453440 - Test the timespan-based logic of the sanitizer code
 var now_uSec = Date.now() * 1000;
 
 const dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
-const bhist = Cc["@mozilla.org/browser/global-history;2"].getService(Ci.nsIBrowserHistory);
 const formhist = Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);
 
 const kUsecPerMin = 60 * 1000000;
 
 let tempScope = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
 let Sanitizer = tempScope.Sanitizer;
 
 function test() {
   waitForExplicitFinish();
 
   setupDownloads();
   setupFormHistory();
-  setupHistory(onHistoryReady);
+  setupHistory(function() {
+    Task.spawn(onHistoryReady).then(finish);
+  });
 }
 
 function onHistoryReady() {
   var hoursSinceMidnight = new Date().getHours();
   var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
 
   // Should test cookies here, but nsICookieManager/nsICookieService
   // doesn't let us fake creation times.  bug 463127
@@ -40,29 +41,38 @@ function onHistoryReady() {
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", false);
 
   // Clear 10 minutes ago
   s.range = [now_uSec - 10*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://10minutes.com")), "10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://1hour.com")), "Pretend visit to 1hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  
-  if (minutesSinceMidnight > 10)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
+     "Pretend visit to 10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+     "Pretend visit to 1hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 10) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("10minutes"), "10minutes form entry should be deleted");
   ok(formhist.nameExists("1hour"), "1hour form entry should still exist");
   ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 10)
@@ -79,28 +89,36 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
 
   if (minutesSinceMidnight > 10)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 1 hour
   Sanitizer.prefs.setIntPref("timeSpan", 1);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://1hour.com")), "1hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://1hour10minutes.com/")), "Pretend visit to 1hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  
-  if (hoursSinceMidnight > 1)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+     "Pretend visit to 1hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 1) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("1hour"), "1hour form entry should be deleted");
   ok(formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should still exist");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 1)
     ok(formhist.nameExists("today"), "today form entry should still exist");
@@ -116,26 +134,34 @@ function onHistoryReady() {
 
   if (hoursSinceMidnight > 1)
     ok(downloadExists(5555554), "'Today' download should still be present");
   
   // Clear 1 hour 10 minutes
   s.range = [now_uSec - 70*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://1hour10minutes.com")), "Pretend visit to 1hour10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (minutesSinceMidnight > 70)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+     "Pretend visit to 1hour10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 70) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("1hour10minutes"), "1hour10minutes form entry should be deleted");
   ok(formhist.nameExists("2hour"), "2hour form entry should still exist");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 70)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
@@ -147,25 +173,32 @@ function onHistoryReady() {
   ok(downloadExists(5555553), "<4 hour old download should still be present");
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   if (minutesSinceMidnight > 70)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 2 hours
   Sanitizer.prefs.setIntPref("timeSpan", 2);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://2hour.com")), "Pretend visit to 2hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://2hour10minutes.com/")), "Pretend visit to 2hour10minutes.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (hoursSinceMidnight > 2)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+     "Pretend visit to 2hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 2) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("2hour"), "2hour form entry should be deleted");
   ok(formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should still exist");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 2)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
@@ -177,24 +210,30 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   if (hoursSinceMidnight > 2)
     ok(downloadExists(5555554), "'Today' download should still be present");
   
   // Clear 2 hours 10 minutes
   s.range = [now_uSec - 130*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://2hour10minutes.com")), "Pretend visit to 2hour10minutes.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should still exist");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (minutesSinceMidnight > 130)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+     "Pretend visit to 2hour10minutes.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (minutesSinceMidnight > 130) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("2hour10minutes"), "2hour10minutes form entry should be deleted");
   ok(formhist.nameExists("4hour"), "4hour form entry should still exist");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (minutesSinceMidnight > 130)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555557), "2 hour 10 minute old download should now be deleted");
@@ -202,44 +241,53 @@ function onHistoryReady() {
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   ok(downloadExists(5555550), "Year old download should still be present");
   if (minutesSinceMidnight > 130)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 4 hours
   Sanitizer.prefs.setIntPref("timeSpan", 3);
   s.sanitize();
-  
-  ok(!bhist.isVisited(makeURI("http://4hour.com")), "Pretend visit to 4hour.com should now be deleted");
-  ok(bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should still exist");
-  if (hoursSinceMidnight > 4)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
-  
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+     "Pretend visit to 4hour.com should now be deleted");
+  ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should should still exist");
+  if (hoursSinceMidnight > 4) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
+
   ok(!formhist.nameExists("4hour"), "4hour form entry should be deleted");
   ok(formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should still exist");
   if (hoursSinceMidnight > 4)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
   ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
   ok(downloadExists(5555550), "Year old download should still be present");
   if (hoursSinceMidnight > 4)
     ok(downloadExists(5555554), "'Today' download should still be present");
 
   // Clear 4 hours 10 minutes
   s.range = [now_uSec - 250*60*1000000, now_uSec];
   s.sanitize();
   s.range = null;
-  
-  ok(!bhist.isVisited(makeURI("http://4hour10minutes.com/")), "Pretend visit to 4hour10minutes.com should now be deleted");
-  if (minutesSinceMidnight > 250)
-    ok(bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should still exist");
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+
+  ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+     "Pretend visit to 4hour10minutes.com should now be deleted");
+  if (minutesSinceMidnight > 250) {
+    ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should still exist");
+  }
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+    "Pretend visit to before-today.com should still exist");
   
   ok(!formhist.nameExists("4hour10minutes"), "4hour10minutes form entry should be deleted");
   if (minutesSinceMidnight > 250)
     ok(formhist.nameExists("today"), "today form entry should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
 
   ok(!downloadExists(5555558), "4 hour 10 minute download should now be deleted");
   ok(downloadExists(5555550), "Year old download should still be present");
@@ -252,36 +300,37 @@ function onHistoryReady() {
 
   // Be careful.  If we add our objectss just before midnight, and sanitize
   // runs immediately after, they won't be expired.  This is expected, but
   // we should not test in that case.  We cannot just test for opposite
   // condition because we could cross midnight just one moment after we
   // cache our time, then we would have an even worse random failure.
   var today = isToday(new Date(now_uSec/1000));
   if (today) {
-    ok(!bhist.isVisited(makeURI("http://today.com")), "Pretend visit to today.com should now be deleted");
+    ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
+       "Pretend visit to today.com should now be deleted");
     ok(!formhist.nameExists("today"), "today form entry should be deleted");
     ok(!downloadExists(5555554), "'Today' download should now be deleted");
   }
 
-  ok(bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should still exist");
+  ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+     "Pretend visit to before-today.com should still exist");
   ok(formhist.nameExists("b4today"), "b4today form entry should still exist");
   ok(downloadExists(5555550), "Year old download should still be present");
 
   // Choose everything
   Sanitizer.prefs.setIntPref("timeSpan", 0);
   s.sanitize();
 
-  ok(!bhist.isVisited(makeURI("http://before-today.com")), "Pretend visit to before-today.com should now be deleted");
+  ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+     "Pretend visit to before-today.com should now be deleted");
 
   ok(!formhist.nameExists("b4today"), "b4today form entry should be deleted");
 
   ok(!downloadExists(5555550), "Year old download should now be deleted");
-
-  finish();
 }
 
 function setupHistory(aCallback) {
   let places = [];
 
   function addPlace(aURI, aTitle, aVisitDate) {
     places.push({
       uri: aURI,
--- a/browser/base/content/test/browser_sanitizeDialog.js
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -72,20 +72,21 @@ var gAllTests = [
         // Show details
         this.toggleDetails();
         this.checkDetails(true);
 
         // Hide details
         this.toggleDetails();
         this.checkDetails(false);
         this.cancelDialog();
-
-        ensureHistoryClearedState(uris, false);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, false);
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
@@ -131,28 +132,29 @@ var gAllTests = [
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", true,
                    "history pref should be true after accepting dialog with " +
                    "history checkbox checked");
         boolPrefIs("cpd.downloads", true,
                    "downloads pref should be true after accepting dialog with " +
                    "history checkbox checked");
-
+      };
+      wh.onunload = function () {
         // History visits and downloads within one hour should be cleared.
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
 
         // Visits and downloads > 1 hour should still exist.
-        ensureHistoryClearedState(olderURIs, false);
+        yield promiseHistoryClearedState(olderURIs, false);
         ensureDownloadsClearedState(olderDownloadIDs, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(olderURIs, true);
+        yield promiseHistoryClearedState(olderURIs, true);
         ensureDownloadsClearedState(olderDownloadIDs, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
@@ -195,25 +197,26 @@ var gAllTests = [
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", false,
                    "history pref should be false after accepting dialog with " +
                    "history checkbox unchecked");
         boolPrefIs("cpd.downloads", false,
                    "downloads pref should be false after accepting dialog with " +
                    "history checkbox unchecked");
-
+      };
+      wh.onunload = function () {
         // Of the three only form entries should be cleared.
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
         ensureDownloadsClearedState(downloadIDs, false);
         ensureFormEntriesClearedState(formEntries, true);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
@@ -248,17 +251,19 @@ var gAllTests = [
         this.toggleDetails();
         this.checkDetails(true);
 
         this.acceptDialog();
 
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * Ensures that the "Everything" warning is visible on dialog open after
    * the previous test.
@@ -283,17 +288,19 @@ var gAllTests = [
            "with clearing everything");
         this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         this.checkPrefCheckbox("history", true);
         this.acceptDialog();
 
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
       };
       wh.open();
     });
   },
 
   /**
    * The next three tests checks that when a certain history item cannot be
    * cleared then the checkbox should be both disabled and unchecked.
@@ -316,18 +323,19 @@ var gAllTests = [
 
         var cb = this.win.document.querySelectorAll(
                    "#itemList > [preference='privacy.cpd.history']");
         ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
            "clear history should be enabled.");
 
         this.checkAllCheckboxes();
         this.acceptDialog();
-
-        ensureHistoryClearedState(uris, true);
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, true);
         ensureFormEntriesClearedState(formEntries, true);
       };
       wh.open();
     });
   },
   function () {
     let wh = new WindowHelper();
     wh.onload = function() {
@@ -368,16 +376,18 @@ var gAllTests = [
 
       var cb = this.win.document.querySelectorAll(
                  "#itemList > [preference='privacy.cpd.formdata']");
       ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
          "There exists formEntries so the checkbox should be in sync with " +
          "the pref.");
 
       this.acceptDialog();
+    };
+    wh.onunload = function () {
       ensureFormEntriesClearedState(formEntries, true);
     };
     wh.open();
   },
 
 
   /**
    * These next six tests together ensure that toggling details persists
@@ -778,19 +788,23 @@ WindowHelper.prototype = {
 
         win.removeEventListener("unload", onunload, false);
         wh.win = win;
 
         executeSoon(function () {
           // Some exceptions that reach here don't reach the test harness, but
           // ok()/is() do...
           try {
-            if (wh.onunload)
-              wh.onunload();
-            waitForAsyncUpdates(doNextTest);
+            if (wh.onunload) {
+              Task.spawn(wh.onunload).then(function() {
+                waitForAsyncUpdates(doNextTest);
+              });
+            } else {
+              waitForAsyncUpdates(doNextTest);
+            }
           }
           catch (exc) {
             win.close();
             ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
             finish();
           }
         });
       }, false);
@@ -897,55 +911,16 @@ function addFormEntryWithMinutesAgo(aMin
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
 /**
- * Waits for all pending async statements on the default connection, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- * @param aScope
- *        Scope for the callback.
- * @param aArguments
- *        Arguments array for the callback.
- *
- * @note The result is achieved by asynchronously executing a query requiring
- *       a write lock.  Since all statements on the same connection are
- *       serialized, the end of this write operation means that all writes are
- *       complete.  Note that WAL makes so that writers don't block readers, but
- *       this is a problem only across different connections.
- */
-function waitForAsyncUpdates(aCallback, aScope, aArguments)
-{
-  let scope = aScope || this;
-  let args = aArguments || [];
-  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
-  begin.executeAsync();
-  begin.finalize();
-
-  let commit = db.createAsyncStatement("COMMIT");
-  commit.executeAsync({
-    handleResult: function() {},
-    handleError: function() {},
-    handleCompletion: function(aReason)
-    {
-      aCallback.apply(scope, args);
-    }
-  });
-  commit.finalize();
-}
-
-/**
  * Ensures that the given pref is the expected value.
  *
  * @param aPrefName
  *        The pref's sub-branch under the privacy branch
  * @param aExpectedVal
  *        The pref's expected value
  * @param aMsg
  *        Passed to is()
@@ -1019,32 +994,16 @@ function ensureFormEntriesClearedState(a
   let niceStr = aShouldBeCleared ? "no longer" : "still";
   aFormEntries.forEach(function (entry) {
     is(formhist.nameExists(entry), !aShouldBeCleared,
        "form entry " + entry + " should " + niceStr + " exist");
   });
 }
 
 /**
- * Ensures that the specified URIs are either cleared or not.
- *
- * @param aURIs
- *        Array of page URIs
- * @param aShouldBeCleared
- *        True if each visit to the URI should be cleared, false otherwise
- */
-function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aURIs.forEach(function (aURI) {
-    is(PlacesUtils.bhistory.isVisited(aURI), !aShouldBeCleared,
-       "history visit " + aURI.spec + " should " + niceStr + " exist");
-  });
-}
-
-/**
  * Ensures that the given pref is the expected value.
  *
  * @param aPrefName
  *        The pref's sub-branch under the privacy branch
  * @param aExpectedVal
  *        The pref's expected value
  * @param aMsg
  *        Passed to is()
--- a/browser/base/content/test/browser_sanitizeDialog_treeView.js
+++ b/browser/base/content/test/browser_sanitizeDialog_treeView.js
@@ -72,21 +72,21 @@ var gAllTests = [
         wh.checkGrippy("Grippy should remain at last row after trying to move " +
                        "it down",
                        wh.getRowCount() - 1);
 
         // Cancel the dialog, make sure history visits are not cleared.
         wh.checkPrefCheckbox("history", false);
 
         wh.cancelDialog();
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
    */
@@ -128,26 +128,26 @@ var gAllTests = [
         wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
                        "duration",
                        uris.length);
 
         // Accept the dialog, make sure history visits and downloads within one
         // hour are cleared.
         wh.checkPrefCheckbox("history", true);
         wh.acceptDialog();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
 
         // Make sure visits and downloads > 1 hour still exist.
-        ensureHistoryClearedState(olderURIs, false);
+        yield promiseHistoryClearedState(olderURIs, false);
         ensureDownloadsClearedState(olderDownloadIDs, false);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(olderURIs, true);
+        yield promiseHistoryClearedState(olderURIs, true);
         ensureDownloadsClearedState(olderDownloadIDs, true);
       });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
    * history visits nor downloads when not checked.
@@ -182,23 +182,23 @@ var gAllTests = [
                        wh.getRowCount() - 1);
 
         // Remove only form entries, leave history (including downloads).
         wh.checkPrefCheckbox("history", false);
         wh.checkPrefCheckbox("formdata", true);
         wh.acceptDialog();
 
         // Of the three only form entries should be cleared.
-        ensureHistoryClearedState(uris, false);
+        yield promiseHistoryClearedState(uris, false);
         ensureDownloadsClearedState(downloadIDs, false);
         ensureFormEntriesClearedState(formEntries, true);
 
         // OK, done, cleanup after ourselves.
         blankSlate();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
         ensureDownloadsClearedState(downloadIDs, true);
       });
     });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
    */
@@ -217,17 +217,17 @@ var gAllTests = [
     addVisits(places, function() {
 
       // Open the dialog and do our tests.
       openWindow(function (aWin) {
         let wh = new WindowHelper(aWin);
         wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         wh.checkPrefCheckbox("history", true);
         wh.acceptDialog();
-        ensureHistoryClearedState(uris, true);
+        yield promiseHistoryClearedState(uris, true);
       });
     });
   }
 ];
 
 // Used as the download database ID for a new download.  Incremented for each
 // new download.  See addDownloadWithMinutesAgo().
 var gDownloadId = 5555551;
@@ -498,55 +498,16 @@ function addFormEntryWithMinutesAgo(aMin
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
 /**
- * Waits for all pending async statements on the default connection, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- * @param aScope
- *        Scope for the callback.
- * @param aArguments
- *        Arguments array for the callback.
- *
- * @note The result is achieved by asynchronously executing a query requiring
- *       a write lock.  Since all statements on the same connection are
- *       serialized, the end of this write operation means that all writes are
- *       complete.  Note that WAL makes so that writers don't block readers, but
- *       this is a problem only across different connections.
- */
-function waitForAsyncUpdates(aCallback, aScope, aArguments)
-{
-  let scope = aScope || this;
-  let args = aArguments || [];
-  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
-  begin.executeAsync();
-  begin.finalize();
-
-  let commit = db.createAsyncStatement("COMMIT");
-  commit.executeAsync({
-    handleResult: function() {},
-    handleError: function() {},
-    handleCompletion: function(aReason)
-    {
-      aCallback.apply(scope, args);
-    }
-  });
-  commit.finalize();
-}
-
-/**
  * Checks to see if the download with the specified ID exists.
  *
  * @param  aID
  *         The ID of the download to check
  * @return True if the download exists, false otherwise
  */
 function downloadExists(aID)
 {
@@ -606,32 +567,16 @@ function ensureFormEntriesClearedState(a
   let niceStr = aShouldBeCleared ? "no longer" : "still";
   aFormEntries.forEach(function (entry) {
     is(formhist.nameExists(entry), !aShouldBeCleared,
        "form entry " + entry + " should " + niceStr + " exist");
   });
 }
 
 /**
- * Ensures that the specified URIs are either cleared or not.
- *
- * @param aURIs
- *        Array of page URIs
- * @param aShouldBeCleared
- *        True if each visit to the URI should be cleared, false otherwise
- */
-function ensureHistoryClearedState(aURIs, aShouldBeCleared) {
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  aURIs.forEach(function (aURI) {
-    is(PlacesUtils.bhistory.isVisited(aURI), !aShouldBeCleared,
-       "history visit " + aURI.spec + " should " + niceStr + " exist");
-  });
-}
-
-/**
  * Opens the sanitize dialog and runs a callback once it's finished loading.
  * 
  * @param aOnloadCallback
  *        A function that will be called once the dialog has loaded
  */
 function openWindow(aOnloadCallback) {
   function windowObserver(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened")
@@ -640,18 +585,21 @@ function openWindow(aOnloadCallback) {
     Services.ww.unregisterNotification(windowObserver);
     let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
     win.addEventListener("load", function onload(event) {
       win.removeEventListener("load", onload, false);
       executeSoon(function () {
         // Some exceptions that reach here don't reach the test harness, but
         // ok()/is() do...
         try {
-          aOnloadCallback(win);
-          waitForAsyncUpdates(doNextTest);
+          Task.spawn(function() {
+            aOnloadCallback(win);
+          }).then(function() {
+            waitForAsyncUpdates(doNextTest);
+          });
         }
         catch (exc) {
           win.close();
           ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
           finish();
         }
       });
     }, false);
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -1,8 +1,18 @@
+netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm");
+
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
   }, "browser-delayed-startup-finished", false);
 }
@@ -127,16 +137,71 @@ function resetBlocklist() {
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
     aCallback(win);
   }, false);
 }
 
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ *        Function to be called when done.
+ * @param aScope
+ *        Scope for the callback.
+ * @param aArguments
+ *        Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ *       a write lock.  Since all statements on the same connection are
+ *       serialized, the end of this write operation means that all writes are
+ *       complete.  Note that WAL makes so that writers don't block readers, but
+ *       this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments) {
+  let scope = aScope || this;
+  let args = aArguments || [];
+  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+                              .DBConnection;
+  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+  begin.executeAsync();
+  begin.finalize();
+
+  let commit = db.createAsyncStatement("COMMIT");
+  commit.executeAsync({
+    handleResult: function() {},
+    handleError: function() {},
+    handleCompletion: function(aReason) {
+      aCallback.apply(scope, args);
+    }
+  });
+  commit.finalize();
+}
+
+/**
+ * Asynchronously check a url is visited.
+
+ * @param aURI The URI.
+ * @param aExpectedValue The expected value.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI, aExpectedValue) {
+  let deferred = Promise.defer();
+  PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+    deferred.resolve(aIsVisited);
+  });
+
+  return deferred.promise;
+}
+
 function addVisits(aPlaceInfo, aCallback) {
   let places = [];
   if (aPlaceInfo instanceof Ci.nsIURI) {
     places.push({ uri: aPlaceInfo });
   } else if (Array.isArray(aPlaceInfo)) {
     places = places.concat(aPlaceInfo);
   } else {
     places.push(aPlaceInfo);
@@ -165,8 +230,35 @@ function addVisits(aPlaceInfo, aCallback
       handleResult: function () {},
       handleCompletion: function UP_handleCompletion() {
         if (aCallback)
           aCallback();
       }
     }
   );
 }
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ *        Array of page URIs
+ * @param aShouldBeCleared
+ *        True if each visit to the URI should be cleared, false otherwise
+ */
+function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+  let deferred = Promise.defer();
+  let callbackCount = 0;
+  let niceStr = aShouldBeCleared ? "no longer" : "still";
+  function callbackDone() {
+    if (++callbackCount == aURIs.length)
+      deferred.resolve();
+  }
+  aURIs.forEach(function (aURI) {
+    PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+      is(aIsVisited, !aShouldBeCleared,
+         "history visit " + aURI.spec + " should " + niceStr + " exist");
+      callbackDone();
+    });
+  });
+
+  return deferred.promise;
+}
--- a/browser/base/content/test/social/browser_social_chatwindow.js
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -186,17 +186,17 @@ var tests = {
         case "got-chatbox-message":
           ok(true, "got a chat window opened");
           let chats = document.getElementById("pinnedchats");
           ok(chats.selectedChat.minimized, "chatbox from worker opened as minimized");
           while (chats.selectedChat) {
             chats.selectedChat.close();
           }
           ok(!chats.selectedChat, "chats are all closed");
-          ensureSocialUrlNotRemembered(chatUrl);
+          gURLsNotRemembered.push(chatUrl);
           port.close();
           next();
           break;
       }
     }
     port.postMessage({topic: "test-worker-chat", data: chatUrl});
   },
   testCloseSelf: function(next) {
--- a/browser/base/content/test/social/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/social/browser_social_mozSocial_API.js
@@ -48,17 +48,17 @@ var tests = {
       switch (topic) {
         case "test-init-done":
           iconsReady = true;
           checkNext();
           break;
         case "got-panel-message":
           ok(true, "got panel message");
           // Check the panel isn't in our history.
-          ensureSocialUrlNotRemembered(e.data.location);
+          gURLsNotRemembered.push(e.data.location);
           break;
         case "got-social-panel-visibility":
           if (e.data.result == "shown") {
             ok(true, "panel shown");
             let panel = document.getElementById("social-notification-panel");
             panel.hidePopup();
           } else if (e.data.result == "hidden") {
             ok(true, "panel hidden");
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -1,12 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm");
+
 function waitForCondition(condition, nextTest, errorMsg) {
   var tries = 0;
   var interval = setInterval(function() {
     if (tries >= 30) {
       ok(false, errorMsg);
       moveOn();
     }
     if (condition()) {
@@ -14,46 +23,56 @@ function waitForCondition(condition, nex
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
 // Check that a specified (string) URL hasn't been "remembered" (ie, is not
 // in history, will not appear in about:newtab or auto-complete, etc.)
-function ensureSocialUrlNotRemembered(url) {
-  let gh = Cc["@mozilla.org/browser/global-history;2"]
-           .getService(Ci.nsIGlobalHistory2);
+function promiseSocialUrlNotRemembered(url) {
+  let deferred = Promise.defer();
   let uri = Services.io.newURI(url, null, null);
-  ok(!gh.isVisited(uri), "social URL " + url + " should not be in global history");
+  PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
+    ok(!aIsVisited, "social URL " + url + " should not be in global history");
+    deferred.resolve();
+  });
+  return deferred.promise;
 }
 
+let gURLsNotRemembered = [];
+
 function runSocialTestWithProvider(manifest, callback) {
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
   let manifests = Array.isArray(manifest) ? manifest : [manifest];
 
   // Check that none of the provider's content ends up in history.
-  registerCleanupFunction(function () {
-    manifests.forEach(function (m) {
+  function finishCleanUp() {
+    for (let i = 0; i < manifests.length; i++) {
+      let m = manifests[i];
       for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
         if (m[what]) {
-          ensureSocialUrlNotRemembered(m[what]);
+          yield promiseSocialUrlNotRemembered(m[what]);
         }
-      }
-    });
-  });
+      };
+    }
+    for (let i = 0; i < gURLsNotRemembered.length; i++) {
+      yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
+    }
+    gURLsNotRemembered = [];
+  }
 
   info("runSocialTestWithProvider: " + manifests.toSource());
 
   let finishCount = 0;
   function finishIfDone(callFinish) {
     finishCount++;
     if (finishCount == manifests.length)
-      finish();
+      Task.spawn(finishCleanUp).then(finish);
   }
   function removeAddedProviders(cleanup) {
     manifests.forEach(function (m) {
       // If we're "cleaning up", don't call finish when done.
       let callback = cleanup ? function () {} : finishIfDone;
       // Similarly, if we're cleaning up, catch exceptions from removeProvider
       let removeProvider = SocialService.removeProvider.bind(SocialService);
       if (cleanup) {
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1246,20 +1246,27 @@ let ContentArea = {
         typeof aView != "object" && typeof aView != "function")
       throw new Error("Invalid arguments");
 
     this._specialViews.set(aQueryString, { view: aView,
                                            options: aOptions || new Object() });
   },
 
   get currentView() PlacesUIUtils.getViewForNode(this._deck.selectedPanel),
-  set currentView(aView) {
-    if (this.currentView != aView)
-      this._deck.selectedPanel = aView.associatedElement;
-    return aView;
+  set currentView(aNewView) {
+    let oldView = this.currentView;
+    if (oldView != aNewView) {
+      this._deck.selectedPanel = aNewView.associatedElement;
+
+      // If the content area inactivated view was focused, move focus
+      // to the new view.
+      if (document.activeElement == oldView.associatedElement)
+        aNewView.associatedElement.focus();
+    }
+    return aNewView;
   },
 
   get currentPlace() this.currentView.place,
   set currentPlace(aQueryString) {
     let oldView = this.currentView;
     let newView = this.getContentViewForQueryString(aQueryString);
     newView.place = aQueryString;
     if (oldView != newView) {
--- a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
+++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
@@ -64,20 +64,17 @@ let tests = {
 
   makeHistVisit: function(aCallback) {
     // need to add a history object
     let testURI1 = NetUtil.newURI(MOZURISPEC);
     isnot(testURI1, null, "testURI is not null");
     addVisits(
       {uri: testURI1, transition: PlacesUtils.history.TRANSITION_TYPED},
       window,
-      function() {
-        ok(PlacesUtils.ghistory2.isVisited(testURI1), MOZURISPEC + " is a visited url.");
-        aCallback();
-      });
+      aCallback);
   },
 
   makeTag: function() {
     // create an initial tag to work with
     let bmId = add_bookmark(NetUtil.newURI(TEST_URL));
     ok(bmId > 0, "A bookmark was added");
     PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]);
     let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -17,19 +17,16 @@ var gLibrary;
 gTests.push({
   desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
   run: function() {
     var PO = gLibrary.PlacesOrganizer;
     let ContentTree = gLibrary.ContentTree;
     var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
 
     function addVisitsCallback() {
-      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added.");
-
       // open all bookmarks node
       PO.selectLeftPaneQuery("AllBookmarks");
       isnot(PO._places.selectedNode, null,
             "Correctly selected all bookmarks node.");
       checkInfoBoxSelected(PO);
       ok(infoBoxExpanderWrapper.hidden,
          "Expander button is hidden for all bookmarks node.");
       checkAddInfoFieldsCollapsed(PO);
--- a/browser/components/places/tests/browser/browser_library_left_pane_commands.js
+++ b/browser/components/places/tests/browser/browser_library_left_pane_commands.js
@@ -14,20 +14,16 @@ var gTests = [];
 var gLibrary;
 
 //------------------------------------------------------------------------------
 
 gTests.push({
   desc: "Bug 489351 - Date containers under History in Library cannot be deleted/cut",
   run: function() {
     function addVisitsCallback() {
-      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-      // Add a visit.
-      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added");
-
       // Select and open the left pane "History" query.
       var PO = gLibrary.PlacesOrganizer;
       PO.selectLeftPaneQuery('History');
       isnot(PO._places.selectedNode, null, "We correctly selected History");
 
       // Check that both delete and cut commands are disabled.
       ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
          "Cut command is disabled");
@@ -51,26 +47,30 @@ gTests.push({
       // Check that delete command is enabled but cut command is disabled.
       ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
          "Cut command is disabled");
       ok(PO._places.controller.isCommandEnabled("cmd_delete"),
          "Delete command is enabled");
 
       // Execute the delete command and check visit has been removed.
       PO._places.controller.doCommand("cmd_delete");
-      ok(!bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been removed");
 
       // Test live update of "History" query.
       is(historyNode.childCount, 0, "History node has no more children");
 
       historyNode.containerOpen = false;
-      nextTest();
+
+      let testURI = NetUtil.newURI(TEST_URI);
+      PlacesUtils.asyncHistory.isURIVisited(testURI, function(aURI, aIsVisited) {
+        ok(!aIsVisited, "Visit has been removed");
+        nextTest();
+      });
     }
     addVisits(
-      {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+      {uri: NetUtil.newURI(TEST_URI), visitDate: Date.now() * 1000,
         transition: PlacesUtils.history.TRANSITION_TYPED},
       window,
       addVisitsCallback);
   }
 });
 
 //------------------------------------------------------------------------------
 
@@ -92,17 +92,17 @@ gTests.push({
        "Delete command is disabled");
 
     var toolbarNode = PO._places.selectedNode
                         .QueryInterface(Ci.nsINavHistoryContainerResultNode);
     toolbarNode.containerOpen = true;
 
     // Add an History query to the toolbar.
     PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
-                                         PlacesUtils._uri("place:sort=4"),
+                                         NetUtil.newURI("place:sort=4"),
                                          0, // Insert at start.
                                          "special_query");
     // Get first child and check it is the "Most Visited" smart bookmark.
     ok(toolbarNode.childCount > 0, "Toolbar node has children");
     var queryNode = toolbarNode.getChild(0);
     is(queryNode.title, "special_query", "Query node is correctly selected");
 
     // Select query node.
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -3608,20 +3608,22 @@ let SessionStoreInternal = {
    *        Bool update all windows
    */
   saveState: function ssi_saveState(aUpdateAll) {
     // If crash recovery is disabled, we only want to resume with pinned tabs
     // if we crash.
     let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
 
     var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
     if (!oState) {
       TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+      TelemetryStopwatch.cancel("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
       return;
     }
 
     // Forget about private windows.
     for (let i = oState.windows.length - 1; i >= 0; i--) {
       if (oState.windows[i].isPrivate) {
         oState.windows.splice(i, 1);
         if (oState.selectedWindow >= i) {
@@ -3665,27 +3667,30 @@ let SessionStoreInternal = {
       }
     }
 
     // Persist the last session if we deferred restoring it
     if (this._lastSessionState)
       oState.lastSessionState = this._lastSessionState;
 
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_MS");
+    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_DATA_LONGEST_OP_MS");
 
     this._saveStateObject(oState);
   },
 
   /**
    * write a state object to disk
    */
   _saveStateObject: function ssi_saveStateObject(aStateObj) {
     TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
+    TelemetryStopwatch.start("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
     let data = this._toJSONString(aStateObj);
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_MS");
+    TelemetryStopwatch.finish("FX_SESSION_RESTORE_SERIALIZE_DATA_LONGEST_OP_MS");
 
     let stateString = this._createSupportsString(data);
     Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
     data = stateString.data;
 
     // Don't touch the file if an observer has deleted all state data.
     if (!data) {
       return;
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -43,18 +43,16 @@ public:
 
   virtual already_AddRefed<nsIDOMBlob>
   CreateSlice(uint64_t aStart, uint64_t aLength,
               const nsAString& aContentType) = 0;
 
   virtual const nsTArray<nsCOMPtr<nsIDOMBlob> >*
   GetSubBlobs() const { return nullptr; }
 
-  virtual bool IsMemoryBacked() const { return false; }
-
   NS_DECL_NSIDOMBLOB
   NS_DECL_NSIDOMFILE
   NS_DECL_NSIXHRSENDABLE
   NS_DECL_NSIMUTABLE
 
   void
   SetLazyData(const nsAString& aName, const nsAString& aContentType,
               uint64_t aLength, uint64_t aLastModifiedDate)
@@ -347,19 +345,18 @@ protected:
 
 class nsDOMMemoryFile : public nsDOMFile
 {
 public:
   // Create as file
   nsDOMMemoryFile(void *aMemoryBuffer,
                   uint64_t aLength,
                   const nsAString& aName,
-                  const nsAString& aContentType,
-                  uint64_t aModDate = UINT64_MAX)
-    : nsDOMFile(aName, aContentType, aLength, aModDate),
+                  const nsAString& aContentType)
+    : nsDOMFile(aName, aContentType, aLength, UINT64_MAX),
       mDataOwner(new DataOwner(aMemoryBuffer, aLength))
   {
     NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
   }
 
   // Create as blob
   nsDOMMemoryFile(void *aMemoryBuffer,
                   uint64_t aLength,
@@ -367,20 +364,16 @@ public:
     : nsDOMFile(aContentType, aLength),
       mDataOwner(new DataOwner(aMemoryBuffer, aLength))
   {
     NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
   }
 
   NS_IMETHOD GetInternalStream(nsIInputStream**);
 
-  virtual bool IsMemoryBacked() const { return true; }
-  void* GetData() const { return mDataOwner->mData; }
-  uint64_t GetLength() const { return mDataOwner->mLength; }
-
 protected:
   // Create slice
   nsDOMMemoryFile(const nsDOMMemoryFile* aOther, uint64_t aStart,
                   uint64_t aLength, const nsAString& aContentType)
     : nsDOMFile(aContentType, aOther->mStart + aStart, aLength),
       mDataOwner(aOther->mDataOwner)
   {
     NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data");
--- a/content/base/src/nsDOMBlobBuilder.cpp
+++ b/content/base/src/nsDOMBlobBuilder.cpp
@@ -141,25 +141,25 @@ nsDOMMultipartFile::CreateSlice(uint64_t
   nsCOMPtr<nsIDOMBlob> blob = new nsDOMMultipartFile(blobs, aContentType);
   return blob.forget();
 }
 
 /* static */ nsresult
 nsDOMMultipartFile::NewFile(const nsAString& aName, nsISupports* *aNewObject)
 {
   nsCOMPtr<nsISupports> file =
-    do_QueryObject(new nsDOMMultipartFile(aName, EmptyString()));
+    do_QueryObject(new nsDOMMultipartFile(aName));
   file.forget(aNewObject);
   return NS_OK;
 }
 
 /* static */ nsresult
 nsDOMMultipartFile::NewBlob(nsISupports* *aNewObject)
 {
-  nsCOMPtr<nsISupports> file = do_QueryObject(new nsDOMMultipartFile(EmptyString()));
+  nsCOMPtr<nsISupports> file = do_QueryObject(new nsDOMMultipartFile());
   file.forget(aNewObject);
   return NS_OK;
 }
 
 static nsIDOMBlob*
 GetXPConnectNative(JSContext* aCx, JSObject* aObj) {
   nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(
     nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, aObj));
--- a/content/base/src/nsDOMBlobBuilder.h
+++ b/content/base/src/nsDOMBlobBuilder.h
@@ -12,42 +12,41 @@
 #include "mozilla/Attributes.h"
 #include <algorithm>
 
 class nsDOMMultipartFile : public nsDOMFile,
                            public nsIJSNativeInitializer
 {
 public:
   // Create as a file
-  nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> >& aBlobs,
+  nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> > aBlobs,
                      const nsAString& aName,
                      const nsAString& aContentType)
     : nsDOMFile(aName, aContentType, UINT64_MAX),
       mBlobs(aBlobs)
   {
   }
 
   // Create as a blob
   nsDOMMultipartFile(nsTArray<nsCOMPtr<nsIDOMBlob> >& aBlobs,
                      const nsAString& aContentType)
     : nsDOMFile(aContentType, UINT64_MAX),
       mBlobs(aBlobs)
   {
   }
 
   // Create as a file to be later initialized
-  nsDOMMultipartFile(const nsAString& aName,
-                     const nsAString& aContentType)
-    : nsDOMFile(aName, aContentType, UINT64_MAX)
+  nsDOMMultipartFile(const nsAString& aName)
+    : nsDOMFile(aName, EmptyString(), UINT64_MAX)
   {
   }
 
   // Create as a blob to be later initialized
-  nsDOMMultipartFile(const nsAString& aContentType)
-    : nsDOMFile(aContentType, UINT64_MAX)
+  nsDOMMultipartFile()
+    : nsDOMFile(EmptyString(), UINT64_MAX)
   {
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIJSNativeInitializer
   NS_IMETHOD Initialize(nsISupports* aOwner,
                         JSContext* aCx,
@@ -85,22 +84,16 @@ public:
     // Initialization will set the filename, so we can pass in an empty string
     // for now.
     return NewFile(EmptyString(), aNewObject);
   }
 
   virtual const nsTArray<nsCOMPtr<nsIDOMBlob> >*
   GetSubBlobs() const { return &mBlobs; }
 
-  void
-  AddBlob(nsIDOMBlob* aBlob)
-  {
-    mBlobs.AppendElement(aBlob);
-  }
-
 protected:
   nsTArray<nsCOMPtr<nsIDOMBlob> > mBlobs;
 };
 
 class BlobSet {
 public:
   BlobSet()
     : mData(nullptr), mDataLen(0), mDataBufferLen(0)
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -2445,49 +2445,49 @@ public:
   virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked)
   {
     nsCOMPtr<nsIRunnable> event;
     if (aBlocked == BLOCKED) {
       event = NS_NewRunnableMethod(this, &StreamListener::DoNotifyBlocked);
     } else {
       event = NS_NewRunnableMethod(this, &StreamListener::DoNotifyUnblocked);
     }
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   virtual void NotifyFinished(MediaStreamGraph* aGraph)
   {
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &StreamListener::DoNotifyFinished);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
   virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph,
                                     bool aHasCurrentData)
   {
     MutexAutoLock lock(mMutex);
     if (mDidHaveCurrentData == aHasCurrentData)
       return;
     mDidHaveCurrentData = aHasCurrentData;
     // Ignore the case where aHasCurrentData is false. If aHasCurrentData
     // changes from true to false, we don't worry about it. Video elements
     // preserve the last played frame anyway.
     if (aHasCurrentData) {
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(this, &StreamListener::DoNotifyHaveCurrentData);
-      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
+      aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
     }
   }
   virtual void NotifyOutput(MediaStreamGraph* aGraph)
   {
     MutexAutoLock lock(mMutex);
     if (mPendingNotifyOutput)
       return;
     mPendingNotifyOutput = true;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(this, &StreamListener::DoNotifyOutput);
-    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event);
+    aGraph->DispatchToMainThreadAfterStreamStateUpdate(event.forget());
   }
 
 private:
   // These fields may only be accessed on the main thread
   nsHTMLMediaElement* mElement;
   bool mHaveCurrentData;
   bool mBlocked;
 
rename from content/media/webaudio/AudioEventTimeline.h
rename to content/media/AudioEventTimeline.h
new file mode 100644
--- /dev/null
+++ b/content/media/AudioNodeEngine.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeEngine.h"
+
+namespace mozilla {
+
+void
+AllocateAudioBlock(uint32_t aChannelCount, AudioChunk* aChunk)
+{
+  // XXX for SIMD purposes we should do something here to make sure the
+  // channel buffers are 16-byte aligned.
+  nsRefPtr<SharedBuffer> buffer =
+    SharedBuffer::Create(WEBAUDIO_BLOCK_SIZE*aChannelCount*sizeof(float));
+  aChunk->mDuration = WEBAUDIO_BLOCK_SIZE;
+  aChunk->mChannelData.SetLength(aChannelCount);
+  float* data = static_cast<float*>(buffer->Data());
+  for (uint32_t i = 0; i < aChannelCount; ++i) {
+    aChunk->mChannelData[i] = data + i*WEBAUDIO_BLOCK_SIZE;
+  }
+  aChunk->mBuffer = buffer.forget();
+  aChunk->mVolume = 1.0f;
+  aChunk->mBufferFormat = AUDIO_FORMAT_FLOAT32;
+}
+
+void
+WriteZeroesToAudioBlock(AudioChunk* aChunk, uint32_t aStart, uint32_t aLength)
+{
+  MOZ_ASSERT(aStart + aLength <= WEBAUDIO_BLOCK_SIZE);
+  if (aLength == 0)
+    return;
+  for (uint32_t i = 0; i < aChunk->mChannelData.Length(); ++i) {
+    memset(static_cast<float*>(const_cast<void*>(aChunk->mChannelData[i])) + aStart,
+           0, aLength*sizeof(float));
+  }
+}
+
+void
+AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+                              float aScale,
+                              float aOutput[WEBAUDIO_BLOCK_SIZE])
+{
+  if (aScale == 1.0f) {
+    for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+      aOutput[i] += aInput[i];
+    }
+  } else {
+    for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+      aOutput[i] += aInput[i]*aScale;
+    }
+  }
+}
+
+void
+AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+                               float aScale,
+                               float aOutput[WEBAUDIO_BLOCK_SIZE])
+{
+  if (aScale == 1.0f) {
+    memcpy(aOutput, aInput, WEBAUDIO_BLOCK_SIZE*sizeof(float));
+  } else {
+    for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+      aOutput[i] = aInput[i]*aScale;
+    }
+  }
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/AudioNodeEngine.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef MOZILLA_AUDIONODEENGINE_H_
+#define MOZILLA_AUDIONODEENGINE_H_
+
+#include "AudioSegment.h"
+#include "mozilla/dom/AudioParam.h"
+
+namespace mozilla {
+
+class AudioNodeStream;
+
+// We ensure that the graph advances in steps that are multiples of the Web
+// Audio block size
+const uint32_t WEBAUDIO_BLOCK_SIZE_BITS = 7;
+const uint32_t WEBAUDIO_BLOCK_SIZE = 1 << WEBAUDIO_BLOCK_SIZE_BITS;
+
+/**
+ * This class holds onto a set of immutable channel buffers. The storage
+ * for the buffers must be malloced, but the buffer pointers and the malloc
+ * pointers can be different (e.g. if the buffers are contained inside
+ * some malloced object).
+ */
+class ThreadSharedFloatArrayBufferList : public ThreadSharedObject {
+public:
+  /**
+   * Construct with null data.
+   */
+  ThreadSharedFloatArrayBufferList(uint32_t aCount)
+  {
+    mContents.SetLength(aCount);
+  }
+
+  struct Storage {
+    Storage()
+    {
+      mDataToFree = nullptr;
+      mSampleData = nullptr;
+    }
+    ~Storage() { free(mDataToFree); }
+    void* mDataToFree;
+    const float* mSampleData;
+  };
+
+  /**
+   * This can be called on any thread.
+   */
+  uint32_t GetChannels() const { return mContents.Length(); }
+  /**
+   * This can be called on any thread.
+   */
+  const float* GetData(uint32_t aIndex) const { return mContents[aIndex].mSampleData; }
+
+  /**
+   * Call this only during initialization, before the object is handed to
+   * any other thread.
+   */
+  void SetData(uint32_t aIndex, void* aDataToFree, const float* aData)
+  {
+    Storage* s = &mContents[aIndex];
+    free(s->mDataToFree);
+    s->mDataToFree = aDataToFree;
+    s->mSampleData = aData;
+  }
+
+  /**
+   * Put this object into an error state where there are no channels.
+   */
+  void Clear() { mContents.Clear(); }
+
+private:
+  AutoFallibleTArray<Storage,2> mContents;
+};
+
+/**
+ * Allocates an AudioChunk with fresh buffers of WEBAUDIO_BLOCK_SIZE float samples.
+ * AudioChunk::mChannelData's entries can be cast to float* for writing.
+ */
+void AllocateAudioBlock(uint32_t aChannelCount, AudioChunk* aChunk);
+
+/**
+ * aChunk must have been allocated by AllocateAudioBlock.
+ */
+void WriteZeroesToAudioBlock(AudioChunk* aChunk, uint32_t aStart, uint32_t aLength);
+
+/**
+ * Pointwise multiply-add operation. aScale == 1.0f should be optimized.
+ */
+void AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+                                   float aScale,
+                                   float aOutput[WEBAUDIO_BLOCK_SIZE]);
+
+/**
+ * Pointwise copy-scaled operation. aScale == 1.0f should be optimized.
+ */
+void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+                                    float aScale,
+                                    float aOutput[WEBAUDIO_BLOCK_SIZE]);
+
+/**
+ * All methods of this class and its subclasses are called on the
+ * MediaStreamGraph thread.
+ */
+class AudioNodeEngine {
+public:
+  AudioNodeEngine() {}
+  virtual ~AudioNodeEngine() {}
+
+  virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
+  {
+    NS_ERROR("Invalid SetStreamTimeParameter index");
+  }
+  virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
+  {
+    NS_ERROR("Invalid SetDoubleParameter index");
+  }
+  virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
+  {
+    NS_ERROR("Invalid SetInt32Parameter index");
+  }
+  virtual void SetTimelineParameter(uint32_t aIndex,
+                                    const dom::AudioParamTimeline& aValue)
+  {
+    NS_ERROR("Invalid SetTimelineParameter index");
+  }
+  virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
+  {
+    NS_ERROR("SetBuffer called on engine that doesn't support it");
+  }
+
+  /**
+   * Produce the next block of audio samples, given input samples aInput
+   * (the mixed data for input 0).
+   * By default, simply returns the mixed input.
+   * aInput is guaranteed to have float sample format (if it has samples at all)
+   * and to have been resampled to IdealAudioRate(), and to have exactly
+   * WEBAUDIO_BLOCK_SIZE samples.
+   * *aFinished is set to false by the caller. If the callee sets it to true,
+   * we'll finish the stream and not call this again.
+   */
+  virtual void ProduceAudioBlock(AudioNodeStream* aStream,
+                                 const AudioChunk& aInput,
+                                 AudioChunk* aOutput,
+                                 bool* aFinished)
+  {
+    *aOutput = aInput;
+  }
+};
+
+}
+
+#endif /* MOZILLA_AUDIONODEENGINE_H_ */
new file mode 100644
--- /dev/null
+++ b/content/media/AudioNodeStream.cpp
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeStream.h"
+
+#include "MediaStreamGraphImpl.h"
+#include "AudioNodeEngine.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+/**
+ * An AudioNodeStream produces a single audio track with ID
+ * AUDIO_NODE_STREAM_TRACK_ID. This track has rate IdealAudioRate().
+ * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
+ */
+static const int AUDIO_NODE_STREAM_TRACK_ID = 1;
+
+AudioNodeStream::~AudioNodeStream()
+{
+}
+
+void
+AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, MediaStream* aRelativeToStream,
+                                        double aStreamTime)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream,
+            double aStreamTime)
+      : ControlMessage(aStream), mStreamTime(aStreamTime),
+        mRelativeToStream(aRelativeToStream), mIndex(aIndex) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->
+          SetStreamTimeParameterImpl(mIndex, mRelativeToStream, mStreamTime);
+    }
+    double mStreamTime;
+    MediaStream* mRelativeToStream;
+    uint32_t mIndex;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aIndex, aRelativeToStream, aStreamTime));
+}
+
+void
+AudioNodeStream::SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
+                                            double aStreamTime)
+{
+  StreamTime streamTime = std::max<MediaTime>(0, SecondsToMediaTime(aStreamTime));
+  GraphTime graphTime = aRelativeToStream->StreamTimeToGraphTime(streamTime);
+  StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime);
+  TrackTicks ticks = TimeToTicksRoundDown(IdealAudioRate(), thisStreamTime);
+  mEngine->SetStreamTimeParameter(aIndex, ticks);
+}
+
+void
+AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream, uint32_t aIndex, double aValue)
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->Engine()->
+          SetDoubleParameter(mIndex, mValue);
+    }
+    double mValue;
+    uint32_t mIndex;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
+}
+
+void
+AudioNodeStream::SetInt32Parameter(uint32_t aIndex, int32_t aValue)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream, uint32_t aIndex, int32_t aValue)
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->Engine()->
+          SetInt32Parameter(mIndex, mValue);
+    }
+    int32_t mValue;
+    uint32_t mIndex;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
+}
+
+void
+AudioNodeStream::SetTimelineParameter(uint32_t aIndex,
+                                      const AudioEventTimeline<ErrorResult>& aValue)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream, uint32_t aIndex,
+            const AudioEventTimeline<ErrorResult>& aValue)
+      : ControlMessage(aStream), mValue(aValue), mIndex(aIndex) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->Engine()->
+          SetTimelineParameter(mIndex, mValue);
+    }
+    AudioEventTimeline<ErrorResult> mValue;
+    uint32_t mIndex;
+  };
+  GraphImpl()->AppendMessage(new Message(this, aIndex, aValue));
+}
+
+void
+AudioNodeStream::SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream,
+            already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
+      : ControlMessage(aStream), mBuffer(aBuffer) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->Engine()->
+          SetBuffer(mBuffer.forget());
+    }
+    nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aBuffer));
+}
+
+StreamBuffer::Track*
+AudioNodeStream::EnsureTrack()
+{
+  StreamBuffer::Track* track = mBuffer.FindTrack(AUDIO_NODE_STREAM_TRACK_ID);
+  if (!track) {
+    nsAutoPtr<MediaSegment> segment(new AudioSegment());
+    for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+      MediaStreamListener* l = mListeners[j];
+      l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), 0,
+                                  MediaStreamListener::TRACK_EVENT_CREATED,
+                                  *segment);
+    }
+    track = &mBuffer.AddTrack(AUDIO_NODE_STREAM_TRACK_ID, IdealAudioRate(), 0, segment.forget());
+  }
+  return track;
+}
+
+AudioChunk*
+AudioNodeStream::ObtainInputBlock(AudioChunk* aTmpChunk)
+{
+  uint32_t inputCount = mInputs.Length();
+  uint32_t outputChannelCount = 0;
+  nsAutoTArray<AudioChunk*,250> inputChunks;
+  for (uint32_t i = 0; i < inputCount; ++i) {
+    MediaStream* s = mInputs[i]->GetSource();
+    AudioNodeStream* a = s->AsAudioNodeStream();
+    MOZ_ASSERT(a);
+    if (a->IsFinishedOnGraphThread()) {
+      continue;
+    }
+    AudioChunk* chunk = a->mLastChunk;
+    // XXX when we implement DelayNode, this will no longer be true and we'll
+    // need to treat a null chunk (when the DelayNode hasn't had a chance
+    // to produce data yet) as silence here.
+    MOZ_ASSERT(chunk);
+    if (chunk->IsNull()) {
+      continue;
+    }
+
+    inputChunks.AppendElement(chunk);
+    outputChannelCount =
+      GetAudioChannelsSuperset(outputChannelCount, chunk->mChannelData.Length());
+  }
+
+  uint32_t inputChunkCount = inputChunks.Length();
+  if (inputChunkCount == 0) {
+    aTmpChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
+    return aTmpChunk;
+  }
+
+  if (inputChunkCount == 1) {
+    return inputChunks[0];
+  }
+
+  AllocateAudioBlock(outputChannelCount, aTmpChunk);
+
+  for (uint32_t i = 0; i < inputChunkCount; ++i) {
+    AudioChunk* chunk = inputChunks[i];
+    nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channels;
+    channels.AppendElements(chunk->mChannelData);
+    if (channels.Length() < outputChannelCount) {
+      AudioChannelsUpMix(&channels, outputChannelCount, nullptr);
+      NS_ASSERTION(outputChannelCount == channels.Length(),
+                   "We called GetAudioChannelsSuperset to avoid this");
+    }
+
+    for (uint32_t c = 0; c < channels.Length(); ++c) {
+      const float* inputData = static_cast<const float*>(channels[c]);
+      float* outputData = static_cast<float*>(const_cast<void*>(aTmpChunk->mChannelData[c]));
+      if (inputData) {
+        if (i == 0) {
+          AudioBlockCopyChannelWithScale(inputData, chunk->mVolume, outputData);
+        } else {
+          AudioBlockAddChannelWithScale(inputData, chunk->mVolume, outputData);
+        }
+      } else {
+        if (i == 0) {
+          memset(outputData, 0, WEBAUDIO_BLOCK_SIZE*sizeof(float));
+        }
+      }
+    }
+  }
+
+  return aTmpChunk;
+}
+
+// The MediaStreamGraph guarantees that this is actually one block, for
+// AudioNodeStreams.
+void
+AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
+{
+  StreamBuffer::Track* track = EnsureTrack();
+
+  AudioChunk outputChunk;
+  AudioSegment* segment = track->Get<AudioSegment>();
+
+  if (mInCycle) {
+    // XXX DelayNode not supported yet so just produce silence
+    outputChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
+  } else {
+    AudioChunk tmpChunk;
+    AudioChunk* inputChunk = ObtainInputBlock(&tmpChunk);
+    bool finished = false;
+    mEngine->ProduceAudioBlock(this, *inputChunk, &outputChunk, &finished);
+    if (finished) {
+      FinishOutput();
+    }
+  }
+
+  mLastChunk = segment->AppendAndConsumeChunk(&outputChunk);
+
+  for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+    MediaStreamListener* l = mListeners[j];
+    AudioChunk copyChunk = *mLastChunk;
+    AudioSegment tmpSegment;
+    tmpSegment.AppendAndConsumeChunk(&copyChunk);
+    l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID,
+                                IdealAudioRate(), segment->GetDuration(), 0,
+                                tmpSegment);
+  }
+}
+
+TrackTicks
+AudioNodeStream::GetCurrentPosition()
+{
+  return EnsureTrack()->Get<AudioSegment>()->GetDuration();
+}
+
+void
+AudioNodeStream::FinishOutput()
+{
+  if (IsFinishedOnGraphThread()) {
+    return;
+  }
+
+  StreamBuffer::Track* track = EnsureTrack();
+  track->SetEnded();
+  FinishOnGraphThread();
+
+  for (uint32_t j = 0; j < mListeners.Length(); ++j) {
+    MediaStreamListener* l = mListeners[j];
+    AudioSegment emptySegment;
+    l->NotifyQueuedTrackChanges(Graph(), AUDIO_NODE_STREAM_TRACK_ID,
+                                IdealAudioRate(),
+                                track->GetSegment()->GetDuration(),
+                                MediaStreamListener::TRACK_EVENT_ENDED, emptySegment);
+  }
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/AudioNodeStream.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIONODESTREAM_H_
+#define MOZILLA_AUDIONODESTREAM_H_
+
+#include "MediaStreamGraph.h"
+#include "AudioChannelFormat.h"
+#include "AudioNodeEngine.h"
+#include "mozilla/dom/AudioParam.h"
+
+#ifdef PR_LOGGING
+#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
+#else
+#define LOG(type, msg)
+#endif
+
+namespace mozilla {
+
+class ThreadSharedFloatArrayBufferList;
+
+/**
+ * An AudioNodeStream produces one audio track with ID AUDIO_TRACK.
+ * The start time of the AudioTrack is aligned to the start time of the
+ * AudioContext's destination node stream, plus some multiple of BLOCK_SIZE
+ * samples.
+ *
+ * An AudioNodeStream has an AudioNodeEngine plugged into it that does the
+ * actual audio processing. AudioNodeStream contains the glue code that
+ * integrates audio processing with the MediaStreamGraph.
+ */
+class AudioNodeStream : public ProcessedMediaStream {
+public:
+  enum { AUDIO_TRACK = 1 };
+
+  /**
+   * Transfers ownership of aEngine to the new AudioNodeStream.
+   */
+  explicit AudioNodeStream(AudioNodeEngine* aEngine)
+    : ProcessedMediaStream(nullptr), mEngine(aEngine), mLastChunk(nullptr)
+  {
+  }
+  ~AudioNodeStream();
+
+  // Control API
+  /**
+   * Sets a parameter that's a time relative to some stream's played time.
+   * This time is converted to a time relative to this stream when it's set.
+   */
+  void SetStreamTimeParameter(uint32_t aIndex, MediaStream* aRelativeToStream,
+                              double aStreamTime);
+  void SetDoubleParameter(uint32_t aIndex, double aValue);
+  void SetInt32Parameter(uint32_t aIndex, int32_t aValue);
+  void SetTimelineParameter(uint32_t aIndex, const dom::AudioParamTimeline& aValue);
+  void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
+
+  virtual AudioNodeStream* AsAudioNodeStream() { return this; }
+
+  // Graph thread only
+  void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
+                                  double aStreamTime);
+  virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
+  TrackTicks GetCurrentPosition();
+
+  // Any thread
+  AudioNodeEngine* Engine() { return mEngine; }
+
+protected:
+  void FinishOutput();
+
+  StreamBuffer::Track* EnsureTrack();
+  AudioChunk* ObtainInputBlock(AudioChunk* aTmpChunk);
+
+  // The engine that will generate output for this node.
+  nsAutoPtr<AudioNodeEngine> mEngine;
+  // The last block produced by this node.
+  AudioChunk* mLastChunk;
+};
+
+}
+
+#endif /* MOZILLA_AUDIONODESTREAM_H_ */
--- a/content/media/AudioSegment.cpp
+++ b/content/media/AudioSegment.cpp
@@ -84,17 +84,16 @@ void
 AudioSegment::ApplyVolume(float aVolume)
 {
   for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
     ci->mVolume *= aVolume;
   }
 }
 
 static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
-static const int GUESS_AUDIO_CHANNELS = 2;
 static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
 
 void
 AudioSegment::WriteTo(AudioStream* aOutput)
 {
   uint32_t outputChannels = aOutput->GetChannels();
   nsAutoTArray<AudioDataValue,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf;
   nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
--- a/content/media/AudioSegment.h
+++ b/content/media/AudioSegment.h
@@ -11,16 +11,21 @@
 #include "AudioSampleFormat.h"
 #include "SharedBuffer.h"
 
 namespace mozilla {
 
 class AudioStream;
 
 /**
+ * For auto-arrays etc, guess this as the common number of channels.
+ */
+const int GUESS_AUDIO_CHANNELS = 2;
+
+/**
  * An AudioChunk represents a multi-channel buffer of audio samples.
  * It references an underlying ThreadSharedObject which manages the lifetime
  * of the buffer. An AudioChunk maintains its own duration and channel data
  * pointers so it can represent a subinterval of a buffer without copying.
  * An AudioChunk can store its individual channels anywhere; it maintains
  * separate pointers to each channel's buffer.
  */
 struct AudioChunk {
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -14,16 +14,19 @@ LIBRARY_NAME = gkconmedia_s
 LIBXUL_LIBRARY = 1
 ifndef _MSC_VER
 FAIL_ON_WARNINGS := 1
 endif # !_MSC_VER
 
 EXPORTS = \
   AbstractMediaDecoder.h \
   AudioChannelFormat.h \
+  AudioEventTimeline.h \
+  AudioNodeEngine.h \
+  AudioNodeStream.h \
   AudioSampleFormat.h \
   AudioSegment.h \
   BufferMediaResource.h \
   DecoderTraits.h \
   FileBlockCache.h \
   MediaDecoderOwner.h \
   MediaResource.h \
   MediaSegment.h \
@@ -41,16 +44,18 @@ EXPORTS = \
   VideoUtils.h \
   VideoSegment.h \
   VorbisUtils.h \
   MediaMetadataManager.h \
   $(NULL)
 
 CPPSRCS = \
   AudioChannelFormat.cpp \
+  AudioNodeEngine.cpp \
+  AudioNodeStream.cpp \
   AudioSegment.cpp \
   DecoderTraits.cpp \
   FileBlockCache.cpp \
   MediaResource.cpp \
   MediaStreamGraph.cpp \
   AudioAvailableEventManager.cpp \
   MediaDecoder.cpp \
   MediaDecoderStateMachine.cpp \
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -152,40 +152,46 @@ void MediaDecoder::ConnectDecodedStreamT
   aStream->mPort = aStream->mStream->AllocateInputPort(mDecodedStream->mStream,
       MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT);
   // Unblock the output stream now. While it's connected to mDecodedStream,
   // mDecodedStream is responsible for controlling blocking.
   aStream->mStream->ChangeExplicitBlockerCount(-1);
 }
 
 MediaDecoder::DecodedStreamData::DecodedStreamData(MediaDecoder* aDecoder,
-                                                       int64_t aInitialTime,
-                                                       SourceMediaStream* aStream)
+                                                   int64_t aInitialTime,
+                                                   SourceMediaStream* aStream)
   : mLastAudioPacketTime(-1),
     mLastAudioPacketEndTime(-1),
     mAudioFramesWritten(0),
     mInitialTime(aInitialTime),
     mNextVideoTime(aInitialTime),
+    mDecoder(aDecoder),
     mStreamInitialized(false),
     mHaveSentFinish(false),
     mHaveSentFinishAudio(false),
     mHaveSentFinishVideo(false),
     mStream(aStream),
-    mMainThreadListener(new DecodedStreamMainThreadListener(aDecoder)),
     mHaveBlockedForPlayState(false)
 {
-  mStream->AddMainThreadListener(mMainThreadListener);
+  mStream->AddMainThreadListener(this);
 }
 
 MediaDecoder::DecodedStreamData::~DecodedStreamData()
 {
-  mStream->RemoveMainThreadListener(mMainThreadListener);
+  mStream->RemoveMainThreadListener(this);
   mStream->Destroy();
 }
 
+void
+MediaDecoder::DecodedStreamData::NotifyMainThreadStateChanged()
+{
+  mDecoder->NotifyDecodedStreamMainThreadStateChanged();
+}
+
 void MediaDecoder::DestroyDecodedStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
 
   // All streams are having their SourceMediaStream disconnected, so they
   // need to be explicitly blocked again.
   for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -230,17 +230,16 @@ static const uint32_t FRAMEBUFFER_LENGTH
 #undef GetCurrentTime
 #endif
 
 class MediaDecoder : public nsIObserver,
                      public AbstractMediaDecoder
 {
 public:
   typedef mozilla::layers::Image Image;
-  class DecodedStreamMainThreadListener;
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   // Enumeration for the valid play states (see mPlayState)
   enum PlayState {
     PLAY_STATE_START,
     PLAY_STATE_LOADING,
@@ -335,17 +334,17 @@ public:
 
   // All MediaStream-related data is protected by mReentrantMonitor.
   // We have at most one DecodedStreamData per MediaDecoder. Its stream
   // is used as the input for each ProcessedMediaStream created by calls to
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
   // not connected to streams created by captureStreamUntilEnded.
 
-  struct DecodedStreamData {
+  struct DecodedStreamData MOZ_FINAL : public MainThreadMediaStreamListener {
     DecodedStreamData(MediaDecoder* aDecoder,
                       int64_t aInitialTime, SourceMediaStream* aStream);
     ~DecodedStreamData();
 
     // The following group of fields are protected by the decoder's monitor
     // and can be read or written on any thread.
     int64_t mLastAudioPacketTime; // microseconds
     int64_t mLastAudioPacketEndTime; // microseconds
@@ -353,36 +352,36 @@ public:
     int64_t mAudioFramesWritten;
     // Saved value of aInitialTime. Timestamp of the first audio and/or
     // video packet written.
     int64_t mInitialTime; // microseconds
     // mNextVideoTime is the end timestamp for the last packet sent to the stream.
     // Therefore video packets starting at or after this time need to be copied
     // to the output stream.
     int64_t mNextVideoTime; // microseconds
+    MediaDecoder* mDecoder;
     // The last video image sent to the stream. Useful if we need to replicate
     // the image.
     nsRefPtr<Image> mLastVideoImage;
     gfxIntSize mLastVideoImageDisplaySize;
     // This is set to true when the stream is initialized (audio and
     // video tracks added).
     bool mStreamInitialized;
     bool mHaveSentFinish;
     bool mHaveSentFinishAudio;
     bool mHaveSentFinishVideo;
 
     // The decoder is responsible for calling Destroy() on this stream.
     // Can be read from any thread.
     const nsRefPtr<SourceMediaStream> mStream;
-    // A listener object that receives notifications when mStream's
-    // main-thread-visible state changes. Used on the main thread only.
-    const nsRefPtr<DecodedStreamMainThreadListener> mMainThreadListener;
     // True when we've explicitly blocked this stream because we're
     // not in PLAY_STATE_PLAYING. Used on the main thread only.
     bool mHaveBlockedForPlayState;
+
+    virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
   };
   struct OutputStreamData {
     void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
     {
       mStream = aStream;
       mFinishWhenEnded = aFinishWhenEnded;
     }
     nsRefPtr<ProcessedMediaStream> mStream;
@@ -416,26 +415,16 @@ public:
     GetReentrantMonitor().AssertCurrentThreadIn();
     return mOutputStreams;
   }
   DecodedStreamData* GetDecodedStream()
   {
     GetReentrantMonitor().AssertCurrentThreadIn();
     return mDecodedStream;
   }
-  class DecodedStreamMainThreadListener : public MainThreadMediaStreamListener {
-  public:
-    DecodedStreamMainThreadListener(MediaDecoder* aDecoder)
-      : mDecoder(aDecoder) {}
-    virtual void NotifyMainThreadStateChanged()
-    {
-      mDecoder->NotifyDecodedStreamMainThreadStateChanged();
-    }
-    MediaDecoder* mDecoder;
-  };
 
   // Add an output stream. All decoder output will be sent to the stream.
   // The stream is initially blocked. The decoder is responsible for unblocking
   // it while it is playing back.
   virtual void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
 
   // Return the duration of the video in seconds.
   virtual double GetDuration();
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1,520 +1,42 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "MediaStreamGraph.h"
+#include "MediaStreamGraphImpl.h"
 
-#include "mozilla/Monitor.h"
-#include "mozilla/TimeStamp.h"
 #include "AudioSegment.h"
 #include "VideoSegment.h"
 #include "nsContentUtils.h"
 #include "nsIAppShell.h"
 #include "nsIObserver.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prlog.h"
 #include "VideoUtils.h"
 #include "mozilla/Attributes.h"
 #include "TrackUnionStream.h"
 #include "ImageContainer.h"
 #include "AudioChannelCommon.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
 #include <algorithm>
 
 using namespace mozilla::layers;
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gMediaStreamGraphLog;
-#define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
-#else
-#define LOG(type, msg)
 #endif
 
-namespace {
-
-/**
- * Assume we can run an iteration of the MediaStreamGraph loop in this much time
- * or less.
- * We try to run the control loop at this rate.
- */
-const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
-
-/**
- * Assume that we might miss our scheduled wakeup of the MediaStreamGraph by
- * this much.
- */
-const int SCHEDULE_SAFETY_MARGIN_MS = 10;
-
-/**
- * Try have this much audio buffered in streams and queued to the hardware.
- * The maximum delay to the end of the next control loop
- * is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
- * There is no point in buffering more audio than this in a stream at any
- * given time (until we add processing).
- * This is not optimal yet.
- */
-const int AUDIO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
-    SCHEDULE_SAFETY_MARGIN_MS;
-
-/**
- * Try have this much video buffered. Video frames are set
- * near the end of the iteration of the control loop. The maximum delay
- * to the setting of the next video frame is 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
- * SCHEDULE_SAFETY_MARGIN_MS. This is not optimal yet.
- */
-const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
-    SCHEDULE_SAFETY_MARGIN_MS;
-
-/**
- * A per-stream update message passed from the media graph thread to the
- * main thread.
- */
-struct StreamUpdate {
-  int64_t mGraphUpdateIndex;
-  nsRefPtr<MediaStream> mStream;
-  StreamTime mNextMainThreadCurrentTime;
-  bool mNextMainThreadFinished;
-};
-
-/**
- * This represents a message passed from the main thread to the graph thread.
- * A ControlMessage always references a particular affected stream.
- */
-class ControlMessage {
-public:
-  ControlMessage(MediaStream* aStream) : mStream(aStream)
-  {
-    MOZ_COUNT_CTOR(ControlMessage);
-  }
-  // All these run on the graph thread
-  virtual ~ControlMessage()
-  {
-    MOZ_COUNT_DTOR(ControlMessage);
-  }
-  // Do the action of this message on the MediaStreamGraph thread. Any actions
-  // affecting graph processing should take effect at mStateComputedTime.
-  // All stream data for times < mStateComputedTime has already been
-  // computed.
-  virtual void Run() = 0;
-  // When we're shutting down the application, most messages are ignored but
-  // some cleanup messages should still be processed (on the main thread).
-  virtual void RunDuringShutdown() {}
-  MediaStream* GetStream() { return mStream; }
-
-protected:
-  // We do not hold a reference to mStream. The graph will be holding
-  // a reference to the stream until the Destroy message is processed. The
-  // last message referencing a stream is the Destroy message for that stream.
-  MediaStream* mStream;
-};
-
-}
-
-/**
- * The implementation of a media stream graph. This class is private to this
- * file. It's not in the anonymous namespace because MediaStream needs to
- * be able to friend it.
- *
- * Currently we only have one per process.
- */
-class MediaStreamGraphImpl : public MediaStreamGraph {
-public:
-  MediaStreamGraphImpl();
-  ~MediaStreamGraphImpl()
-  {
-    NS_ASSERTION(IsEmpty(),
-                 "All streams should have been destroyed by messages from the main thread");
-    LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p destroyed", this));
-  }
-
-  // Main thread only.
-  /**
-   * This runs every time we need to sync state from the media graph thread
-   * to the main thread while the main thread is not in the middle
-   * of a script. It runs during a "stable state" (per HTML5) or during
-   * an event posted to the main thread.
-   */
-  void RunInStableState();
-  /**
-   * Ensure a runnable to run RunInStableState is posted to the appshell to
-   * run at the next stable state (per HTML5).
-   * See EnsureStableStateEventPosted.
-   */
-  void EnsureRunInStableState();
-  /**
-   * Called to apply a StreamUpdate to its stream.
-   */
-  void ApplyStreamUpdate(StreamUpdate* aUpdate);
-  /**
-   * Append a ControlMessage to the message queue. This queue is drained
-   * during RunInStableState; the messages will run on the graph thread.
-   */
-  void AppendMessage(ControlMessage* aMessage);
-  /**
-   * Make this MediaStreamGraph enter forced-shutdown state. This state
-   * will be noticed by the media graph thread, which will shut down all streams
-   * and other state controlled by the media graph thread.
-   * This is called during application shutdown.
-   */
-  void ForceShutDown();
-  /**
-   * Shutdown() this MediaStreamGraph's threads and return when they've shut down.
-   */
-  void ShutdownThreads();
-
-  // The following methods run on the graph thread (or possibly the main thread if
-  // mLifecycleState > LIFECYCLE_RUNNING)
-  /**
-   * Runs main control loop on the graph thread. Normally a single invocation
-   * of this runs for the entire lifetime of the graph thread.
-   */
-  void RunThread();
-  /**
-   * Call this to indicate that another iteration of the control loop is
-   * required on its regular schedule. The monitor must not be held.
-   */
-  void EnsureNextIteration();
-  /**
-   * As above, but with the monitor already held.
-   */
-  void EnsureNextIterationLocked(MonitorAutoLock& aLock);
-  /**
-   * Call this to indicate that another iteration of the control loop is
-   * required immediately. The monitor must already be held.
-   */
-  void EnsureImmediateWakeUpLocked(MonitorAutoLock& aLock);
-  /**
-   * Ensure there is an event posted to the main thread to run RunInStableState.
-   * mMonitor must be held.
-   * See EnsureRunInStableState
-   */
-  void EnsureStableStateEventPosted();
-  /**
-   * Generate messages to the main thread to update it for all state changes.
-   * mMonitor must be held.
-   */
-  void PrepareUpdatesToMainThreadState();
-  // The following methods are the various stages of RunThread processing.
-  /**
-   * Compute a new current time for the graph and advance all on-graph-thread
-   * state to the new current time.
-   */
-  void UpdateCurrentTime();
-  /**
-   * Update the consumption state of aStream to reflect whether its data
-   * is needed or not.
-   */
-  void UpdateConsumptionState(SourceMediaStream* aStream);
-  /**
-   * Extract any state updates pending in aStream, and apply them.
-   */
-  void ExtractPendingInput(SourceMediaStream* aStream,
-                           GraphTime aDesiredUpToTime,
-                           bool* aEnsureNextIteration);
-  /**
-   * Update "have enough data" flags in aStream.
-   */
-  void UpdateBufferSufficiencyState(SourceMediaStream* aStream);
-  /*
-   * If aStream hasn't already been ordered, push it onto aStack and order
-   * its children.
-   */
-  void UpdateStreamOrderForStream(nsTArray<MediaStream*>* aStack,
-                                  already_AddRefed<MediaStream> aStream);
-  /**
-   * Mark aStream and all its inputs (recursively) as consumed.
-   */
-  static void MarkConsumed(MediaStream* aStream);
-  /**
-   * Sort mStreams so that every stream not in a cycle is after any streams
-   * it depends on, and every stream in a cycle is marked as being in a cycle.
-   * Also sets mIsConsumed on every stream.
-   */
-  void UpdateStreamOrder();
-  /**
-   * Compute the blocking states of streams from mStateComputedTime
-   * until the desired future time aEndBlockingDecisions.
-   * Updates mStateComputedTime and sets MediaStream::mBlocked
-   * for all streams.
-   */
-  void RecomputeBlocking(GraphTime aEndBlockingDecisions);
-  // The following methods are used to help RecomputeBlocking.
-  /**
-   * If aStream isn't already in aStreams, add it and recursively call
-   * AddBlockingRelatedStreamsToSet on all the streams whose blocking
-   * status could depend on or affect the state of aStream.
-   */
-  void AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
-                                      MediaStream* aStream);
-  /**
-   * Mark a stream blocked at time aTime. If this results in decisions that need
-   * to be revisited at some point in the future, *aEnd will be reduced to the
-   * first time in the future to recompute those decisions.
-   */
-  void MarkStreamBlocking(MediaStream* aStream);
-  /**
-   * Recompute blocking for the streams in aStreams for the interval starting at aTime.
-   * If this results in decisions that need to be revisited at some point
-   * in the future, *aEnd will be reduced to the first time in the future to
-   * recompute those decisions.
-   */
-  void RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
-                           GraphTime aTime, GraphTime aEndBlockingDecisions,
-                           GraphTime* aEnd);
-  /**
-   * Returns true if aStream will underrun at aTime for its own playback.
-   * aEndBlockingDecisions is when we plan to stop making blocking decisions.
-   * *aEnd will be reduced to the first time in the future to recompute these
-   * decisions.
-   */
-  bool WillUnderrun(MediaStream* aStream, GraphTime aTime,
-                    GraphTime aEndBlockingDecisions, GraphTime* aEnd);
-  /**
-   * Given a graph time aTime, convert it to a stream time taking into
-   * account the time during which aStream is scheduled to be blocked.
-   */
-  StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
-  enum {
-    INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
-  };
-  /**
-   * Given a stream time aTime, convert it to a graph time taking into
-   * account the time during which aStream is scheduled to be blocked.
-   * aTime must be <= mStateComputedTime since blocking decisions
-   * are only known up to that point.
-   * If aTime is exactly at the start of a blocked interval, then the blocked
-   * interval is included in the time returned if and only if
-   * aFlags includes INCLUDE_TRAILING_BLOCKED_INTERVAL.
-   */
-  GraphTime StreamTimeToGraphTime(MediaStream* aStream, StreamTime aTime,
-                                  uint32_t aFlags = 0);
-  /**
-   * Get the current audio position of the stream's audio output.
-   */
-  GraphTime GetAudioPosition(MediaStream* aStream);
-  /**
-   * Call NotifyHaveCurrentData on aStream's listeners.
-   */
-  void NotifyHasCurrentData(MediaStream* aStream);
-  /**
-   * If aStream needs an audio stream but doesn't have one, create it.
-   * If aStream doesn't need an audio stream but has one, destroy it.
-   */
-  void CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
-                                   MediaStream* aStream);
-  /**
-   * Queue audio (mix of stream audio and silence for blocked intervals)
-   * to the audio output stream.
-   */
-  void PlayAudio(MediaStream* aStream, GraphTime aFrom, GraphTime aTo);
-  /**
-   * Set the correct current video frame for stream aStream.
-   */
-  void PlayVideo(MediaStream* aStream);
-  /**
-   * No more data will be forthcoming for aStream. The stream will end
-   * at the current buffer end point. The StreamBuffer's tracks must be
-   * explicitly set to finished by the caller.
-   */
-  void FinishStream(MediaStream* aStream);
-  /**
-   * Compute how much stream data we would like to buffer for aStream.
-   */
-  StreamTime GetDesiredBufferEnd(MediaStream* aStream);
-  /**
-   * Returns true when there are no active streams.
-   */
-  bool IsEmpty() { return mStreams.IsEmpty() && mPortCount == 0; }
-
-  // For use by control messages
-  /**
-   * Identify which graph update index we are currently processing.
-   */
-  int64_t GetProcessingGraphUpdateIndex() { return mProcessingGraphUpdateIndex; }
-  /**
-   * Add aStream to the graph and initializes its graph-specific state.
-   */
-  void AddStream(MediaStream* aStream);
-  /**
-   * Remove aStream from the graph. Ensures that pending messages about the
-   * stream back to the main thread are flushed.
-   */
-  void RemoveStream(MediaStream* aStream);
-  /**
-   * Remove aPort from the graph and release it.
-   */
-  void DestroyPort(MediaInputPort* aPort);
-
-  // Data members
-
-  /**
-   * Media graph thread.
-   * Readonly after initialization on the main thread.
-   */
-  nsCOMPtr<nsIThread> mThread;
-
-  // The following state is managed on the graph thread only, unless
-  // mLifecycleState > LIFECYCLE_RUNNING in which case the graph thread
-  // is not running and this state can be used from the main thread.
-
-  nsTArray<nsRefPtr<MediaStream> > mStreams;
-  /**
-   * The current graph time for the current iteration of the RunThread control
-   * loop.
-   */
-  GraphTime mCurrentTime;
-  /**
-   * Blocking decisions and all stream contents have been computed up to this
-   * time. The next batch of updates from the main thread will be processed
-   * at this time. Always >= mCurrentTime.
-   */
-  GraphTime mStateComputedTime;
-  /**
-   * This is only used for logging.
-   */
-  TimeStamp mInitialTimeStamp;
-  /**
-   * The real timestamp of the latest run of UpdateCurrentTime.
-   */
-  TimeStamp mCurrentTimeStamp;
-  /**
-   * Which update batch we are currently processing.
-   */
-  int64_t mProcessingGraphUpdateIndex;
-  /**
-   * Number of active MediaInputPorts
-   */
-  int32_t mPortCount;
-
-  // mMonitor guards the data below.
-  // MediaStreamGraph normally does its work without holding mMonitor, so it is
-  // not safe to just grab mMonitor from some thread and start monkeying with
-  // the graph. Instead, communicate with the graph thread using provided
-  // mechanisms such as the ControlMessage queue.
-  Monitor mMonitor;
-
-  // Data guarded by mMonitor (must always be accessed with mMonitor held,
-  // regardless of the value of mLifecycleState.
-
-  /**
-   * State to copy to main thread
-   */
-  nsTArray<StreamUpdate> mStreamUpdates;
-  /**
-   * Runnables to run after the next update to main thread state.
-   */
-  nsTArray<nsCOMPtr<nsIRunnable> > mUpdateRunnables;
-  struct MessageBlock {
-    int64_t mGraphUpdateIndex;
-    nsTArray<nsAutoPtr<ControlMessage> > mMessages;
-  };
-  /**
-   * A list of batches of messages to process. Each batch is processed
-   * as an atomic unit.
-   */
-  nsTArray<MessageBlock> mMessageQueue;
-  /**
-   * This enum specifies where this graph is in its lifecycle. This is used
-   * to control shutdown.
-   * Shutdown is tricky because it can happen in two different ways:
-   * 1) Shutdown due to inactivity. RunThread() detects that it has no
-   * pending messages and no streams, and exits. The next RunInStableState()
-   * checks if there are new pending messages from the main thread (true only
-   * if new stream creation raced with shutdown); if there are, it revives
-   * RunThread(), otherwise it commits to shutting down the graph. New stream
-   * creation after this point will create a new graph. An async event is
-   * dispatched to Shutdown() the graph's threads and then delete the graph
-   * object.
-   * 2) Forced shutdown at application shutdown. A flag is set, RunThread()
-   * detects the flag and exits, the next RunInStableState() detects the flag,
-   * and dispatches the async event to Shutdown() the graph's threads. However
-   * the graph object is not deleted. New messages for the graph are processed
-   * synchronously on the main thread if necessary. When the last stream is
-   * destroyed, the graph object is deleted.
-   */
-  enum LifecycleState {
-    // The graph thread hasn't started yet.
-    LIFECYCLE_THREAD_NOT_STARTED,
-    // RunThread() is running normally.
-    LIFECYCLE_RUNNING,
-    // In the following states, the graph thread is not running so
-    // all "graph thread only" state in this class can be used safely
-    // on the main thread.
-    // RunThread() has exited and we're waiting for the next
-    // RunInStableState(), at which point we can clean up the main-thread
-    // side of the graph.
-    LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP,
-    // RunInStableState() posted a ShutdownRunnable, and we're waiting for it
-    // to shut down the graph thread(s).
-    LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN,
-    // Graph threads have shut down but we're waiting for remaining streams
-    // to be destroyed. Only happens during application shutdown since normally
-    // we'd only shut down a graph when it has no streams.
-    LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION
-  };
-  LifecycleState mLifecycleState;
-  /**
-   * This enum specifies the wait state of the graph thread.
-   */
-  enum WaitState {
-    // RunThread() is running normally
-    WAITSTATE_RUNNING,
-    // RunThread() is paused waiting for its next iteration, which will
-    // happen soon
-    WAITSTATE_WAITING_FOR_NEXT_ITERATION,
-    // RunThread() is paused indefinitely waiting for something to change
-    WAITSTATE_WAITING_INDEFINITELY,
-    // Something has signaled RunThread() to wake up immediately,
-    // but it hasn't done so yet
-    WAITSTATE_WAKING_UP
-  };
-  WaitState mWaitState;
-  /**
-   * True when another iteration of the control loop is required.
-   */
-  bool mNeedAnotherIteration;
-  /**
-   * True when we need to do a forced shutdown during application shutdown.
-   */
-  bool mForceShutDown;
-  /**
-   * True when we have posted an event to the main thread to run
-   * RunInStableState() and the event hasn't run yet.
-   */
-  bool mPostedRunInStableStateEvent;
-
-  // Main thread only
-
-  /**
-   * Messages posted by the current event loop task. These are forwarded to
-   * the media graph thread during RunInStableState. We can't forward them
-   * immediately because we want all messages between stable states to be
-   * processed as an atomic batch.
-   */
-  nsTArray<nsAutoPtr<ControlMessage> > mCurrentTaskMessageQueue;
-  /**
-   * True when RunInStableState has determined that mLifecycleState is >
-   * LIFECYCLE_RUNNING. Since only the main thread can reset mLifecycleState to
-   * LIFECYCLE_RUNNING, this can be relied on to not change unexpectedly.
-   */
-  bool mDetectedNotRunning;
-  /**
-   * True when a stable state runner has been posted to the appshell to run
-   * RunInStableState at the next stable state.
-   */
-  bool mPostedRunInStableState;
-};
-
 /**
  * The singleton graph instance.
  */
 static MediaStreamGraphImpl* gGraph;
 
 StreamTime
 MediaStreamGraphImpl::GetDesiredBufferEnd(MediaStream* aStream)
 {
@@ -699,17 +221,26 @@ MediaStreamGraphImpl::GraphTimeToStreamT
   while (t < aTime) {
     GraphTime end;
     if (!aStream->mBlocked.GetAt(t, &end)) {
       s += std::min(aTime, end) - t;
     }
     t = end;
   }
   return std::max<StreamTime>(0, s);
-}  
+}
+
+StreamTime
+MediaStreamGraphImpl::GraphTimeToStreamTimeOptimistic(MediaStream* aStream,
+                                                      GraphTime aTime)
+{
+  GraphTime computedUpToTime = std::min(mStateComputedTime, aTime);
+  StreamTime s = GraphTimeToStreamTime(aStream, computedUpToTime);
+  return s + (aTime - computedUpToTime);
+}
 
 GraphTime
 MediaStreamGraphImpl::StreamTimeToGraphTime(MediaStream* aStream,
                                             StreamTime aTime, uint32_t aFlags)
 {
   if (aTime >= STREAM_TIME_MAX) {
     return GRAPH_TIME_MAX;
   }
@@ -767,25 +298,17 @@ void
 MediaStreamGraphImpl::UpdateCurrentTime()
 {
   GraphTime prevCurrentTime = mCurrentTime;
   TimeStamp now = TimeStamp::Now();
   GraphTime nextCurrentTime =
     SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()) + mCurrentTime;
   if (mStateComputedTime < nextCurrentTime) {
     LOG(PR_LOG_WARNING, ("Media graph global underrun detected"));
-    LOG(PR_LOG_DEBUG, ("Advancing mStateComputedTime from %f to %f",
-                       MediaTimeToSeconds(mStateComputedTime),
-                       MediaTimeToSeconds(nextCurrentTime)));
-    // Advance mStateComputedTime to nextCurrentTime by
-    // adding blocked time to all streams starting at mStateComputedTime
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      mStreams[i]->mBlocked.SetAtAndAfter(mStateComputedTime, true);
-    }
-    mStateComputedTime = nextCurrentTime;
+    nextCurrentTime = mStateComputedTime;
   }
   mCurrentTimeStamp = now;
 
   LOG(PR_LOG_DEBUG, ("Updating current time to %f (real %f, mStateComputedTime %f)",
                      MediaTimeToSeconds(nextCurrentTime),
                      (now - mInitialTimeStamp).ToSeconds(),
                      MediaTimeToSeconds(mStateComputedTime)));
 
@@ -1348,16 +871,50 @@ MediaStreamGraphImpl::EnsureNextIteratio
     return;
   mNeedAnotherIteration = true;
   if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
     mWaitState = WAITSTATE_WAKING_UP;
     aLock.Notify();
   }
 }
 
+static GraphTime
+RoundUpToAudioBlock(GraphTime aTime)
+{
+  TrackRate rate = IdealAudioRate();
+  int64_t ticksAtIdealRate = (aTime*rate) >> MEDIA_TIME_FRAC_BITS;
+  // Round up to nearest block boundary
+  int64_t blocksAtIdealRate =
+    (ticksAtIdealRate + (WEBAUDIO_BLOCK_SIZE - 1)) >>
+    WEBAUDIO_BLOCK_SIZE_BITS;
+  // Round up to nearest MediaTime unit
+  return
+    ((((blocksAtIdealRate + 1)*WEBAUDIO_BLOCK_SIZE) << MEDIA_TIME_FRAC_BITS)
+     + rate - 1)/rate;
+}
+
+void
+MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
+                                                        GraphTime aFrom,
+                                                        GraphTime aTo)
+{
+  GraphTime t = aFrom;
+  while (t < aTo) {
+    GraphTime next = RoundUpToAudioBlock(t + 1);
+    for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
+      ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
+      if (ps) {
+        ps->ProduceOutput(t, next);
+      }
+    }
+    t = next;
+  }
+  NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
+}
+
 void
 MediaStreamGraphImpl::RunThread()
 {
   nsTArray<MessageBlock> messageQueue;
   {
     MonitorAutoLock lock(mMonitor);
     messageQueue.SwapElements(mMessageQueue);
   }
@@ -1379,19 +936,18 @@ MediaStreamGraphImpl::RunThread()
       for (uint32_t j = 0; j < messages.Length(); ++j) {
         messages[j]->Run();
       }
     }
     messageQueue.Clear();
 
     UpdateStreamOrder();
 
-    int32_t writeAudioUpTo = AUDIO_TARGET_MS;
     GraphTime endBlockingDecisions =
-      mCurrentTime + MillisecondsToMediaTime(writeAudioUpTo);
+      RoundUpToAudioBlock(mCurrentTime + MillisecondsToMediaTime(AUDIO_TARGET_MS));
     bool ensureNextIteration = false;
 
     // Grab pending stream input.
     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
       SourceMediaStream* is = mStreams[i]->AsSourceStream();
       if (is) {
         UpdateConsumptionState(is);
         ExtractPendingInput(is, endBlockingDecisions, &ensureNextIteration);
@@ -1400,25 +956,37 @@ MediaStreamGraphImpl::RunThread()
 
     // Figure out which streams are blocked and when.
     GraphTime prevComputedTime = mStateComputedTime;
     RecomputeBlocking(endBlockingDecisions);
 
     // Play stream contents.
     uint32_t audioStreamsActive = 0;
     bool allBlockedForever = true;
+    // True when we've done ProduceOutput for all processed streams.
+    bool doneAllProducing = false;
     // Figure out what each stream wants to do
     for (uint32_t i = 0; i < mStreams.Length(); ++i) {
       MediaStream* stream = mStreams[i];
-      ProcessedMediaStream* ps = stream->AsProcessedStream();
-      if (ps && !ps->mFinished) {
-        ps->ProduceOutput(prevComputedTime, mStateComputedTime);
-        NS_ASSERTION(stream->mBuffer.GetEnd() >=
-                     GraphTimeToStreamTime(stream, mStateComputedTime),
-                     "Stream did not produce enough data");
+      if (!doneAllProducing && !stream->IsFinishedOnGraphThread()) {
+        ProcessedMediaStream* ps = stream->AsProcessedStream();
+        if (ps) {
+          AudioNodeStream* n = stream->AsAudioNodeStream();
+          if (n) {
+            // Since an AudioNodeStream is present, go ahead and
+            // produce audio block by block for all the rest of the streams.
+            ProduceDataForStreamsBlockByBlock(i, prevComputedTime, mStateComputedTime);
+            doneAllProducing = true;
+          } else {
+            ps->ProduceOutput(prevComputedTime, mStateComputedTime);
+            NS_ASSERTION(stream->mBuffer.GetEnd() >=
+                         GraphTimeToStreamTime(stream, mStateComputedTime),
+                       "Stream did not produce enough data");
+          }
+        }
       }
       NotifyHasCurrentData(stream);
       CreateOrDestroyAudioStreams(prevComputedTime, stream);
       PlayAudio(stream, prevComputedTime, mStateComputedTime);
       audioStreamsActive += stream->mAudioOutputStreams.Length();
       PlayVideo(stream);
       SourceMediaStream* is = stream->AsSourceStream();
       if (is) {
@@ -1765,16 +1333,28 @@ MediaStream::Graph()
 }
 
 StreamTime
 MediaStream::GraphTimeToStreamTime(GraphTime aTime)
 {
   return GraphImpl()->GraphTimeToStreamTime(this, aTime);
 }
 
+StreamTime
+MediaStream::GraphTimeToStreamTimeOptimistic(GraphTime aTime)
+{
+  return GraphImpl()->GraphTimeToStreamTimeOptimistic(this, aTime);
+}
+
+GraphTime
+MediaStream::StreamTimeToGraphTime(StreamTime aTime)
+{
+  return GraphImpl()->StreamTimeToGraphTime(this, aTime, 0);
+}
+
 void
 MediaStream::FinishOnGraphThread()
 {
   GraphImpl()->FinishStream(this);
 }
 
 void
 MediaStream::RemoveAllListenersImpl()
@@ -2211,34 +1791,37 @@ MediaInputPort::GraphImpl()
 }
 
 MediaStreamGraph*
 MediaInputPort::Graph()
 {
   return gGraph;
 }
 
-MediaInputPort*
+already_AddRefed<MediaInputPort>
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags)
 {
+  // This method creates two references to the MediaInputPort: one for
+  // the main thread, and one for the MediaStreamGraph.
   class Message : public ControlMessage {
   public:
     Message(MediaInputPort* aPort)
       : ControlMessage(aPort->GetDestination()),
         mPort(aPort) {}
     virtual void Run()
     {
       mPort->Init();
+      // The graph holds its reference implicitly
+      mPort.forget();
     }
-    MediaInputPort* mPort;
+    nsRefPtr<MediaInputPort> mPort;
   };
-  MediaInputPort* port = new MediaInputPort(aStream, this, aFlags);
-  NS_ADDREF(port);
+  nsRefPtr<MediaInputPort> port = new MediaInputPort(aStream, this, aFlags);
   GraphImpl()->AppendMessage(new Message(port));
-  return port;
+  return port.forget();
 }
 
 void
 ProcessedMediaStream::Finish()
 {
   class Message : public ControlMessage {
   public:
     Message(ProcessedMediaStream* aStream)
@@ -2255,17 +1838,17 @@ void
 ProcessedMediaStream::SetAutofinish(bool aAutofinish)
 {
   class Message : public ControlMessage {
   public:
     Message(ProcessedMediaStream* aStream, bool aAutofinish)
       : ControlMessage(aStream), mAutofinish(aAutofinish) {}
     virtual void Run()
     {
-      mStream->AsProcessedStream()->SetAutofinishImpl(mAutofinish);
+      static_cast<ProcessedMediaStream*>(mStream)->SetAutofinishImpl(mAutofinish);
     }
     bool mAutofinish;
   };
   GraphImpl()->AppendMessage(new Message(this, aAutofinish));
 }
 
 void
 ProcessedMediaStream::DestroyImpl()
@@ -2355,9 +1938,18 @@ ProcessedMediaStream*
 MediaStreamGraph::CreateTrackUnionStream(nsDOMMediaStream* aWrapper)
 {
   TrackUnionStream* stream = new TrackUnionStream(aWrapper);
   NS_ADDREF(stream);
   static_cast<MediaStreamGraphImpl*>(this)->AppendMessage(new CreateMessage(stream));
   return stream;
 }
 
+AudioNodeStream*
+MediaStreamGraph::CreateAudioNodeStream(AudioNodeEngine* aEngine)
+{
+  AudioNodeStream* stream = new AudioNodeStream(aEngine);
+  NS_ADDREF(stream);
+  static_cast<MediaStreamGraphImpl*>(this)->AppendMessage(new CreateMessage(stream));
+  return stream;
 }
+
+}
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -167,36 +167,36 @@ public:
                                         const MediaSegment& aQueuedMedia) {}
 };
 
 /**
  * This is a base class for main-thread listener callbacks.
  * This callback is invoked on the main thread when the main-thread-visible
  * state of a stream has changed.
  *
- * These methods are called without the media graph monitor held, so
- * reentry into media graph methods is possible, although very much discouraged!
+ * These methods are called with the media graph monitor held, so
+ * reentry into general media graph methods is not possible.
  * You should do something non-blocking and non-reentrant (e.g. dispatch an
- * event) and return.
+ * event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate
+ * would be a good choice.
  * The listener is allowed to synchronously remove itself from the stream, but
  * not add or remove any other listeners.
  */
 class MainThreadMediaStreamListener {
 public:
-  virtual ~MainThreadMediaStreamListener() {}
-
-  NS_INLINE_DECL_REFCOUNTING(MainThreadMediaStreamListener)
-
   virtual void NotifyMainThreadStateChanged() = 0;
 };
 
 class MediaStreamGraphImpl;
 class SourceMediaStream;
 class ProcessedMediaStream;
 class MediaInputPort;
+class AudioNodeStream;
+class AudioNodeEngine;
+struct AudioChunk;
 
 /**
  * A stream of synchronized audio and video data. All (not blocked) streams
  * progress at the same rate --- "real time". Streams cannot seek. The only
  * operation readers can perform on a stream is to read the next data.
  *
  * Consumers of a stream can be reading from it at different offsets, but that
  * should only happen due to the order in which consumers are being run.
@@ -269,17 +269,22 @@ public:
     , mNotifiedFinished(false)
     , mNotifiedBlocked(false)
     , mWrapper(aWrapper)
     , mMainThreadCurrentTime(0)
     , mMainThreadFinished(false)
     , mMainThreadDestroyed(false)
   {
   }
-  virtual ~MediaStream() {}
+  virtual ~MediaStream()
+  {
+    NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
+    NS_ASSERTION(mMainThreadListeners.IsEmpty(),
+                 "All main thread listeners should have been removed");
+  }
 
   /**
    * Returns the graph that owns this stream.
    */
   MediaStreamGraphImpl* GraphImpl();
   MediaStreamGraph* Graph();
 
   // Control API.
@@ -298,21 +303,25 @@ public:
   void AddVideoOutput(VideoFrameContainer* aContainer);
   void RemoveVideoOutput(VideoFrameContainer* aContainer);
   // Explicitly block. Useful for example if a media element is pausing
   // and we need to stop its stream emitting its buffered data.
   void ChangeExplicitBlockerCount(int32_t aDelta);
   // Events will be dispatched by calling methods of aListener.
   void AddListener(MediaStreamListener* aListener);
   void RemoveListener(MediaStreamListener* aListener);
+  // Events will be dispatched by calling methods of aListener. It is the
+  // responsibility of the caller to remove aListener before it is destroyed.
   void AddMainThreadListener(MainThreadMediaStreamListener* aListener)
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
     mMainThreadListeners.AppendElement(aListener);
   }
+  // It's safe to call this even if aListener is not currently a listener;
+  // the call will be ignored.
   void RemoveMainThreadListener(MainThreadMediaStreamListener* aListener)
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
     mMainThreadListeners.RemoveElement(aListener);
   }
   // Signal that the client is done with this MediaStream. It will be deleted later.
   void Destroy();
   // Returns the main-thread's view of how much data has been processed by
@@ -334,16 +343,17 @@ public:
     return mMainThreadDestroyed;
   }
 
   friend class MediaStreamGraphImpl;
   friend class MediaInputPort;
 
   virtual SourceMediaStream* AsSourceStream() { return nullptr; }
   virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; }
+  virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; }
 
   // media graph thread only
   void Init();
   // These Impl methods perform the core functionality of the control methods
   // above, on the media graph thread.
   /**
    * Stop all stream activity and disconnect it from all inputs and outputs.
    * This must be idempotent.
@@ -359,17 +369,17 @@ public:
   void AddVideoOutputImpl(already_AddRefed<VideoFrameContainer> aContainer)
   {
     *mVideoOutputs.AppendElement() = aContainer;
   }
   void RemoveVideoOutputImpl(VideoFrameContainer* aContainer)
   {
     mVideoOutputs.RemoveElement(aContainer);
   }
-  void ChangeExplicitBlockerCountImpl(StreamTime aTime, int32_t aDelta)
+  void ChangeExplicitBlockerCountImpl(GraphTime aTime, int32_t aDelta)
   {
     mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta);
   }
   void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener);
   void RemoveListenerImpl(MediaStreamListener* aListener);
   void RemoveAllListenersImpl();
 
   void AddConsumer(MediaInputPort* aPort)
@@ -377,17 +387,34 @@ public:
     mConsumers.AppendElement(aPort);
   }
   void RemoveConsumer(MediaInputPort* aPort)
   {
     mConsumers.RemoveElement(aPort);
   }
   const StreamBuffer& GetStreamBuffer() { return mBuffer; }
   GraphTime GetStreamBufferStartTime() { return mBufferStartTime; }
+  /**
+   * Convert graph time to stream time. aTime must be <= mStateComputedTime
+   * to ensure we know exactly how much time this stream will be blocked during
+   * the interval.
+   */
   StreamTime GraphTimeToStreamTime(GraphTime aTime);
+  /**
+   * Convert graph time to stream time. aTime can be > mStateComputedTime,
+   * in which case we optimistically assume the stream will not be blocked
+   * after mStateComputedTime.
+   */
+  StreamTime GraphTimeToStreamTimeOptimistic(GraphTime aTime);
+  /**
+   * Convert stream time to graph time. The result can be > mStateComputedTime,
+   * in which case we did the conversion optimistically assuming the stream
+   * will not be blocked after mStateComputedTime.
+   */
+  GraphTime StreamTimeToGraphTime(StreamTime aTime);
   bool IsFinishedOnGraphThread() { return mFinished; }
   void FinishOnGraphThread();
 
 protected:
   virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
   {
     mBufferStartTime += aBlockedTime;
     mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime);
@@ -419,17 +446,17 @@ protected:
   nsTArray<nsRefPtr<VideoFrameContainer> > mVideoOutputs;
   // We record the last played video frame to avoid redundant setting
   // of the current video frame.
   VideoFrame mLastPlayedVideoFrame;
   // The number of times this stream has been explicitly blocked by the control
   // API, minus the number of times it has been explicitly unblocked.
   TimeVarying<GraphTime,uint32_t> mExplicitBlockerCount;
   nsTArray<nsRefPtr<MediaStreamListener> > mListeners;
-  nsTArray<nsRefPtr<MainThreadMediaStreamListener> > mMainThreadListeners;
+  nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners;
 
   // Precomputed blocking status (over GraphTime).
   // This is only valid between the graph's mCurrentTime and
   // mStateComputedTime. The stream is considered to have
   // not been blocked before mCurrentTime (its mBufferStartTime is increased
   // as necessary to account for that time instead) --- this avoids us having to
   // record the entire history of the stream's blocking-ness in mBlocked.
   TimeVarying<GraphTime,bool> mBlocked;
@@ -738,17 +765,18 @@ public:
     : MediaStream(aWrapper), mAutofinish(false), mInCycle(false)
   {}
 
   // Control API.
   /**
    * Allocates a new input port attached to source aStream.
    * This stream can be removed by calling MediaInputPort::Remove().
    */
-  MediaInputPort* AllocateInputPort(MediaStream* aStream, uint32_t aFlags = 0);
+  already_AddRefed<MediaInputPort> AllocateInputPort(MediaStream* aStream,
+                                                     uint32_t aFlags = 0);
   /**
    * Force this stream into the finished state.
    */
   void Finish();
   /**
    * Set the autofinish flag on this stream (defaults to false). When this flag
    * is set, and all input streams are in the finished state (including if there
    * are no input streams), this stream automatically enters the finished state.
@@ -790,22 +818,30 @@ protected:
   // The list of all inputs that are currently enabled or waiting to be enabled.
   nsTArray<MediaInputPort*> mInputs;
   bool mAutofinish;
   // True if and only if this stream is in a cycle.
   // Updated by MediaStreamGraphImpl::UpdateStreamOrder.
   bool mInCycle;
 };
 
+// Returns ideal audio rate for processing
+inline TrackRate IdealAudioRate() { return 48000; }
+
 /**
  * Initially, at least, we will have a singleton MediaStreamGraph per
  * process.
  */
 class MediaStreamGraph {
 public:
+  // We ensure that the graph current time advances in multiples of
+  // IdealAudioBlockSize()/IdealAudioRate(). A stream that never blocks
+  // and has a track with the ideal audio rate will produce audio in
+  // multiples of the block size.
+
   // Main thread only
   static MediaStreamGraph* GetInstance();
   // Control API.
   /**
    * Create a stream that a media decoder (or some other source of
    * media data, such as a camera) can write to.
    */
   SourceMediaStream* CreateSourceStream(nsDOMMediaStream* aWrapper);
@@ -820,31 +856,36 @@ public:
    * For each added track, the track ID of the output track is the track ID
    * of the input track or one plus the maximum ID of all previously added
    * tracks, whichever is greater.
    * TODO at some point we will probably need to add API to select
    * particular tracks of each input stream.
    */
   ProcessedMediaStream* CreateTrackUnionStream(nsDOMMediaStream* aWrapper);
   /**
+   * Create a stream that will process audio for an AudioNode.
+   * Takes ownership of aEngine.
+   */
+  AudioNodeStream* CreateAudioNodeStream(AudioNodeEngine* aEngine);
+  /**
    * Returns the number of graph updates sent. This can be used to track
    * whether a given update has been processed by the graph thread and reflected
    * in main-thread stream state.
    */
   int64_t GetCurrentGraphUpdateIndex() { return mGraphUpdatesSent; }
 
   /**
    * Media graph thread only.
    * Dispatches a runnable that will run on the main thread after all
    * main-thread stream state has been next updated.
    * Should only be called during MediaStreamListener callbacks.
    */
-  void DispatchToMainThreadAfterStreamStateUpdate(nsIRunnable* aRunnable)
+  void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
   {
-    mPendingUpdateRunnables.AppendElement(aRunnable);
+    *mPendingUpdateRunnables.AppendElement() = aRunnable;
   }
 
 protected:
   MediaStreamGraph()
     : mGraphUpdatesSent(1)
   {
     MOZ_COUNT_CTOR(MediaStreamGraph);
   }
copy from content/media/MediaStreamGraph.cpp
copy to content/media/MediaStreamGraphImpl.h
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraphImpl.h
@@ -1,78 +1,63 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifndef MOZILLA_MEDIASTREAMGRAPHIMPL_H_
+#define MOZILLA_MEDIASTREAMGRAPHIMPL_H_
+
 #include "MediaStreamGraph.h"
 
 #include "mozilla/Monitor.h"
 #include "mozilla/TimeStamp.h"
-#include "AudioSegment.h"
-#include "VideoSegment.h"
-#include "nsContentUtils.h"
-#include "nsIAppShell.h"
-#include "nsIObserver.h"
-#include "nsServiceManagerUtils.h"
-#include "nsWidgetsCID.h"
-#include "nsXPCOMCIDInternal.h"
-#include "prlog.h"
-#include "VideoUtils.h"
-#include "mozilla/Attributes.h"
-#include "TrackUnionStream.h"
-#include "ImageContainer.h"
-#include "AudioChannelCommon.h"
-#include <algorithm>
-
-using namespace mozilla::layers;
-using namespace mozilla::dom;
+#include "nsIThread.h"
+#include "nsIRunnable.h"
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
-PRLogModuleInfo* gMediaStreamGraphLog;
+extern PRLogModuleInfo* gMediaStreamGraphLog;
 #define LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
 #else
 #define LOG(type, msg)
 #endif
 
-namespace {
-
 /**
  * Assume we can run an iteration of the MediaStreamGraph loop in this much time
  * or less.
  * We try to run the control loop at this rate.
  */
-const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
+static const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
 
 /**
  * Assume that we might miss our scheduled wakeup of the MediaStreamGraph by
  * this much.
  */
-const int SCHEDULE_SAFETY_MARGIN_MS = 10;
+static const int SCHEDULE_SAFETY_MARGIN_MS = 10;
 
 /**
  * Try have this much audio buffered in streams and queued to the hardware.
  * The maximum delay to the end of the next control loop
  * is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
  * There is no point in buffering more audio than this in a stream at any
  * given time (until we add processing).
  * This is not optimal yet.
  */
-const int AUDIO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
+static const int AUDIO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
     SCHEDULE_SAFETY_MARGIN_MS;
 
 /**
  * Try have this much video buffered. Video frames are set
  * near the end of the iteration of the control loop. The maximum delay
  * to the setting of the next video frame is 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
  * SCHEDULE_SAFETY_MARGIN_MS. This is not optimal yet.
  */
-const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
+static const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
     SCHEDULE_SAFETY_MARGIN_MS;
 
 /**
  * A per-stream update message passed from the media graph thread to the
  * main thread.
  */
 struct StreamUpdate {
   int64_t mGraphUpdateIndex;
@@ -108,18 +93,16 @@ public:
 
 protected:
   // We do not hold a reference to mStream. The graph will be holding
   // a reference to the stream until the Destroy message is processed. The
   // last message referencing a stream is the Destroy message for that stream.
   MediaStream* mStream;
 };
 
-}
-
 /**
  * The implementation of a media stream graph. This class is private to this
  * file. It's not in the anonymous namespace because MediaStream needs to
  * be able to friend it.
  *
  * Currently we only have one per process.
  */
 class MediaStreamGraphImpl : public MediaStreamGraph {
@@ -262,28 +245,47 @@ public:
    * If this results in decisions that need to be revisited at some point
    * in the future, *aEnd will be reduced to the first time in the future to
    * recompute those decisions.
    */
   void RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
                            GraphTime aTime, GraphTime aEndBlockingDecisions,
                            GraphTime* aEnd);
   /**
+   * Produce data for all streams >= aStreamIndex for the given time interval.
+   * Advances block by block, each iteration producing data for all streams
+   * for a single block.
+   * This is needed if there are WebAudio delay nodes, whose output for a block
+   * may depend on the output of any other node (including itself) for the
+   * previous block. This is probably also more performant due to better memory
+   * locality.
+   * This is called whenever we have an AudioNodeStream in the graph.
+   */
+  void ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
+                                         GraphTime aFrom,
+                                         GraphTime aTo);
+  /**
    * Returns true if aStream will underrun at aTime for its own playback.
    * aEndBlockingDecisions is when we plan to stop making blocking decisions.
    * *aEnd will be reduced to the first time in the future to recompute these
    * decisions.
    */
   bool WillUnderrun(MediaStream* aStream, GraphTime aTime,
                     GraphTime aEndBlockingDecisions, GraphTime* aEnd);
   /**
    * Given a graph time aTime, convert it to a stream time taking into
    * account the time during which aStream is scheduled to be blocked.
    */
   StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
+  /**
+   * Given a graph time aTime, convert it to a stream time taking into
+   * account the time during which aStream is scheduled to be blocked, and
+   * when we don't know whether it's blocked or not, we assume it's not blocked.
+   */
+  StreamTime GraphTimeToStreamTimeOptimistic(MediaStream* aStream, GraphTime aTime);
   enum {
     INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
   };
   /**
    * Given a stream time aTime, convert it to a graph time taking into
    * account the time during which aStream is scheduled to be blocked.
    * aTime must be <= mStateComputedTime since blocking decisions
    * are only known up to that point.
@@ -505,1859 +507,11 @@ public:
   bool mDetectedNotRunning;
   /**
    * True when a stable state runner has been posted to the appshell to run
    * RunInStableState at the next stable state.
    */
   bool mPostedRunInStableState;
 };
 
-/**
- * The singleton graph instance.
- */
-static MediaStreamGraphImpl* gGraph;
-
-StreamTime
-MediaStreamGraphImpl::GetDesiredBufferEnd(MediaStream* aStream)
-{
-  StreamTime current = mCurrentTime - aStream->mBufferStartTime;
-  return current +
-      MillisecondsToMediaTime(std::max(AUDIO_TARGET_MS, VIDEO_TARGET_MS));
-}
-
-void
-MediaStreamGraphImpl::FinishStream(MediaStream* aStream)
-{
-  if (aStream->mFinished)
-    return;
-  LOG(PR_LOG_DEBUG, ("MediaStream %p will finish", aStream));
-  aStream->mFinished = true;
-  // Force at least one more iteration of the control loop, since we rely
-  // on UpdateCurrentTime to notify our listeners once the stream end
-  // has been reached.
-  EnsureNextIteration();
-}
-
-void
-MediaStreamGraphImpl::AddStream(MediaStream* aStream)
-{
-  aStream->mBufferStartTime = mCurrentTime;
-  *mStreams.AppendElement() = already_AddRefed<MediaStream>(aStream);
-  LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream));
-}
-
-void
-MediaStreamGraphImpl::RemoveStream(MediaStream* aStream)
-{
-  // Remove references in mStreamUpdates before we allow aStream to die.
-  // Pending updates are not needed (since the main thread has already given
-  // up the stream) so we will just drop them.
-  {
-    MonitorAutoLock lock(mMonitor);
-    for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) {
-      if (mStreamUpdates[i].mStream == aStream) {
-        mStreamUpdates[i].mStream = nullptr;
-      }
-    }
-  }
-
-  // This unrefs the stream, probably destroying it
-  mStreams.RemoveElement(aStream);
-
-  LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream));
-}
-
-void
-MediaStreamGraphImpl::UpdateConsumptionState(SourceMediaStream* aStream)
-{
-  MediaStreamListener::Consumption state =
-      aStream->mIsConsumed ? MediaStreamListener::CONSUMED
-      : MediaStreamListener::NOT_CONSUMED;
-  if (state != aStream->mLastConsumptionState) {
-    aStream->mLastConsumptionState = state;
-    for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-      MediaStreamListener* l = aStream->mListeners[j];
-      l->NotifyConsumptionChanged(this, state);
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream,
-                                          GraphTime aDesiredUpToTime,
-                                          bool* aEnsureNextIteration)
-{
-  bool finished;
-  {
-    MutexAutoLock lock(aStream->mMutex);
-    if (aStream->mPullEnabled && !aStream->mFinished &&
-        !aStream->mListeners.IsEmpty()) {
-      // Compute how much stream time we'll need assuming we don't block
-      // the stream at all between mBlockingDecisionsMadeUntilTime and
-      // aDesiredUpToTime.
-      StreamTime t =
-        GraphTimeToStreamTime(aStream, mStateComputedTime) +
-        (aDesiredUpToTime - mStateComputedTime);
-      if (t > aStream->mBuffer.GetEnd()) {
-        *aEnsureNextIteration = true;
-        for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-          MediaStreamListener* l = aStream->mListeners[j];
-          {
-            MutexAutoUnlock unlock(aStream->mMutex);
-            l->NotifyPull(this, t);
-          }
-        }
-      }
-    }
-    finished = aStream->mUpdateFinished;
-    for (int32_t i = aStream->mUpdateTracks.Length() - 1; i >= 0; --i) {
-      SourceMediaStream::TrackData* data = &aStream->mUpdateTracks[i];
-      for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-        MediaStreamListener* l = aStream->mListeners[j];
-        TrackTicks offset = (data->mCommands & SourceMediaStream::TRACK_CREATE)
-            ? data->mStart : aStream->mBuffer.FindTrack(data->mID)->GetSegment()->GetDuration();
-        l->NotifyQueuedTrackChanges(this, data->mID, data->mRate,
-                                    offset, data->mCommands, *data->mData);
-      }
-      if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
-        MediaSegment* segment = data->mData.forget();
-        LOG(PR_LOG_DEBUG, ("SourceMediaStream %p creating track %d, rate %d, start %lld, initial end %lld",
-                           aStream, data->mID, data->mRate, int64_t(data->mStart),
-                           int64_t(segment->GetDuration())));
-        aStream->mBuffer.AddTrack(data->mID, data->mRate, data->mStart, segment);
-        // The track has taken ownership of data->mData, so let's replace
-        // data->mData with an empty clone.
-        data->mData = segment->CreateEmptyClone();
-        data->mCommands &= ~SourceMediaStream::TRACK_CREATE;
-      } else if (data->mData->GetDuration() > 0) {
-        MediaSegment* dest = aStream->mBuffer.FindTrack(data->mID)->GetSegment();
-        LOG(PR_LOG_DEBUG, ("SourceMediaStream %p track %d, advancing end from %lld to %lld",
-                           aStream, data->mID,
-                           int64_t(dest->GetDuration()),
-                           int64_t(dest->GetDuration() + data->mData->GetDuration())));
-        dest->AppendFrom(data->mData);
-      }
-      if (data->mCommands & SourceMediaStream::TRACK_END) {
-        aStream->mBuffer.FindTrack(data->mID)->SetEnded();
-        aStream->mUpdateTracks.RemoveElementAt(i);
-      }
-    }
-    aStream->mBuffer.AdvanceKnownTracksTime(aStream->mUpdateKnownTracksTime);
-  }
-  if (finished) {
-    FinishStream(aStream);
-  }
-}
-
-void
-MediaStreamGraphImpl::UpdateBufferSufficiencyState(SourceMediaStream* aStream)
-{
-  StreamTime desiredEnd = GetDesiredBufferEnd(aStream);
-  nsTArray<SourceMediaStream::ThreadAndRunnable> runnables;
-
-  {
-    MutexAutoLock lock(aStream->mMutex);
-    for (uint32_t i = 0; i < aStream->mUpdateTracks.Length(); ++i) {
-      SourceMediaStream::TrackData* data = &aStream->mUpdateTracks[i];
-      if (data->mCommands & SourceMediaStream::TRACK_CREATE) {
-        // This track hasn't been created yet, so we have no sufficiency
-        // data. The track will be created in the next iteration of the
-        // control loop and then we'll fire insufficiency notifications
-        // if necessary.
-        continue;
-      }
-      if (data->mCommands & SourceMediaStream::TRACK_END) {
-        // This track will end, so no point in firing not-enough-data
-        // callbacks.
-        continue;
-      }
-      StreamBuffer::Track* track = aStream->mBuffer.FindTrack(data->mID);
-      // Note that track->IsEnded() must be false, otherwise we would have
-      // removed the track from mUpdateTracks already.
-      NS_ASSERTION(!track->IsEnded(), "What is this track doing here?");
-      data->mHaveEnough = track->GetEndTimeRoundDown() >= desiredEnd;
-      if (!data->mHaveEnough) {
-        runnables.MoveElementsFrom(data->mDispatchWhenNotEnough);
-      }
-    }
-  }
-
-  for (uint32_t i = 0; i < runnables.Length(); ++i) {
-    runnables[i].mThread->Dispatch(runnables[i].mRunnable, 0);
-  }
-}
-
-StreamTime
-MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
-                                            GraphTime aTime)
-{
-  NS_ASSERTION(aTime <= mStateComputedTime,
-               "Don't ask about times where we haven't made blocking decisions yet");
-  if (aTime <= mCurrentTime) {
-    return std::max<StreamTime>(0, aTime - aStream->mBufferStartTime);
-  }
-  GraphTime t = mCurrentTime;
-  StreamTime s = t - aStream->mBufferStartTime;
-  while (t < aTime) {
-    GraphTime end;
-    if (!aStream->mBlocked.GetAt(t, &end)) {
-      s += std::min(aTime, end) - t;
-    }
-    t = end;
-  }
-  return std::max<StreamTime>(0, s);
-}  
-
-GraphTime
-MediaStreamGraphImpl::StreamTimeToGraphTime(MediaStream* aStream,
-                                            StreamTime aTime, uint32_t aFlags)
-{
-  if (aTime >= STREAM_TIME_MAX) {
-    return GRAPH_TIME_MAX;
-  }
-  MediaTime bufferElapsedToCurrentTime = mCurrentTime - aStream->mBufferStartTime;
-  if (aTime < bufferElapsedToCurrentTime ||
-      (aTime == bufferElapsedToCurrentTime && !(aFlags & INCLUDE_TRAILING_BLOCKED_INTERVAL))) {
-    return aTime + aStream->mBufferStartTime;
-  }
-
-  MediaTime streamAmount = aTime - bufferElapsedToCurrentTime;
-  NS_ASSERTION(streamAmount >= 0, "Can't answer queries before current time");
-
-  GraphTime t = mCurrentTime;
-  while (t < GRAPH_TIME_MAX) {
-    bool blocked;
-    GraphTime end;
-    if (t < mStateComputedTime) {
-      blocked = aStream->mBlocked.GetAt(t, &end);
-      end = std::min(end, mStateComputedTime);
-    } else {
-      blocked = false;
-      end = GRAPH_TIME_MAX;
-    }
-    if (blocked) {
-      t = end;
-    } else {
-      if (streamAmount == 0) {
-        // No more stream time to consume at time t, so we're done.
-        break;
-      }
-      MediaTime consume = std::min(end - t, streamAmount);
-      streamAmount -= consume;
-      t += consume;
-    }
-  }
-  return t;
-}
-
-GraphTime
-MediaStreamGraphImpl::GetAudioPosition(MediaStream* aStream)
-{
-  if (aStream->mAudioOutputStreams.IsEmpty()) {
-    return mCurrentTime;
-  }
-  int64_t positionInFrames = aStream->mAudioOutputStreams[0].mStream->GetPositionInFrames();
-  if (positionInFrames < 0) {
-    return mCurrentTime;
-  }
-  return aStream->mAudioOutputStreams[0].mAudioPlaybackStartTime +
-      TicksToTimeRoundDown(aStream->mAudioOutputStreams[0].mStream->GetRate(),
-                           positionInFrames);
-}
-
-void
-MediaStreamGraphImpl::UpdateCurrentTime()
-{
-  GraphTime prevCurrentTime = mCurrentTime;
-  TimeStamp now = TimeStamp::Now();
-  GraphTime nextCurrentTime =
-    SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()) + mCurrentTime;
-  if (mStateComputedTime < nextCurrentTime) {
-    LOG(PR_LOG_WARNING, ("Media graph global underrun detected"));
-    LOG(PR_LOG_DEBUG, ("Advancing mStateComputedTime from %f to %f",
-                       MediaTimeToSeconds(mStateComputedTime),
-                       MediaTimeToSeconds(nextCurrentTime)));
-    // Advance mStateComputedTime to nextCurrentTime by
-    // adding blocked time to all streams starting at mStateComputedTime
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      mStreams[i]->mBlocked.SetAtAndAfter(mStateComputedTime, true);
-    }
-    mStateComputedTime = nextCurrentTime;
-  }
-  mCurrentTimeStamp = now;
-
-  LOG(PR_LOG_DEBUG, ("Updating current time to %f (real %f, mStateComputedTime %f)",
-                     MediaTimeToSeconds(nextCurrentTime),
-                     (now - mInitialTimeStamp).ToSeconds(),
-                     MediaTimeToSeconds(mStateComputedTime)));
-
-  if (prevCurrentTime >= nextCurrentTime) {
-    NS_ASSERTION(prevCurrentTime == nextCurrentTime, "Time can't go backwards!");
-    // This could happen due to low clock resolution, maybe?
-    LOG(PR_LOG_DEBUG, ("Time did not advance"));
-    // There's not much left to do here, but the code below that notifies
-    // listeners that streams have ended still needs to run.
-  }
-
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    MediaStream* stream = mStreams[i];
-
-    // Calculate blocked time and fire Blocked/Unblocked events
-    GraphTime blockedTime = 0;
-    GraphTime t = prevCurrentTime;
-    while (t < nextCurrentTime) {
-      GraphTime end;
-      bool blocked = stream->mBlocked.GetAt(t, &end);
-      if (blocked) {
-        blockedTime += std::min(end, nextCurrentTime) - t;
-      }
-      if (blocked != stream->mNotifiedBlocked) {
-        for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
-          MediaStreamListener* l = stream->mListeners[j];
-          l->NotifyBlockingChanged(this,
-              blocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
-        }
-        stream->mNotifiedBlocked = blocked;
-      }
-      t = end;
-    }
-
-    stream->AdvanceTimeVaryingValuesToCurrentTime(nextCurrentTime, blockedTime);
-    // Advance mBlocked last so that implementations of
-    // AdvanceTimeVaryingValuesToCurrentTime can rely on the value of mBlocked.
-    stream->mBlocked.AdvanceCurrentTime(nextCurrentTime);
-
-    if (blockedTime < nextCurrentTime - prevCurrentTime) {
-      for (uint32_t i = 0; i < stream->mListeners.Length(); ++i) {
-        MediaStreamListener* l = stream->mListeners[i];
-        l->NotifyOutput(this);
-      }
-    }
-
-    if (stream->mFinished && !stream->mNotifiedFinished &&
-        stream->mBufferStartTime + stream->GetBufferEnd() <= nextCurrentTime) {
-      stream->mNotifiedFinished = true;
-      stream->mLastPlayedVideoFrame.SetNull();
-      for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
-        MediaStreamListener* l = stream->mListeners[j];
-        l->NotifyFinished(this);
-      }
-    }
-
-    LOG(PR_LOG_DEBUG, ("MediaStream %p bufferStartTime=%f blockedTime=%f",
-                       stream, MediaTimeToSeconds(stream->mBufferStartTime),
-                       MediaTimeToSeconds(blockedTime)));
-  }
-
-  mCurrentTime = nextCurrentTime;
-}
-
-bool
-MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime,
-                                   GraphTime aEndBlockingDecisions, GraphTime* aEnd)
-{
-  // Finished streams can't underrun. ProcessedMediaStreams also can't cause
-  // underrun currently, since we'll always be able to produce data for them
-  // unless they block on some other stream.
-  if (aStream->mFinished || aStream->AsProcessedStream()) {
-    return false;
-  }
-  GraphTime bufferEnd =
-    StreamTimeToGraphTime(aStream, aStream->GetBufferEnd(),
-                          INCLUDE_TRAILING_BLOCKED_INTERVAL);
-  NS_ASSERTION(bufferEnd >= mCurrentTime, "Buffer underran");
-  // We should block after bufferEnd.
-  if (bufferEnd <= aTime) {
-    LOG(PR_LOG_DEBUG, ("MediaStream %p will block due to data underrun, "
-                       "bufferEnd %f",
-                       aStream, MediaTimeToSeconds(bufferEnd)));
-    return true;
-  }
-  // We should keep blocking if we're currently blocked and we don't have
-  // data all the way through to aEndBlockingDecisions. If we don't have
-  // data all the way through to aEndBlockingDecisions, we'll block soon,
-  // but we might as well remain unblocked and play the data we've got while
-  // we can.
-  if (bufferEnd <= aEndBlockingDecisions && aStream->mBlocked.GetBefore(aTime)) {
-    LOG(PR_LOG_DEBUG, ("MediaStream %p will block due to speculative data underrun, "
-                       "bufferEnd %f",
-                       aStream, MediaTimeToSeconds(bufferEnd)));
-    return true;
-  }
-  // Reconsider decisions at bufferEnd
-  *aEnd = std::min(*aEnd, bufferEnd);
-  return false;
-}
-
-void
-MediaStreamGraphImpl::MarkConsumed(MediaStream* aStream)
-{
-  if (aStream->mIsConsumed) {
-    return;
-  }
-  aStream->mIsConsumed = true;
-
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (!ps) {
-    return;
-  }
-  // Mark all the inputs to this stream as consumed
-  for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-    MarkConsumed(ps->mInputs[i]->mSource);
-  }
-}
-
-void
-MediaStreamGraphImpl::UpdateStreamOrderForStream(nsTArray<MediaStream*>* aStack,
-                                                 already_AddRefed<MediaStream> aStream)
-{
-  nsRefPtr<MediaStream> stream = aStream;
-  NS_ASSERTION(!stream->mHasBeenOrdered, "stream should not have already been ordered");
-  if (stream->mIsOnOrderingStack) {
-    for (int32_t i = aStack->Length() - 1; ; --i) {
-      aStack->ElementAt(i)->AsProcessedStream()->mInCycle = true;
-      if (aStack->ElementAt(i) == stream)
-        break;
-    }
-    return;
-  }
-  ProcessedMediaStream* ps = stream->AsProcessedStream();
-  if (ps) {
-    aStack->AppendElement(stream);
-    stream->mIsOnOrderingStack = true;
-    for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-      MediaStream* source = ps->mInputs[i]->mSource;
-      if (!source->mHasBeenOrdered) {
-        nsRefPtr<MediaStream> s = source;
-        UpdateStreamOrderForStream(aStack, s.forget());
-      }
-    }
-    aStack->RemoveElementAt(aStack->Length() - 1);
-    stream->mIsOnOrderingStack = false;
-  }
-
-  stream->mHasBeenOrdered = true;
-  *mStreams.AppendElement() = stream.forget();
-}
-
-void
-MediaStreamGraphImpl::UpdateStreamOrder()
-{
-  nsTArray<nsRefPtr<MediaStream> > oldStreams;
-  oldStreams.SwapElements(mStreams);
-  for (uint32_t i = 0; i < oldStreams.Length(); ++i) {
-    MediaStream* stream = oldStreams[i];
-    stream->mHasBeenOrdered = false;
-    stream->mIsConsumed = false;
-    stream->mIsOnOrderingStack = false;
-    stream->mInBlockingSet = false;
-    ProcessedMediaStream* ps = stream->AsProcessedStream();
-    if (ps) {
-      ps->mInCycle = false;
-    }
-  }
-
-  nsAutoTArray<MediaStream*,10> stack;
-  for (uint32_t i = 0; i < oldStreams.Length(); ++i) {
-    nsRefPtr<MediaStream>& s = oldStreams[i];
-    if (!s->mAudioOutputs.IsEmpty() || !s->mVideoOutputs.IsEmpty()) {
-      MarkConsumed(s);
-    }
-    if (!s->mHasBeenOrdered) {
-      UpdateStreamOrderForStream(&stack, s.forget());
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions)
-{
-  bool blockingDecisionsWillChange = false;
-
-  LOG(PR_LOG_DEBUG, ("Media graph %p computing blocking for time %f",
-                     this, MediaTimeToSeconds(mStateComputedTime)));
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    MediaStream* stream = mStreams[i];
-    if (!stream->mInBlockingSet) {
-      // Compute a partition of the streams containing 'stream' such that we can
-      // compute the blocking status of each subset independently.
-      nsAutoTArray<MediaStream*,10> streamSet;
-      AddBlockingRelatedStreamsToSet(&streamSet, stream);
-
-      GraphTime end;
-      for (GraphTime t = mStateComputedTime;
-           t < aEndBlockingDecisions; t = end) {
-        end = GRAPH_TIME_MAX;
-        RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
-        if (end < GRAPH_TIME_MAX) {
-          blockingDecisionsWillChange = true;
-        }
-      }
-    }
-
-    GraphTime end;
-    stream->mBlocked.GetAt(mCurrentTime, &end);
-    if (end < GRAPH_TIME_MAX) {
-      blockingDecisionsWillChange = true;
-    }
-  }
-  LOG(PR_LOG_DEBUG, ("Media graph %p computed blocking for interval %f to %f",
-                     this, MediaTimeToSeconds(mStateComputedTime),
-                     MediaTimeToSeconds(aEndBlockingDecisions)));
-  mStateComputedTime = aEndBlockingDecisions;
- 
-  if (blockingDecisionsWillChange) {
-    // Make sure we wake up to notify listeners about these changes.
-    EnsureNextIteration();
-  }
-}
-
-void
-MediaStreamGraphImpl::AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
-                                                     MediaStream* aStream)
-{
-  if (aStream->mInBlockingSet)
-    return;
-  aStream->mInBlockingSet = true;
-  aStreams->AppendElement(aStream);
-  for (uint32_t i = 0; i < aStream->mConsumers.Length(); ++i) {
-    MediaInputPort* port = aStream->mConsumers[i];
-    if (port->mFlags & (MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT)) {
-      AddBlockingRelatedStreamsToSet(aStreams, port->mDest);
-    }
-  }
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (ps) {
-    for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-      MediaInputPort* port = ps->mInputs[i];
-      if (port->mFlags & (MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT)) {
-        AddBlockingRelatedStreamsToSet(aStreams, port->mSource);
-      }
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::MarkStreamBlocking(MediaStream* aStream)
-{
-  if (aStream->mBlockInThisPhase)
-    return;
-  aStream->mBlockInThisPhase = true;
-  for (uint32_t i = 0; i < aStream->mConsumers.Length(); ++i) {
-    MediaInputPort* port = aStream->mConsumers[i];
-    if (port->mFlags & MediaInputPort::FLAG_BLOCK_OUTPUT) {
-      MarkStreamBlocking(port->mDest);
-    }
-  }
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (ps) {
-    for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-      MediaInputPort* port = ps->mInputs[i];
-      if (port->mFlags & MediaInputPort::FLAG_BLOCK_INPUT) {
-        MarkStreamBlocking(port->mSource);
-      }
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
-                                          GraphTime aTime,
-                                          GraphTime aEndBlockingDecisions,
-                                          GraphTime* aEnd)
-{
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-    stream->mBlockInThisPhase = false;
-  }
-
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-
-    if (stream->mFinished) {
-      GraphTime endTime = StreamTimeToGraphTime(stream, stream->GetBufferEnd());
-      if (endTime <= aTime) {
-        LOG(PR_LOG_DEBUG, ("MediaStream %p is blocked due to being finished", stream));
-        // We'll block indefinitely
-        MarkStreamBlocking(stream);
-        *aEnd = aEndBlockingDecisions;
-        continue;
-      } else {
-        LOG(PR_LOG_DEBUG, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)",
-                           stream, MediaTimeToSeconds(stream->GetBufferEnd()),
-                           MediaTimeToSeconds(endTime)));
-        *aEnd = std::min(*aEnd, endTime);
-      }
-    }
-
-    GraphTime end;
-    bool explicitBlock = stream->mExplicitBlockerCount.GetAt(aTime, &end) > 0;
-    *aEnd = std::min(*aEnd, end);
-    if (explicitBlock) {
-      LOG(PR_LOG_DEBUG, ("MediaStream %p is blocked due to explicit blocker", stream));
-      MarkStreamBlocking(stream);
-      continue;
-    }
-
-    bool underrun = WillUnderrun(stream, aTime, aEndBlockingDecisions, aEnd);
-    if (underrun) {
-      // We'll block indefinitely
-      MarkStreamBlocking(stream);
-      *aEnd = aEndBlockingDecisions;
-      continue;
-    }
-  }
-  NS_ASSERTION(*aEnd > aTime, "Failed to advance!");
-
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-    stream->mBlocked.SetAtAndAfter(aTime, stream->mBlockInThisPhase);
-  }
-}
-
-void
-MediaStreamGraphImpl::NotifyHasCurrentData(MediaStream* aStream)
-{
-  for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-    MediaStreamListener* l = aStream->mListeners[j];
-    l->NotifyHasCurrentData(this,
-      GraphTimeToStreamTime(aStream, mCurrentTime) < aStream->mBuffer.GetEnd());
-  }
-}
-
-void
-MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
-                                                  MediaStream* aStream)
-{
-  nsAutoTArray<bool,2> audioOutputStreamsFound;
-  for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) {
-    audioOutputStreamsFound.AppendElement(false);
-  }
-
-  if (!aStream->mAudioOutputs.IsEmpty()) {
-    for (StreamBuffer::TrackIter tracks(aStream->GetStreamBuffer(), MediaSegment::AUDIO);
-         !tracks.IsEnded(); tracks.Next()) {
-      uint32_t i;
-      for (i = 0; i < audioOutputStreamsFound.Length(); ++i) {
-        if (aStream->mAudioOutputStreams[i].mTrackID == tracks->GetID()) {
-          break;
-        }
-      }
-      if (i < audioOutputStreamsFound.Length()) {
-        audioOutputStreamsFound[i] = true;
-      } else {
-        // No output stream created for this track yet. Check if it's time to
-        // create one.
-        GraphTime startTime =
-          StreamTimeToGraphTime(aStream, tracks->GetStartTimeRoundDown(),
-                                INCLUDE_TRAILING_BLOCKED_INTERVAL);
-        if (startTime >= mStateComputedTime) {
-          // The stream wants to play audio, but nothing will play for the forseeable
-          // future, so don't create the stream.
-          continue;
-        }
-
-        // XXX allocating a AudioStream could be slow so we're going to have to do
-        // something here ... preallocation, async allocation, multiplexing onto a single
-        // stream ...
-        MediaStream::AudioOutputStream* audioOutputStream =
-          aStream->mAudioOutputStreams.AppendElement();
-        audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
-        audioOutputStream->mBlockedAudioTime = 0;
-        audioOutputStream->mStream = AudioStream::AllocateStream();
-        // XXX for now, allocate stereo output. But we need to fix this to
-        // match the system's ideal channel configuration.
-        audioOutputStream->mStream->Init(2, tracks->GetRate(), AUDIO_CHANNEL_NORMAL);
-        audioOutputStream->mTrackID = tracks->GetID();
-      }
-    }
-  }
-
-  for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) {
-    if (!audioOutputStreamsFound[i]) {
-      aStream->mAudioOutputStreams[i].mStream->Shutdown();
-      aStream->mAudioOutputStreams.RemoveElementAt(i);
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::PlayAudio(MediaStream* aStream,
-                                GraphTime aFrom, GraphTime aTo)
-{
-  if (aStream->mAudioOutputStreams.IsEmpty()) {
-    return;
-  }
-
-  // When we're playing multiple copies of this stream at the same time, they're
-  // perfectly correlated so adding volumes is the right thing to do.
-  float volume = 0.0f;
-  for (uint32_t i = 0; i < aStream->mAudioOutputs.Length(); ++i) {
-    volume += aStream->mAudioOutputs[i].mVolume;
-  }
-
-  for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) {
-    MediaStream::AudioOutputStream& audioOutput = aStream->mAudioOutputStreams[i];
-    StreamBuffer::Track* track = aStream->mBuffer.FindTrack(audioOutput.mTrackID);
-    AudioSegment* audio = track->Get<AudioSegment>();
-
-    // We don't update aStream->mBufferStartTime here to account for
-    // time spent blocked. Instead, we'll update it in UpdateCurrentTime after the
-    // blocked period has completed. But we do need to make sure we play from the
-    // right offsets in the stream buffer, even if we've already written silence for
-    // some amount of blocked time after the current time.
-    GraphTime t = aFrom;
-    while (t < aTo) {
-      GraphTime end;
-      bool blocked = aStream->mBlocked.GetAt(t, &end);
-      end = std::min(end, aTo);
-
-      AudioSegment output;
-      if (blocked) {
-        // Track total blocked time in aStream->mBlockedAudioTime so that
-        // the amount of silent samples we've inserted for blocking never gets
-        // more than one sample away from the ideal amount.
-        TrackTicks startTicks =
-            TimeToTicksRoundDown(track->GetRate(), audioOutput.mBlockedAudioTime);
-        audioOutput.mBlockedAudioTime += end - t;
-        TrackTicks endTicks =
-            TimeToTicksRoundDown(track->GetRate(), audioOutput.mBlockedAudioTime);
-
-        output.InsertNullDataAtStart(endTicks - startTicks);
-        LOG(PR_LOG_DEBUG, ("MediaStream %p writing blocking-silence samples for %f to %f",
-                         aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end)));
-      } else {
-        TrackTicks startTicks =
-            track->TimeToTicksRoundDown(GraphTimeToStreamTime(aStream, t));
-        TrackTicks endTicks =
-            track->TimeToTicksRoundDown(GraphTimeToStreamTime(aStream, end));
-
-        // If startTicks is before the track start, then that part of 'audio'
-        // will just be silence, which is fine here. But if endTicks is after
-        // the track end, then 'audio' won't be long enough, so we'll need
-        // to explicitly play silence.
-        TrackTicks sliceEnd = std::min(endTicks, audio->GetDuration());
-        if (sliceEnd > startTicks) {
-          output.AppendSlice(*audio, startTicks, sliceEnd);
-        }
-        // Play silence where the track has ended
-        output.AppendNullData(endTicks - sliceEnd);
-        NS_ASSERTION(endTicks == sliceEnd || track->IsEnded(),
-                     "Ran out of data but track not ended?");
-        output.ApplyVolume(volume);
-        LOG(PR_LOG_DEBUG, ("MediaStream %p writing samples for %f to %f (samples %lld to %lld)",
-                           aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end),
-                           startTicks, endTicks));
-      }
-      output.WriteTo(audioOutput.mStream);
-      t = end;
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::PlayVideo(MediaStream* aStream)
-{
-  if (aStream->mVideoOutputs.IsEmpty())
-    return;
-
-  // Display the next frame a bit early. This is better than letting the current
-  // frame be displayed for too long.
-  GraphTime framePosition = mCurrentTime + MEDIA_GRAPH_TARGET_PERIOD_MS;
-  NS_ASSERTION(framePosition >= aStream->mBufferStartTime, "frame position before buffer?");
-  StreamTime frameBufferTime = GraphTimeToStreamTime(aStream, framePosition);
-
-  TrackTicks start;
-  const VideoFrame* frame = nullptr;
-  StreamBuffer::Track* track;
-  for (StreamBuffer::TrackIter tracks(aStream->GetStreamBuffer(), MediaSegment::VIDEO);
-       !tracks.IsEnded(); tracks.Next()) {
-    VideoSegment* segment = tracks->Get<VideoSegment>();
-    TrackTicks thisStart;
-    const VideoFrame* thisFrame =
-      segment->GetFrameAt(tracks->TimeToTicksRoundDown(frameBufferTime), &thisStart);
-    if (thisFrame && thisFrame->GetImage()) {
-      start = thisStart;
-      frame = thisFrame;
-      track = tracks.get();
-    }
-  }
-  if (!frame || *frame == aStream->mLastPlayedVideoFrame)
-    return;
-
-  LOG(PR_LOG_DEBUG, ("MediaStream %p writing video frame %p (%dx%d)",
-                     aStream, frame->GetImage(), frame->GetIntrinsicSize().width,
-                     frame->GetIntrinsicSize().height));
-  GraphTime startTime = StreamTimeToGraphTime(aStream,
-      track->TicksToTimeRoundDown(start), INCLUDE_TRAILING_BLOCKED_INTERVAL);
-  TimeStamp targetTime = mCurrentTimeStamp +
-      TimeDuration::FromMilliseconds(double(startTime - mCurrentTime));
-  for (uint32_t i = 0; i < aStream->mVideoOutputs.Length(); ++i) {
-    VideoFrameContainer* output = aStream->mVideoOutputs[i];
-    output->SetCurrentFrame(frame->GetIntrinsicSize(), frame->GetImage(),
-                            targetTime);
-    nsCOMPtr<nsIRunnable> event =
-      NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate);
-    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-  }
-  if (!aStream->mNotifiedFinished) {
-    aStream->mLastPlayedVideoFrame = *frame;
-  }
-}
-
-void
-MediaStreamGraphImpl::PrepareUpdatesToMainThreadState()
-{
-  mMonitor.AssertCurrentThreadOwns();
-
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    MediaStream* stream = mStreams[i];
-    StreamUpdate* update = mStreamUpdates.AppendElement();
-    update->mGraphUpdateIndex = stream->mGraphUpdateIndices.GetAt(mCurrentTime);
-    update->mStream = stream;
-    update->mNextMainThreadCurrentTime =
-      GraphTimeToStreamTime(stream, mCurrentTime);
-    update->mNextMainThreadFinished =
-      stream->mFinished &&
-      StreamTimeToGraphTime(stream, stream->GetBufferEnd()) <= mCurrentTime;
-  }
-  mUpdateRunnables.MoveElementsFrom(mPendingUpdateRunnables);
-
-  EnsureStableStateEventPosted();
-}
-
-void
-MediaStreamGraphImpl::EnsureImmediateWakeUpLocked(MonitorAutoLock& aLock)
-{
-  if (mWaitState == WAITSTATE_WAITING_FOR_NEXT_ITERATION ||
-      mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
-    mWaitState = WAITSTATE_WAKING_UP;
-    aLock.Notify();
-  }
-}
-
-void
-MediaStreamGraphImpl::EnsureNextIteration()
-{
-  MonitorAutoLock lock(mMonitor);
-  EnsureNextIterationLocked(lock);
-}
-
-void
-MediaStreamGraphImpl::EnsureNextIterationLocked(MonitorAutoLock& aLock)
-{
-  if (mNeedAnotherIteration)
-    return;
-  mNeedAnotherIteration = true;
-  if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) {
-    mWaitState = WAITSTATE_WAKING_UP;
-    aLock.Notify();
-  }
 }
 
-void
-MediaStreamGraphImpl::RunThread()
-{
-  nsTArray<MessageBlock> messageQueue;
-  {
-    MonitorAutoLock lock(mMonitor);
-    messageQueue.SwapElements(mMessageQueue);
-  }
-  NS_ASSERTION(!messageQueue.IsEmpty(),
-               "Shouldn't have started a graph with empty message queue!");
-
-  for (;;) {
-    // Update mCurrentTime to the min of the playing audio times, or using the
-    // wall-clock time change if no audio is playing.
-    UpdateCurrentTime();
-
-    // Calculate independent action times for each batch of messages (each
-    // batch corresponding to an event loop task). This isolates the performance
-    // of different scripts to some extent.
-    for (uint32_t i = 0; i < messageQueue.Length(); ++i) {
-      mProcessingGraphUpdateIndex = messageQueue[i].mGraphUpdateIndex;
-      nsTArray<nsAutoPtr<ControlMessage> >& messages = messageQueue[i].mMessages;
-
-      for (uint32_t j = 0; j < messages.Length(); ++j) {
-        messages[j]->Run();
-      }
-    }
-    messageQueue.Clear();
-
-    UpdateStreamOrder();
-
-    int32_t writeAudioUpTo = AUDIO_TARGET_MS;
-    GraphTime endBlockingDecisions =
-      mCurrentTime + MillisecondsToMediaTime(writeAudioUpTo);
-    bool ensureNextIteration = false;
-
-    // Grab pending stream input.
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      SourceMediaStream* is = mStreams[i]->AsSourceStream();
-      if (is) {
-        UpdateConsumptionState(is);
-        ExtractPendingInput(is, endBlockingDecisions, &ensureNextIteration);
-      }
-    }
-
-    // Figure out which streams are blocked and when.
-    GraphTime prevComputedTime = mStateComputedTime;
-    RecomputeBlocking(endBlockingDecisions);
-
-    // Play stream contents.
-    uint32_t audioStreamsActive = 0;
-    bool allBlockedForever = true;
-    // Figure out what each stream wants to do
-    for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-      MediaStream* stream = mStreams[i];
-      ProcessedMediaStream* ps = stream->AsProcessedStream();
-      if (ps && !ps->mFinished) {
-        ps->ProduceOutput(prevComputedTime, mStateComputedTime);
-        NS_ASSERTION(stream->mBuffer.GetEnd() >=
-                     GraphTimeToStreamTime(stream, mStateComputedTime),
-                     "Stream did not produce enough data");
-      }
-      NotifyHasCurrentData(stream);
-      CreateOrDestroyAudioStreams(prevComputedTime, stream);
-      PlayAudio(stream, prevComputedTime, mStateComputedTime);
-      audioStreamsActive += stream->mAudioOutputStreams.Length();
-      PlayVideo(stream);
-      SourceMediaStream* is = stream->AsSourceStream();
-      if (is) {
-        UpdateBufferSufficiencyState(is);
-      }
-      GraphTime end;
-      if (!stream->mBlocked.GetAt(mCurrentTime, &end) || end < GRAPH_TIME_MAX) {
-        allBlockedForever = false;
-      }
-    }
-    if (ensureNextIteration || !allBlockedForever || audioStreamsActive > 0) {
-      EnsureNextIteration();
-    }
-
-    // Send updates to the main thread and wait for the next control loop
-    // iteration.
-    {
-      // Not using MonitorAutoLock since we need to unlock in a way
-      // that doesn't match lexical scopes.
-      MonitorAutoLock lock(mMonitor);
-      PrepareUpdatesToMainThreadState();
-      if (mForceShutDown || (IsEmpty() && mMessageQueue.IsEmpty())) {
-        // Enter shutdown mode. The stable-state handler will detect this
-        // and complete shutdown. Destroy any streams immediately.
-        LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p waiting for main thread cleanup", this));
-        // Commit to shutting down this graph object.
-        mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP;
-        // No need to Destroy streams here. The main-thread owner of each
-        // stream is responsible for calling Destroy them.
-        return;
-      }
-
-      PRIntervalTime timeout = PR_INTERVAL_NO_TIMEOUT;
-      TimeStamp now = TimeStamp::Now();
-      if (mNeedAnotherIteration) {
-        int64_t timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS -
-          int64_t((now - mCurrentTimeStamp).ToMilliseconds());
-        // Make sure timeoutMS doesn't overflow 32 bits by waking up at
-        // least once a minute, if we need to wake up at all
-        timeoutMS = std::max<int64_t>(0, std::min<int64_t>(timeoutMS, 60*1000));
-        timeout = PR_MillisecondsToInterval(uint32_t(timeoutMS));
-        LOG(PR_LOG_DEBUG, ("Waiting for next iteration; at %f, timeout=%f",
-                           (now - mInitialTimeStamp).ToSeconds(), timeoutMS/1000.0));
-        mWaitState = WAITSTATE_WAITING_FOR_NEXT_ITERATION;
-      } else {
-        mWaitState = WAITSTATE_WAITING_INDEFINITELY;
-      }
-      if (timeout > 0) {
-        mMonitor.Wait(timeout);
-        LOG(PR_LOG_DEBUG, ("Resuming after timeout; at %f, elapsed=%f",
-                           (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(),
-                           (TimeStamp::Now() - now).ToSeconds()));
-      }
-      mWaitState = WAITSTATE_RUNNING;
-      mNeedAnotherIteration = false;
-      messageQueue.SwapElements(mMessageQueue);
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate)
-{
-  mMonitor.AssertCurrentThreadOwns();
-
-  MediaStream* stream = aUpdate->mStream;
-  if (!stream)
-    return;
-  stream->mMainThreadCurrentTime = aUpdate->mNextMainThreadCurrentTime;
-  stream->mMainThreadFinished = aUpdate->mNextMainThreadFinished;
-
-  for (int32_t i = stream->mMainThreadListeners.Length() - 1; i >= 0; --i) {
-    stream->mMainThreadListeners[i]->NotifyMainThreadStateChanged();
-  }
-}
-
-void
-MediaStreamGraphImpl::ShutdownThreads()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
-  // mGraph's thread is not running so it's OK to do whatever here
-  LOG(PR_LOG_DEBUG, ("Stopping threads for MediaStreamGraph %p", this));
-
-  if (mThread) {
-    mThread->Shutdown();
-    mThread = nullptr;
-  }
-}
-
-void
-MediaStreamGraphImpl::ForceShutDown()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
-  LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p ForceShutdown", this));
-  {
-    MonitorAutoLock lock(mMonitor);
-    mForceShutDown = true;
-    EnsureImmediateWakeUpLocked(lock);
-  }
-}
-
-namespace {
-
-class MediaStreamGraphThreadRunnable : public nsRunnable {
-public:
-  NS_IMETHOD Run()
-  {
-    gGraph->RunThread();
-    return NS_OK;
-  }
-};
-
-class MediaStreamGraphShutDownRunnable : public nsRunnable {
-public:
-  MediaStreamGraphShutDownRunnable(MediaStreamGraphImpl* aGraph) : mGraph(aGraph) {}
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(mGraph->mDetectedNotRunning,
-                 "We should know the graph thread control loop isn't running!");
-    // mGraph's thread is not running so it's OK to do whatever here
-    if (mGraph->IsEmpty()) {
-      // mGraph is no longer needed, so delete it. If the graph is not empty
-      // then we must be in a forced shutdown and some later AppendMessage will
-      // detect that the manager has been emptied, and delete it.
-      delete mGraph;
-    } else {
-      NS_ASSERTION(mGraph->mForceShutDown, "Not in forced shutdown?");
-      mGraph->mLifecycleState =
-        MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION;
-    }
-    return NS_OK;
-  }
-private:
-  MediaStreamGraphImpl* mGraph;
-};
-
-class MediaStreamGraphStableStateRunnable : public nsRunnable {
-public:
-  NS_IMETHOD Run()
-  {
-    if (gGraph) {
-      gGraph->RunInStableState();
-    }
-    return NS_OK;
-  }
-};
-
-/*
- * Control messages forwarded from main thread to graph manager thread
- */
-class CreateMessage : public ControlMessage {
-public:
-  CreateMessage(MediaStream* aStream) : ControlMessage(aStream) {}
-  virtual void Run()
-  {
-    mStream->GraphImpl()->AddStream(mStream);
-    mStream->Init();
-  }
-};
-
-class MediaStreamGraphShutdownObserver MOZ_FINAL : public nsIObserver
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-};
-
-}
-
-void
-MediaStreamGraphImpl::RunInStableState()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
-
-  nsTArray<nsCOMPtr<nsIRunnable> > runnables;
-  // When we're doing a forced shutdown, pending control messages may be
-  // run on the main thread via RunDuringShutdown. Those messages must
-  // run without the graph monitor being held. So, we collect them here.
-  nsTArray<nsAutoPtr<ControlMessage> > controlMessagesToRunDuringShutdown;
-
-  {
-    MonitorAutoLock lock(mMonitor);
-    mPostedRunInStableStateEvent = false;
-
-    runnables.SwapElements(mUpdateRunnables);
-    for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) {
-      StreamUpdate* update = &mStreamUpdates[i];
-      if (update->mStream) {
-        ApplyStreamUpdate(update);
-      }
-    }
-    mStreamUpdates.Clear();
-
-    if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && mForceShutDown) {
-      // Defer calls to RunDuringShutdown() to happen while mMonitor is not held.
-      for (uint32_t i = 0; i < mMessageQueue.Length(); ++i) {
-        MessageBlock& mb = mMessageQueue[i];
-        controlMessagesToRunDuringShutdown.MoveElementsFrom(mb.mMessages);
-      }
-      mMessageQueue.Clear();
-      controlMessagesToRunDuringShutdown.MoveElementsFrom(mCurrentTaskMessageQueue);
-      // Stop MediaStreamGraph threads. Do not clear gGraph since
-      // we have outstanding DOM objects that may need it.
-      mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
-      nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this);
-      NS_DispatchToMainThread(event);
-    }
-
-    if (mLifecycleState == LIFECYCLE_THREAD_NOT_STARTED) {
-      mLifecycleState = LIFECYCLE_RUNNING;
-      // Start the thread now. We couldn't start it earlier because
-      // the graph might exit immediately on finding it has no streams. The
-      // first message for a new graph must create a stream.
-      nsCOMPtr<nsIRunnable> event = new MediaStreamGraphThreadRunnable();
-      NS_NewThread(getter_AddRefs(mThread), event);
-    }
-
-    if (mCurrentTaskMessageQueue.IsEmpty()) {
-      if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && IsEmpty()) {
-        NS_ASSERTION(gGraph == this, "Not current graph??");
-        // Complete shutdown. First, ensure that this graph is no longer used.
-        // A new graph graph will be created if one is needed.
-        LOG(PR_LOG_DEBUG, ("Disconnecting MediaStreamGraph %p", gGraph));
-        gGraph = nullptr;
-        // Asynchronously clean up old graph. We don't want to do this
-        // synchronously because it spins the event loop waiting for threads
-        // to shut down, and we don't want to do that in a stable state handler.
-        mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
-        nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this);
-        NS_DispatchToMainThread(event);
-      }
-    } else {
-      if (mLifecycleState <= LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
-        MessageBlock* block = mMessageQueue.AppendElement();
-        block->mMessages.SwapElements(mCurrentTaskMessageQueue);
-        block->mGraphUpdateIndex = mGraphUpdatesSent;
-        ++mGraphUpdatesSent;
-        EnsureNextIterationLocked(lock);
-      }
-
-      if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
-        mLifecycleState = LIFECYCLE_RUNNING;
-        // Revive the MediaStreamGraph since we have more messages going to it.
-        // Note that we need to put messages into its queue before reviving it,
-        // or it might exit immediately.
-        nsCOMPtr<nsIRunnable> event = new MediaStreamGraphThreadRunnable();
-        mThread->Dispatch(event, 0);
-      }
-    }
-
-    mDetectedNotRunning = mLifecycleState > LIFECYCLE_RUNNING;
-  }
-
-  // Make sure we get a new current time in the next event loop task
-  mPostedRunInStableState = false;
-
-  for (uint32_t i = 0; i < runnables.Length(); ++i) {
-    runnables[i]->Run();
-  }
-  for (uint32_t i = 0; i < controlMessagesToRunDuringShutdown.Length(); ++i) {
-    controlMessagesToRunDuringShutdown[i]->RunDuringShutdown();
-  }
-}
-
-static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
-
-void
-MediaStreamGraphImpl::EnsureRunInStableState()
-{
-  NS_ASSERTION(NS_IsMainThread(), "main thread only");
-
-  if (mPostedRunInStableState)
-    return;
-  mPostedRunInStableState = true;
-  nsCOMPtr<nsIRunnable> event = new MediaStreamGraphStableStateRunnable();
-  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
-  if (appShell) {
-    appShell->RunInStableState(event);
-  } else {
-    NS_ERROR("Appshell already destroyed?");
-  }
-}
-
-void
-MediaStreamGraphImpl::EnsureStableStateEventPosted()
-{
-  mMonitor.AssertCurrentThreadOwns();
-
-  if (mPostedRunInStableStateEvent)
-    return;
-  mPostedRunInStableStateEvent = true;
-  nsCOMPtr<nsIRunnable> event = new MediaStreamGraphStableStateRunnable();
-  NS_DispatchToMainThread(event);
-}
-
-void
-MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage)
-{
-  NS_ASSERTION(NS_IsMainThread(), "main thread only");
-  NS_ASSERTION(!aMessage->GetStream() ||
-               !aMessage->GetStream()->IsDestroyed(),
-               "Stream already destroyed");
-
-  if (mDetectedNotRunning &&
-      mLifecycleState > LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
-    // The graph control loop is not running and main thread cleanup has
-    // happened. From now on we can't append messages to mCurrentTaskMessageQueue,
-    // because that will never be processed again, so just RunDuringShutdown
-    // this message.
-    // This should only happen during forced shutdown.
-    aMessage->RunDuringShutdown();
-    delete aMessage;
-    if (IsEmpty()) {
-      NS_ASSERTION(gGraph == this, "Switched managers during forced shutdown?");
-      gGraph = nullptr;
-      delete this;
-    }
-    return;
-  }
-
-  mCurrentTaskMessageQueue.AppendElement(aMessage);
-  EnsureRunInStableState();
-}
-
-void
-MediaStream::Init()
-{
-  MediaStreamGraphImpl* graph = GraphImpl();
-  mBlocked.SetAtAndAfter(graph->mCurrentTime, true);
-  mExplicitBlockerCount.SetAtAndAfter(graph->mCurrentTime, true);
-  mExplicitBlockerCount.SetAtAndAfter(graph->mStateComputedTime, false);
-}
-
-MediaStreamGraphImpl*
-MediaStream::GraphImpl()
-{
-  return gGraph;
-}
-
-MediaStreamGraph*
-MediaStream::Graph()
-{
-  return gGraph;
-}
-
-StreamTime
-MediaStream::GraphTimeToStreamTime(GraphTime aTime)
-{
-  return GraphImpl()->GraphTimeToStreamTime(this, aTime);
-}
-
-void
-MediaStream::FinishOnGraphThread()
-{
-  GraphImpl()->FinishStream(this);
-}
-
-void
-MediaStream::RemoveAllListenersImpl()
-{
-  for (int32_t i = mListeners.Length() - 1; i >= 0; --i) {
-    nsRefPtr<MediaStreamListener> listener = mListeners[i].forget();
-    listener->NotifyRemoved(GraphImpl());
-  }
-  mListeners.Clear();
-}
-
-void
-MediaStream::DestroyImpl()
-{
-  RemoveAllListenersImpl();
-
-  for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
-    mConsumers[i]->Disconnect();
-  }
-  for (uint32_t i = 0; i < mAudioOutputStreams.Length(); ++i) {
-    mAudioOutputStreams[i].mStream->Shutdown();
-  }
-  mAudioOutputStreams.Clear();
-}
-
-void
-MediaStream::Destroy()
-{
-  // Keep this stream alive until we leave this method
-  nsRefPtr<MediaStream> kungFuDeathGrip = this;
-
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream) : ControlMessage(aStream) {}
-    virtual void Run()
-    {
-      mStream->DestroyImpl();
-      mStream->GraphImpl()->RemoveStream(mStream);
-    }
-    virtual void RunDuringShutdown()
-    { Run(); }
-  };
-  mWrapper = nullptr;
-  GraphImpl()->AppendMessage(new Message(this));
-  // Message::RunDuringShutdown may have removed this stream from the graph,
-  // but our kungFuDeathGrip above will have kept this stream alive if
-  // necessary.
-  mMainThreadDestroyed = true;
-}
-
-void
-MediaStream::AddAudioOutput(void* aKey)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {}
-    virtual void Run()
-    {
-      mStream->AddAudioOutputImpl(mKey);
-    }
-    void* mKey;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aKey));
-}
-
-void
-MediaStream::SetAudioOutputVolumeImpl(void* aKey, float aVolume)
-{
-  for (uint32_t i = 0; i < mAudioOutputs.Length(); ++i) {
-    if (mAudioOutputs[i].mKey == aKey) {
-      mAudioOutputs[i].mVolume = aVolume;
-      return;
-    }
-  }
-  NS_ERROR("Audio output key not found");
-}
-
-void
-MediaStream::SetAudioOutputVolume(void* aKey, float aVolume)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, void* aKey, float aVolume) :
-      ControlMessage(aStream), mKey(aKey), mVolume(aVolume) {}
-    virtual void Run()
-    {
-      mStream->SetAudioOutputVolumeImpl(mKey, mVolume);
-    }
-    void* mKey;
-    float mVolume;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aKey, aVolume));
-}
-
-void
-MediaStream::RemoveAudioOutputImpl(void* aKey)
-{
-  for (uint32_t i = 0; i < mAudioOutputs.Length(); ++i) {
-    if (mAudioOutputs[i].mKey == aKey) {
-      mAudioOutputs.RemoveElementAt(i);
-      return;
-    }
-  }
-  NS_ERROR("Audio output key not found");
-}
-
-void
-MediaStream::RemoveAudioOutput(void* aKey)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, void* aKey) :
-      ControlMessage(aStream), mKey(aKey) {}
-    virtual void Run()
-    {
-      mStream->RemoveAudioOutputImpl(mKey);
-    }
-    void* mKey;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aKey));
-}
-
-void
-MediaStream::AddVideoOutput(VideoFrameContainer* aContainer)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
-      ControlMessage(aStream), mContainer(aContainer) {}
-    virtual void Run()
-    {
-      mStream->AddVideoOutputImpl(mContainer.forget());
-    }
-    nsRefPtr<VideoFrameContainer> mContainer;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aContainer));
-}
-
-void
-MediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
-      ControlMessage(aStream), mContainer(aContainer) {}
-    virtual void Run()
-    {
-      mStream->RemoveVideoOutputImpl(mContainer);
-    }
-    nsRefPtr<VideoFrameContainer> mContainer;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aContainer));
-}
-
-void
-MediaStream::ChangeExplicitBlockerCount(int32_t aDelta)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, int32_t aDelta) :
-      ControlMessage(aStream), mDelta(aDelta) {}
-    virtual void Run()
-    {
-      mStream->ChangeExplicitBlockerCountImpl(
-          mStream->GraphImpl()->mStateComputedTime, mDelta);
-    }
-    int32_t mDelta;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aDelta));
-}
-
-void
-MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
-{
-  MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
-  listener->NotifyBlockingChanged(GraphImpl(),
-    mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED);
-  if (mNotifiedFinished) {
-    listener->NotifyFinished(GraphImpl());
-  }
-}
-
-void
-MediaStream::AddListener(MediaStreamListener* aListener)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, MediaStreamListener* aListener) :
-      ControlMessage(aStream), mListener(aListener) {}
-    virtual void Run()
-    {
-      mStream->AddListenerImpl(mListener.forget());
-    }
-    nsRefPtr<MediaStreamListener> mListener;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aListener));
-}
-
-void
-MediaStream::RemoveListenerImpl(MediaStreamListener* aListener)
-{ 
-  // wouldn't need this if we could do it in the opposite order
-  nsRefPtr<MediaStreamListener> listener(aListener);
-  mListeners.RemoveElement(aListener);
-  listener->NotifyRemoved(GraphImpl());
-}
-
-void
-MediaStream::RemoveListener(MediaStreamListener* aListener)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaStream* aStream, MediaStreamListener* aListener) :
-      ControlMessage(aStream), mListener(aListener) {}
-    virtual void Run()
-    {
-      mStream->RemoveListenerImpl(mListener);
-    }
-    nsRefPtr<MediaStreamListener> mListener;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aListener));
-}
-
-void
-SourceMediaStream::DestroyImpl()
-{
-  {
-    MutexAutoLock lock(mMutex);
-    mDestroyed = true;
-  }
-  MediaStream::DestroyImpl();
-}
-
-void
-SourceMediaStream::SetPullEnabled(bool aEnabled)
-{
-  MutexAutoLock lock(mMutex);
-  mPullEnabled = aEnabled;
-  if (mPullEnabled && !mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-void
-SourceMediaStream::AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart,
-                            MediaSegment* aSegment)
-{
-  MutexAutoLock lock(mMutex);
-  TrackData* data = mUpdateTracks.AppendElement();
-  data->mID = aID;
-  data->mRate = aRate;
-  data->mStart = aStart;
-  data->mCommands = TRACK_CREATE;
-  data->mData = aSegment;
-  data->mHaveEnough = false;
-  if (!mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-void
-SourceMediaStream::AppendToTrack(TrackID aID, MediaSegment* aSegment)
-{
-  MutexAutoLock lock(mMutex);
-  // ::EndAllTrackAndFinished() can end these before the sources notice
-  if (!mFinished) {
-    TrackData *track = FindDataForTrack(aID);
-    if (track) {
-      track->mData->AppendFrom(aSegment);
-    } else {
-      NS_ERROR("Append to non-existent track!");
-    }
-  }
-  if (!mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-bool
-SourceMediaStream::HaveEnoughBuffered(TrackID aID)
-{
-  MutexAutoLock lock(mMutex);
-  TrackData *track = FindDataForTrack(aID);
-  if (track) {
-    return track->mHaveEnough;
-  }
-  NS_ERROR("No track in HaveEnoughBuffered!");
-  return true;
-}
-
-void
-SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID,
-    nsIThread* aSignalThread, nsIRunnable* aSignalRunnable)
-{
-  MutexAutoLock lock(mMutex);
-  TrackData* data = FindDataForTrack(aID);
-  if (!data) {
-    NS_ERROR("No track in DispatchWhenNotEnoughBuffered");
-    return;
-  }
-
-  if (data->mHaveEnough) {
-    data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalThread, aSignalRunnable);
-  } else {
-    aSignalThread->Dispatch(aSignalRunnable, 0);
-  }
-}
-
-void
-SourceMediaStream::EndTrack(TrackID aID)
-{
-  MutexAutoLock lock(mMutex);
-  // ::EndAllTrackAndFinished() can end these before the sources call this
-  if (!mFinished) {
-    TrackData *track = FindDataForTrack(aID);
-    if (track) {
-      track->mCommands |= TRACK_END;
-    } else {
-      NS_ERROR("End of non-existant track");
-    }
-  }
-  if (!mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-void
-SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime)
-{
-  MutexAutoLock lock(mMutex);
-  mUpdateKnownTracksTime = aKnownTime;
-  if (!mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-void
-SourceMediaStream::FinishWithLockHeld()
-{
-  mUpdateFinished = true;
-  if (!mDestroyed) {
-    GraphImpl()->EnsureNextIteration();
-  }
-}
-
-void
-SourceMediaStream::EndAllTrackAndFinish()
-{
-  {
-    MutexAutoLock lock(mMutex);
-    for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) {
-      SourceMediaStream::TrackData* data = &mUpdateTracks[i];
-      data->mCommands |= TRACK_END;
-    }
-  }
-  FinishWithLockHeld();
-  // we will call NotifyFinished() to let GetUserMedia know
-}
-
-void
-MediaInputPort::Init()
-{
-  LOG(PR_LOG_DEBUG, ("Adding MediaInputPort %p (from %p to %p) to the graph",
-      this, mSource, mDest));
-  mSource->AddConsumer(this);
-  mDest->AddInput(this);
-  // mPortCount decremented via MediaInputPort::Destroy's message
-  ++mDest->GraphImpl()->mPortCount;
-}
-
-void
-MediaInputPort::Disconnect()
-{
-  NS_ASSERTION(!mSource == !mDest,
-               "mSource must either both be null or both non-null");
-  if (!mSource)
-    return;
-
-  mSource->RemoveConsumer(this);
-  mSource = nullptr;
-  mDest->RemoveInput(this);
-  mDest = nullptr;
-}
-
-MediaInputPort::InputInterval
-MediaInputPort::GetNextInputInterval(GraphTime aTime)
-{
-  InputInterval result = { GRAPH_TIME_MAX, GRAPH_TIME_MAX, false };
-  GraphTime t = aTime;
-  GraphTime end;
-  for (;;) {
-    if (!mDest->mBlocked.GetAt(t, &end))
-      break;
-    if (end == GRAPH_TIME_MAX)
-      return result;
-    t = end;
-  }
-  result.mStart = t;
-  GraphTime sourceEnd;
-  result.mInputIsBlocked = mSource->mBlocked.GetAt(t, &sourceEnd);
-  result.mEnd = std::min(end, sourceEnd);
-  return result;
-}
-
-void
-MediaInputPort::Destroy()
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaInputPort* aPort)
-      : ControlMessage(nullptr), mPort(aPort) {}
-    virtual void Run()
-    {
-      mPort->Disconnect();
-      --mPort->GraphImpl()->mPortCount;
-      NS_RELEASE(mPort);
-    }
-    virtual void RunDuringShutdown()
-    {
-      Run();
-    }
-    // This does not need to be strongly referenced; the graph is holding
-    // a strong reference to the port, which we will remove. This will be the
-    // last message for the port.
-    MediaInputPort* mPort;
-  };
-  GraphImpl()->AppendMessage(new Message(this));
-}
-
-MediaStreamGraphImpl*
-MediaInputPort::GraphImpl()
-{
-  return gGraph;
-}
-
-MediaStreamGraph*
-MediaInputPort::Graph()
-{
-  return gGraph;
-}
-
-MediaInputPort*
-ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(MediaInputPort* aPort)
-      : ControlMessage(aPort->GetDestination()),
-        mPort(aPort) {}
-    virtual void Run()
-    {
-      mPort->Init();
-    }
-    MediaInputPort* mPort;
-  };
-  MediaInputPort* port = new MediaInputPort(aStream, this, aFlags);
-  NS_ADDREF(port);
-  GraphImpl()->AppendMessage(new Message(port));
-  return port;
-}
-
-void
-ProcessedMediaStream::Finish()
-{
-  class Message : public ControlMessage {
-  public:
-    Message(ProcessedMediaStream* aStream)
-      : ControlMessage(aStream) {}
-    virtual void Run()
-    {
-      mStream->GraphImpl()->FinishStream(mStream);
-    }
-  };
-  GraphImpl()->AppendMessage(new Message(this));
-}
-
-void
-ProcessedMediaStream::SetAutofinish(bool aAutofinish)
-{
-  class Message : public ControlMessage {
-  public:
-    Message(ProcessedMediaStream* aStream, bool aAutofinish)
-      : ControlMessage(aStream), mAutofinish(aAutofinish) {}
-    virtual void Run()
-    {
-      mStream->AsProcessedStream()->SetAutofinishImpl(mAutofinish);
-    }
-    bool mAutofinish;
-  };
-  GraphImpl()->AppendMessage(new Message(this, aAutofinish));
-}
-
-void
-ProcessedMediaStream::DestroyImpl()
-{
-  for (int32_t i = mInputs.Length() - 1; i >= 0; --i) {
-    mInputs[i]->Disconnect();
-  }
-  MediaStream::DestroyImpl();
-}
-
-/**
- * We make the initial mCurrentTime nonzero so that zero times can have
- * special meaning if necessary.
- */
-static const int32_t INITIAL_CURRENT_TIME = 1;
-
-MediaStreamGraphImpl::MediaStreamGraphImpl()
-  : mCurrentTime(INITIAL_CURRENT_TIME)
-  , mStateComputedTime(INITIAL_CURRENT_TIME)
-  , mProcessingGraphUpdateIndex(0)
-  , mPortCount(0)
-  , mMonitor("MediaStreamGraphImpl")
-  , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
-  , mWaitState(WAITSTATE_RUNNING)
-  , mNeedAnotherIteration(false)
-  , mForceShutDown(false)
-  , mPostedRunInStableStateEvent(false)
-  , mDetectedNotRunning(false)
-  , mPostedRunInStableState(false)
-{
-#ifdef PR_LOGGING
-  if (!gMediaStreamGraphLog) {
-    gMediaStreamGraphLog = PR_NewLogModule("MediaStreamGraph");
-  }
-#endif
-
-  mCurrentTimeStamp = mInitialTimeStamp = TimeStamp::Now();
-}
-
-NS_IMPL_ISUPPORTS1(MediaStreamGraphShutdownObserver, nsIObserver)
-
-static bool gShutdownObserverRegistered = false;
-
-NS_IMETHODIMP
-MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject,
-                                          const char *aTopic,
-                                          const PRUnichar *aData)
-{
-  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
-    if (gGraph) {
-      gGraph->ForceShutDown();
-    }
-    nsContentUtils::UnregisterShutdownObserver(this);
-    gShutdownObserverRegistered = false;
-  }
-  return NS_OK;
-}
-
-MediaStreamGraph*
-MediaStreamGraph::GetInstance()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Main thread only");
-
-  if (!gGraph) {
-    if (!gShutdownObserverRegistered) {
-      gShutdownObserverRegistered = true;
-      nsContentUtils::RegisterShutdownObserver(new MediaStreamGraphShutdownObserver());
-    }
-
-    gGraph = new MediaStreamGraphImpl();
-    LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph));
-  }
-
-  return gGraph;
-}
-
-SourceMediaStream*
-MediaStreamGraph::CreateSourceStream(nsDOMMediaStream* aWrapper)
-{
-  SourceMediaStream* stream = new SourceMediaStream(aWrapper);
-  NS_ADDREF(stream);
-  static_cast<MediaStreamGraphImpl*>(this)->AppendMessage(new CreateMessage(stream));
-  return stream;
-}
-
-ProcessedMediaStream*
-MediaStreamGraph::CreateTrackUnionStream(nsDOMMediaStream* aWrapper)
-{
-  TrackUnionStream* stream = new TrackUnionStream(aWrapper);
-  NS_ADDREF(stream);
-  static_cast<MediaStreamGraphImpl*>(this)->AppendMessage(new CreateMessage(stream));
-  return stream;
-}
-
-}
+#endif /* MEDIASTREAMGRAPHIMPL_H_ */
--- a/content/media/StreamBuffer.h
+++ b/content/media/StreamBuffer.h
@@ -30,28 +30,28 @@ const TrackRate TRACK_RATE_MAX = 1 << ME
 /**
  * Unique ID for track within a StreamBuffer. Tracks from different
  * StreamBuffers may have the same ID; this matters when appending StreamBuffers,
  * since tracks with the same ID are matched. Only IDs greater than 0 are allowed.
  */
 typedef int32_t TrackID;
 const TrackID TRACK_NONE = 0;
 
-inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aMicroseconds)
+inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aTime)
 {
   NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate");
-  NS_ASSERTION(0 <= aMicroseconds && aMicroseconds <= STREAM_TIME_MAX, "Bad microseconds");
-  return (aMicroseconds*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS;
+  NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time");
+  return (aTime*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS;
 }
 
-inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aMicroseconds)
+inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aTime)
 {
   NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate");
-  NS_ASSERTION(0 <= aMicroseconds && aMicroseconds <= STREAM_TIME_MAX, "Bad microseconds");
-  return (aMicroseconds*aRate) >> MEDIA_TIME_FRAC_BITS;
+  NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time");
+  return (aTime*aRate) >> MEDIA_TIME_FRAC_BITS;
 }
 
 inline StreamTime TicksToTimeRoundUp(TrackRate aRate, TrackTicks aTicks)
 {
   NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate");
   NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples");
   return ((aTicks << MEDIA_TIME_FRAC_BITS) + aRate - 1)/aRate;
 }
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -5,48 +5,49 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioBuffer.h"
 #include "mozilla/dom/AudioBufferBinding.h"
 #include "nsContentUtils.h"
 #include "AudioContext.h"
 #include "jsfriendapi.h"
 #include "mozilla/ErrorResult.h"
+#include "AudioSegment.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannels)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSChannels)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   tmp->ClearJSChannels();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-  for (uint32_t i = 0; i < tmp->mChannels.Length(); ++i) {
-    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mChannels[i])
+  for (uint32_t i = 0; i < tmp->mJSChannels.Length(); ++i) {
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSChannels[i])
   }
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioBuffer)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioBuffer)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioBuffer)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aLength,
-                         uint32_t aSampleRate)
+                         float aSampleRate)
   : mContext(aContext),
     mLength(aLength),
     mSampleRate(aSampleRate)
 {
   SetIsDOMBinding();
 
   NS_HOLD_JS_OBJECTS(this, AudioBuffer);
 }
@@ -54,62 +55,154 @@ AudioBuffer::AudioBuffer(AudioContext* a
 AudioBuffer::~AudioBuffer()
 {
   ClearJSChannels();
 }
 
 void
 AudioBuffer::ClearJSChannels()
 {
-  mChannels.Clear();
+  mJSChannels.Clear();
   NS_DROP_JS_OBJECTS(this, AudioBuffer);
 }
 
 bool
 AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext)
 {
-  if (!mChannels.SetCapacity(aNumberOfChannels)) {
+  if (!mJSChannels.SetCapacity(aNumberOfChannels)) {
     return false;
   }
   for (uint32_t i = 0; i < aNumberOfChannels; ++i) {
     JSObject* array = JS_NewFloat32Array(aJSContext, mLength);
     if (!array) {
       return false;
     }
-    mChannels.AppendElement(array);
+    mJSChannels.AppendElement(array);
   }
 
   return true;
 }
 
 JSObject*
 AudioBuffer::WrapObject(JSContext* aCx, JSObject* aScope,
                         bool* aTriedToWrap)
 {
   return AudioBufferBinding::Wrap(aCx, aScope, this, aTriedToWrap);
 }
 
+void
+AudioBuffer::RestoreJSChannelData(JSContext* aJSContext)
+{
+  if (mSharedChannels) {
+    for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
+      const float* data = mSharedChannels->GetData(i);
+      // The following code first zeroes the array and then copies our data
+      // into it. We could avoid this with additional JS APIs to construct
+      // an array (or ArrayBuffer) containing initial data.
+      JSObject* array = JS_NewFloat32Array(aJSContext, mLength);
+      memcpy(JS_GetFloat32ArrayData(array), data, sizeof(float)*mLength);
+      mJSChannels[i] = array;
+    }
+
+    mSharedChannels = nullptr;
+    mResampledChannels = nullptr;
+  }
+}
+
 JSObject*
 AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
-                            ErrorResult& aRv) const
+                            ErrorResult& aRv)
 {
-  if (aChannel >= mChannels.Length()) {
+  if (aChannel >= NumberOfChannels()) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
-  return GetChannelData(aChannel);
+
+  RestoreJSChannelData(aJSContext);
+
+  return mJSChannels[aChannel];
 }
 
 void
 AudioBuffer::SetChannelDataFromArrayBufferContents(JSContext* aJSContext,
                                                    uint32_t aChannel,
                                                    void* aContents)
 {
-  MOZ_ASSERT(aChannel < mChannels.Length());
+  RestoreJSChannelData(aJSContext);
+
+  MOZ_ASSERT(aChannel < NumberOfChannels());
   JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aJSContext, aContents);
-  mChannels[aChannel] = JS_NewFloat32ArrayWithBuffer(aJSContext, arrayBuffer,
-                                                     0, -1);
-  MOZ_ASSERT(mLength == JS_GetTypedArrayLength(mChannels[aChannel]));
+  mJSChannels[aChannel] = JS_NewFloat32ArrayWithBuffer(aJSContext, arrayBuffer,
+                                                       0, -1);
+  MOZ_ASSERT(mLength == JS_GetTypedArrayLength(mJSChannels[aChannel]));
+}
+
+static already_AddRefed<ThreadSharedFloatArrayBufferList>
+StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext,
+                                                     const nsTArray<JSObject*>& aJSArrays)
+{
+  nsRefPtr<ThreadSharedFloatArrayBufferList> result =
+    new ThreadSharedFloatArrayBufferList(aJSArrays.Length());
+  for (uint32_t i = 0; i < aJSArrays.Length(); ++i) {
+    JSObject* arrayBuffer = JS_GetArrayBufferViewBuffer(aJSArrays[i]);
+    void* dataToFree = nullptr;
+    uint8_t* stolenData = nullptr;
+    if (arrayBuffer &&
+        JS_StealArrayBufferContents(aJSContext, arrayBuffer, &dataToFree,
+                                    &stolenData)) {
+      result->SetData(i, dataToFree, reinterpret_cast<float*>(stolenData));
+    } else {
+      result->Clear();
+      return result.forget();
+    }
+  }
+  return result.forget();
 }
 
+ThreadSharedFloatArrayBufferList*
+AudioBuffer::GetThreadSharedChannelsForRate(JSContext* aJSContext, uint32_t aRate,
+                                            uint32_t* aLength)
+{
+  if (mResampledChannels && mResampledChannelsRate == aRate) {
+    // return cached data
+    *aLength = mResampledChannelsLength;
+    return mResampledChannels;
+  }
+
+  if (!mSharedChannels) {
+    // Steal JS data
+    mSharedChannels =
+      StealJSArrayDataIntoThreadSharedFloatArrayBufferList(aJSContext, mJSChannels);
+  }
+
+  if (mSampleRate == aRate) {
+    *aLength = mLength;
+    return mSharedChannels;
+  }
+
+  mResampledChannels = new ThreadSharedFloatArrayBufferList(NumberOfChannels());
+
+  double newLengthD = ceil(Duration()*aRate);
+  uint32_t newLength = uint32_t(newLengthD);
+  *aLength = newLength;
+  double size = sizeof(float)*NumberOfChannels()*newLengthD;
+  if (size != uint32_t(size)) {
+    return mResampledChannels;
+  }
+  float* outputData = static_cast<float*>(malloc(uint32_t(size)));
+  if (!outputData) {
+    return mResampledChannels;
+  }
+
+  for (uint32_t i = 0; i < NumberOfChannels(); ++i) {
+    NS_ERROR("Resampling not supported yet");
+    // const float* inputData = mSharedChannels->GetData(i);
+    // Resample(inputData, mLength, mSampleRate, outputData, newLength, aRate);
+    mResampledChannels->SetData(i, i == 0 ? outputData : nullptr, outputData);
+    outputData += newLength;
+  }
+  mResampledChannelsRate = aRate;
+  mResampledChannelsLength = newLength;
+  return mResampledChannels;
+}
+ 
 }
 }
-
--- a/content/media/webaudio/AudioBuffer.h
+++ b/content/media/webaudio/AudioBuffer.h
@@ -9,33 +9,40 @@
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "EnableWebAudioCheck.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioContext.h"
+#include "AudioSegment.h"
+#include "AudioNodeEngine.h"
 
 struct JSContext;
 class JSObject;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
+/**
+ * An AudioBuffer keeps its data either in the mJSChannels objects, which
+ * are Float32Arrays, or in mSharedChannels if the mJSChannels objects have
+ * been neutered.
+ */
 class AudioBuffer MOZ_FINAL : public nsISupports,
                               public nsWrapperCache,
                               public EnableWebAudioCheck
 {
 public:
   AudioBuffer(AudioContext* aContext, uint32_t aLength,
-              uint32_t aSampleRate);
+              float aSampleRate);
   ~AudioBuffer();
 
   // This function needs to be called in order to allocate
   // all of the channels.  It is fallible!
   bool InitializeBuffers(uint32_t aNumberOfChannels,
                          JSContext* aJSContext);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -49,50 +56,76 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
                                bool* aTriedToWrap);
 
   float SampleRate() const
   {
     return mSampleRate;
   }
 
-  uint32_t Length() const
+  int32_t Length() const
   {
     return mLength;
   }
 
   double Duration() const
   {
     return mLength / static_cast<double> (mSampleRate);
   }
 
   uint32_t NumberOfChannels() const
   {
-    return mChannels.Length();
+    return mJSChannels.Length();
   }
 
+  /**
+   * If mSharedChannels is non-null, copies its contents to
+   * new Float32Arrays in mJSChannels. Returns a Float32Array.
+   */
   JSObject* GetChannelData(JSContext* aJSContext, uint32_t aChannel,
-                           ErrorResult& aRv) const;
+                           ErrorResult& aRv);
+
   JSObject* GetChannelData(uint32_t aChannel) const {
     // Doesn't perform bounds checking
-    MOZ_ASSERT(aChannel < mChannels.Length());
-    return mChannels[aChannel];
+    MOZ_ASSERT(aChannel < mJSChannels.Length());
+    return mJSChannels[aChannel];
   }
 
+  /**
+   * Returns a ThreadSharedFloatArrayBufferList containing the sample data
+   * at aRate. Sets *aLength to the number of samples per channel.
+   */
+  ThreadSharedFloatArrayBufferList* GetThreadSharedChannelsForRate(JSContext* aContext,
+                                                                   uint32_t aRate,
+                                                                   uint32_t* aLength);
+
   // aContents should either come from JS_AllocateArrayBufferContents or
   // JS_StealArrayBufferContents.
   void SetChannelDataFromArrayBufferContents(JSContext* aJSContext,
                                              uint32_t aChannel,
                                              void* aContents);
 
-private:
+protected:
+  void RestoreJSChannelData(JSContext* aJSContext);
   void ClearJSChannels();
 
   nsRefPtr<AudioContext> mContext;
-  FallibleTArray<JSObject*> mChannels;
+  // Float32Arrays
+  nsAutoTArray<JSObject*,2> mJSChannels;
+
+  // mSharedChannels aggregates the data from mJSChannels. This is non-null
+  // if and only if the mJSChannels are neutered.
+  nsRefPtr<ThreadSharedFloatArrayBufferList> mSharedChannels;
+
+  // One-element cache of resampled data. Can be non-null only if mSharedChannels
+  // is non-null.
+  nsRefPtr<ThreadSharedFloatArrayBufferList> mResampledChannels;
+  uint32_t mResampledChannelsRate;
+  uint32_t mResampledChannelsLength;
+
   uint32_t mLength;
   float mSampleRate;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -1,40 +1,220 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioBufferSourceNode.h"
 #include "mozilla/dom/AudioBufferSourceNodeBinding.h"
+#include "nsMathUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_1(AudioBufferSourceNode, AudioSourceNode, mBuffer)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioSourceNode)
 
 NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioSourceNode)
 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioSourceNode)
 
+class AudioBufferSourceNodeEngine : public AudioNodeEngine
+{
+public:
+  AudioBufferSourceNodeEngine() :
+    mStart(0), mStop(TRACK_TICKS_MAX), mOffset(0), mDuration(0) {}
+
+  // START, OFFSET and DURATION are always set by start() (along with setting
+  // mBuffer to something non-null).
+  // STOP is set by stop().
+  enum Parameters {
+    START,
+    STOP,
+    OFFSET,
+    DURATION
+  };
+  virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
+  {
+    switch (aIndex) {
+    case START: mStart = aParam; break;
+    case STOP: mStop = aParam; break;
+    default:
+      NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter");
+    }
+  }
+  virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
+  {
+    switch (aIndex) {
+    case OFFSET: mOffset = aParam; break;
+    case DURATION: mDuration = aParam; break;
+    default:
+      NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
+    }
+  }
+  virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
+  {
+    mBuffer = aBuffer;
+  }
+
+  virtual void ProduceAudioBlock(AudioNodeStream* aStream,
+                                 const AudioChunk& aInput,
+                                 AudioChunk* aOutput,
+                                 bool* aFinished)
+  {
+    if (!mBuffer)
+      return;
+    TrackTicks currentPosition = aStream->GetCurrentPosition();
+    if (currentPosition + WEBAUDIO_BLOCK_SIZE <= mStart) {
+      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+      return;
+    }
+    TrackTicks endTime = std::min(mStart + mDuration, mStop);
+    // Don't set *aFinished just because we passed mStop. Maybe someone
+    // will call stop() again with a different value.
+    if (currentPosition + WEBAUDIO_BLOCK_SIZE >= mStart + mDuration) {
+      *aFinished = true;
+    }
+    if (currentPosition >= endTime || mStart >= endTime) {
+      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+      return;
+    }
+
+    uint32_t channels = mBuffer->GetChannels();
+    if (!channels) {
+      aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+      return;
+    }
+
+    if (currentPosition >= mStart &&
+        currentPosition + WEBAUDIO_BLOCK_SIZE <= endTime) {
+      // Data is entirely within the buffer. Avoid copying it.
+      aOutput->mDuration = WEBAUDIO_BLOCK_SIZE;
+      aOutput->mBuffer = mBuffer;
+      aOutput->mChannelData.SetLength(channels);
+      for (uint32_t i = 0; i < channels; ++i) {
+        aOutput->mChannelData[i] =
+          mBuffer->GetData(i) + uintptr_t(currentPosition - mStart + mOffset);
+      }
+      aOutput->mVolume = 1.0f;
+      aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
+      return;
+    }
+
+    AllocateAudioBlock(channels, aOutput);
+    TrackTicks start = std::max(currentPosition, mStart);
+    TrackTicks end = std::min(currentPosition + WEBAUDIO_BLOCK_SIZE, endTime);
+    WriteZeroesToAudioBlock(aOutput, 0, uint32_t(start - currentPosition));
+    for (uint32_t i = 0; i < channels; ++i) {
+      memcpy(static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])) +
+             uint32_t(start - currentPosition),
+             mBuffer->GetData(i) +
+             uintptr_t(start - mStart + mOffset),
+             uint32_t(end - start));
+    }
+    uint32_t endOffset = uint32_t(end - currentPosition);
+    WriteZeroesToAudioBlock(aOutput, endOffset, WEBAUDIO_BLOCK_SIZE - endOffset);
+  }
+
+  TrackTicks mStart;
+  TrackTicks mStop;
+  nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
+  int32_t mOffset;
+  int32_t mDuration;
+};
+
 AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
   : AudioSourceNode(aContext)
+  , mStartCalled(false)
 {
+  SetProduceOwnOutput(true);
+  mStream = aContext->Graph()->CreateAudioNodeStream(new AudioBufferSourceNodeEngine());
+  mStream->AddMainThreadListener(this);
+}
+
+AudioBufferSourceNode::~AudioBufferSourceNode()
+{
+  DestroyMediaStream();
 }
 
 JSObject*
 AudioBufferSourceNode::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return AudioBufferSourceNodeBinding::Wrap(aCx, aScope, this);
 }
 
 void
-AudioBufferSourceNode::SetBuffer(AudioBuffer* aBuffer)
+AudioBufferSourceNode::Start(JSContext* aCx, double aWhen, double aOffset,
+                             const Optional<double>& aDuration, ErrorResult& aRv)
 {
-  mBuffer = aBuffer;
+  if (mStartCalled) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+  mStartCalled = true;
+
+  AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
+  if (!mBuffer || !ns) {
+    // Nothing to play, or we're already dead for some reason
+    return;
+  }
+
+  uint32_t rate = Context()->GetRate();
+  uint32_t lengthSamples;
+  nsRefPtr<ThreadSharedFloatArrayBufferList> data =
+    mBuffer->GetThreadSharedChannelsForRate(aCx, rate, &lengthSamples);
+  double length = double(lengthSamples)/rate;
+  double offset = std::max(0.0, aOffset);
+  double endOffset = aDuration.WasPassed() ?
+      std::min(aOffset + aDuration.Value(), length) : length;
+  if (offset >= endOffset) {
+    return;
+  }
+
+  ns->SetBuffer(data.forget());
+  // Don't set parameter unnecessarily
+  if (aWhen > 0.0) {
+    ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::START,
+                               Context()->DestinationStream(),
+                               aWhen);
+  }
+  int32_t offsetTicks = NS_lround(offset*rate);
+  // Don't set parameter unnecessarily
+  if (offsetTicks > 0) {
+    ns->SetInt32Parameter(AudioBufferSourceNodeEngine::OFFSET, offsetTicks);
+  }
+  ns->SetInt32Parameter(AudioBufferSourceNodeEngine::DURATION,
+      NS_lround(endOffset*rate) - offsetTicks);
+}
+
+void
+AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv)
+{
+  if (!mStartCalled) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
+  if (!ns) {
+    // We've already stopped and had our stream shut down
+    return;
+  }
+
+  ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::STOP,
+                             Context()->DestinationStream(),
+                             std::max(0.0, aWhen));
+}
+
+void
+AudioBufferSourceNode::NotifyMainThreadStateChanged()
+{
+  if (mStream->IsFinished()) {
+    SetProduceOwnOutput(false);
+  }
 }
 
 }
 }
-
--- a/content/media/webaudio/AudioBufferSourceNode.h
+++ b/content/media/webaudio/AudioBufferSourceNode.h
@@ -4,40 +4,62 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef AudioBufferSourceNode_h_
 #define AudioBufferSourceNode_h_
 
 #include "AudioSourceNode.h"
 #include "AudioBuffer.h"
+#include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-class AudioBufferSourceNode : public AudioSourceNode
+class AudioBufferSourceNode : public AudioSourceNode,
+                              public MainThreadMediaStreamListener
 {
 public:
   explicit AudioBufferSourceNode(AudioContext* aContext);
+  virtual ~AudioBufferSourceNode();
+
+  virtual void DestroyMediaStream() MOZ_OVERRIDE
+  {
+    if (mStream) {
+      mStream->RemoveMainThreadListener(this);
+    }
+    AudioSourceNode::DestroyMediaStream();
+  }
+  virtual bool SupportsMediaStreams() const MOZ_OVERRIDE
+  {
+    return true;
+  }
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioBufferSourceNode, AudioSourceNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
 
-  void Start(double) { /* no-op for now */ }
-  void Stop(double) { /* no-op for now */ }
+  void Start(JSContext* aCx, double aWhen, double aOffset,
+             const Optional<double>& aDuration, ErrorResult& aRv);
+  void Stop(double aWhen, ErrorResult& aRv);
 
   AudioBuffer* GetBuffer() const
   {
     return mBuffer;
   }
-  void SetBuffer(AudioBuffer* aBuffer);
+  void SetBuffer(AudioBuffer* aBuffer)
+  {
+    mBuffer = aBuffer;
+  }
+
+  virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
 private:
   nsRefPtr<AudioBuffer> mBuffer;
+  bool mStartCalled;
 };
 
 }
 }
 
 #endif
 
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioContext.h"
 #include "nsContentUtils.h"
 #include "nsIDOMWindow.h"
 #include "mozilla/ErrorResult.h"
+#include "MediaStreamGraph.h"
 #include "AudioDestinationNode.h"
 #include "AudioBufferSourceNode.h"
 #include "AudioBuffer.h"
 #include "GainNode.h"
 #include "DelayNode.h"
 #include "PannerNode.h"
 #include "AudioListener.h"
 #include "DynamicsCompressorNode.h"
@@ -23,21 +24,24 @@ namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(AudioContext,
                                         mWindow, mDestination, mListener)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioContext, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioContext, Release)
 
+static uint8_t gWebAudioOutputKey;
+
 AudioContext::AudioContext(nsIDOMWindow* aWindow)
   : mWindow(aWindow)
-  , mDestination(new AudioDestinationNode(this))
-  , mSampleRate(44100) // hard-code for now
+  , mDestination(new AudioDestinationNode(this, MediaStreamGraph::GetInstance()))
 {
+  // Actually play audio
+  mDestination->Stream()->AddAudioOutput(&gWebAudioOutputKey);
   SetIsDOMBinding();
 }
 
 AudioContext::~AudioContext()
 {
 }
 
 JSObject*
@@ -70,21 +74,28 @@ AudioContext::CreateBufferSource()
   return bufferNode.forget();
 }
 
 already_AddRefed<AudioBuffer>
 AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
                            uint32_t aLength, float aSampleRate,
                            ErrorResult& aRv)
 {
-  nsRefPtr<AudioBuffer> buffer = new AudioBuffer(this, aLength, aSampleRate);
+  if (aLength > INT32_MAX) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  nsRefPtr<AudioBuffer> buffer =
+    new AudioBuffer(this, int32_t(aLength), aSampleRate);
   if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
+
   return buffer.forget();
 }
 
 already_AddRefed<GainNode>
 AudioContext::CreateGain()
 {
   nsRefPtr<GainNode> gainNode = new GainNode(this);
   return gainNode.forget();
@@ -159,11 +170,22 @@ AudioContext::DecodeAudioData(const Arra
 }
 
 void
 AudioContext::RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob)
 {
   mDecodeJobs.RemoveElement(aDecodeJob);
 }
 
+MediaStreamGraph*
+AudioContext::Graph() const
+{
+  return Destination()->Stream()->Graph();
+}
+
+MediaStream*
+AudioContext::DestinationStream() const
+{
+  return Destination()->Stream();
+}
+
 }
 }
-
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -12,16 +12,19 @@
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
 #include "EnableWebAudioCheck.h"
 #include "nsAutoPtr.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "MediaBufferDecoder.h"
+#include "StreamBuffer.h"
+#include "MediaStreamGraph.h"
+#include "nsIDOMWindow.h"
 
 struct JSContext;
 class JSObject;
 class nsIDOMWindow;
 
 namespace mozilla {
 
 class ErrorResult;
@@ -39,20 +42,19 @@ class DynamicsCompressorNode;
 class GainNode;
 class GlobalObject;
 class PannerNode;
 
 class AudioContext MOZ_FINAL : public nsWrapperCache,
                                public EnableWebAudioCheck
 {
   explicit AudioContext(nsIDOMWindow* aParentWindow);
+  ~AudioContext();
 
 public:
-  virtual ~AudioContext();
-
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioContext)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioContext)
 
   nsIDOMWindow* GetParentObject() const
   {
     return mWindow;
   }
 
@@ -69,17 +71,17 @@ public:
 
   AudioDestinationNode* Destination() const
   {
     return mDestination;
   }
 
   float SampleRate() const
   {
-    return mSampleRate;
+    return float(IdealAudioRate());
   }
 
   AudioListener* Listener();
 
   already_AddRefed<AudioBufferSourceNode> CreateBufferSource();
 
   already_AddRefed<AudioBuffer>
   CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
@@ -100,27 +102,31 @@ public:
 
   already_AddRefed<BiquadFilterNode>
   CreateBiquadFilter();
 
   void DecodeAudioData(const ArrayBuffer& aBuffer,
                        DecodeSuccessCallback& aSuccessCallback,
                        const Optional<OwningNonNull<DecodeErrorCallback> >& aFailureCallback);
 
+  uint32_t GetRate() const { return IdealAudioRate(); }
+
+  MediaStreamGraph* Graph() const;
+  MediaStream* DestinationStream() const;
+
 private:
   void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
 
   friend struct ::mozilla::WebAudioDecodeJob;
 
 private:
   nsCOMPtr<nsIDOMWindow> mWindow;
   nsRefPtr<AudioDestinationNode> mDestination;
   nsRefPtr<AudioListener> mListener;
   MediaBufferDecoder mDecoder;
   nsTArray<nsAutoPtr<WebAudioDecodeJob> > mDecodeJobs;
-  float mSampleRate;
 };
 
 }
 }
 
 #endif
 
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -1,34 +1,38 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioDestinationNode.h"
 #include "mozilla/dom/AudioDestinationNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "MediaStreamGraph.h"
 #include "nsContentUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS_INHERITED0(AudioDestinationNode, AudioNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioDestinationNode, AudioNode)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioDestinationNode, AudioNode)              \
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AudioDestinationNode)
 
-AudioDestinationNode::AudioDestinationNode(AudioContext* aContext)
+AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, MediaStreamGraph* aGraph)
   : AudioNode(aContext)
 {
+  mStream = aGraph->CreateAudioNodeStream(new AudioNodeEngine());
   SetIsDOMBinding();
 }
 
 JSObject*
 AudioDestinationNode::WrapObject(JSContext* aCx, JSObject* aScope,
                                  bool* aTriedToWrap)
 {
   return AudioDestinationNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap);
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -17,17 +17,17 @@ class AudioContext;
 /**
  * Need to have an nsWrapperCache on AudioDestinationNodes since
  * AudioContext.destination returns them.
  */
 class AudioDestinationNode : public AudioNode,
                              public nsWrapperCache
 {
 public:
-  explicit AudioDestinationNode(AudioContext* aContext);
+  AudioDestinationNode(AudioContext* aContext, MediaStreamGraph* aGraph);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AudioDestinationNode,
                                                          AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
                                bool* aTriedToWrap);
 
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioNode.h"
 #include "AudioContext.h"
 #include "nsContentUtils.h"
 #include "mozilla/ErrorResult.h"
+#include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                             AudioNode::InputNode& aField,
                             const char* aName,
@@ -35,55 +36,123 @@ NS_IMPL_CYCLE_COLLECTION_3(AudioNode, mC
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioNode)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioNode)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioNode)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 AudioNode::AudioNode(AudioContext* aContext)
   : mContext(aContext)
+  , mJSBindingFinalized(false)
+  , mCanProduceOwnOutput(false)
+  , mOutputEnded(false)
 {
   MOZ_ASSERT(aContext);
 }
 
 AudioNode::~AudioNode()
 {
+  DestroyMediaStream();
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
 }
 
 static uint32_t
+FindIndexOfNode(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode)
+{
+  for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
+    if (aInputNodes[i].mInputNode == aNode) {
+      return i;
+    }
+  }
+  return nsTArray<AudioNode::InputNode>::NoIndex;
+}
+
+static uint32_t
 FindIndexOfNodeWithPorts(const nsTArray<AudioNode::InputNode>& aInputNodes, const AudioNode* aNode,
                          uint32_t aInputPort, uint32_t aOutputPort)
 {
   for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
     if (aInputNodes[i].mInputNode == aNode &&
         aInputNodes[i].mInputPort == aInputPort &&
         aInputNodes[i].mOutputPort == aOutputPort) {
       return i;
     }
   }
   return nsTArray<AudioNode::InputNode>::NoIndex;
 }
 
 void
+AudioNode::UpdateOutputEnded()
+{
+  if (mOutputEnded) {
+    // Already ended, so nothing to do.
+    return;
+  }
+  if (mCanProduceOwnOutput ||
+      !mInputNodes.IsEmpty() ||
+      (!mJSBindingFinalized && NumberOfInputs() > 0)) {
+    // This node could still produce output in the future.
+    return;
+  }
+
+  mOutputEnded = true;
+
+  // Addref this temporarily so the refcount bumping below doesn't destroy us
+  // prematurely
+  nsRefPtr<AudioNode> kungFuDeathGrip = this;
+
+  // The idea here is that we remove connections one by one, and at each step
+  // the graph is in a valid state.
+
+  // Disconnect inputs. We don't need them anymore.
+  while (!mInputNodes.IsEmpty()) {
+    uint32_t i = mInputNodes.Length() - 1;
+    nsRefPtr<AudioNode> input = mInputNodes[i].mInputNode.forget();
+    mInputNodes.RemoveElementAt(i);
+    NS_ASSERTION(mOutputNodes.Contains(this), "input/output inconsistency");
+    input->mOutputNodes.RemoveElement(this);
+  }
+
+  while (!mOutputNodes.IsEmpty()) {
+    uint32_t i = mOutputNodes.Length() - 1;
+    nsRefPtr<AudioNode> output = mOutputNodes[i].forget();
+    mOutputNodes.RemoveElementAt(i);
+    uint32_t inputIndex = FindIndexOfNode(output->mInputNodes, this);
+    NS_ASSERTION(inputIndex != nsTArray<AudioNode::InputNode>::NoIndex, "input/output inconsistency");
+    // It doesn't matter which one we remove, since we're going to remove all
+    // entries for this node anyway.
+    output->mInputNodes.RemoveElementAt(inputIndex);
+
+    output->UpdateOutputEnded();
+  }
+
+  DestroyMediaStream();
+}
+
+void
 AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
                    uint32_t aInput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs() ||
       aInput >= aDestination.NumberOfInputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   if (Context() != aDestination.Context()) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return;
   }
 
+  if (IsOutputEnded() || aDestination.IsOutputEnded()) {
+    // No need to connect since we're not going to produce anything other
+    // than silence.
+    return;
+  }
   if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, aOutput) !=
       nsTArray<AudioNode::InputNode>::NoIndex) {
     // connection already exists.
     return;
   }
 
   // The MediaStreamGraph will handle cycle detection. We don't need to do it
   // here.
@@ -91,16 +160,24 @@ AudioNode::Connect(AudioNode& aDestinati
   // Addref this temporarily so the refcount bumping below doesn't destroy us
   nsRefPtr<AudioNode> kungFuDeathGrip = this;
 
   mOutputNodes.AppendElement(&aDestination);
   InputNode* input = aDestination.mInputNodes.AppendElement();
   input->mInputNode = this;
   input->mInputPort = aInput;
   input->mOutputPort = aOutput;
+  if (SupportsMediaStreams() && aDestination.mStream) {
+    // Connect streams in the MediaStreamGraph
+    MOZ_ASSERT(aDestination.mStream->AsProcessedStream());
+    ProcessedMediaStream* ps =
+      static_cast<ProcessedMediaStream*>(aDestination.mStream.get());
+    input->mStreamPort =
+      ps->AllocateInputPort(mStream, MediaInputPort::FLAG_BLOCK_OUTPUT);
+  }
 }
 
 void
 AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
@@ -120,12 +197,16 @@ AudioNode::Disconnect(uint32_t aOutput, 
         // others, and it's not correct to remove them all since some of them
         // could be for different output ports.
         *outputsToUpdate.AppendElement() = mOutputNodes[i].forget();
         mOutputNodes.RemoveElementAt(i);
         break;
       }
     }
   }
+
+  for (uint32_t i = 0; i < outputsToUpdate.Length(); ++i) {
+    outputsToUpdate[i]->UpdateOutputEnded();
+  }
 }
 
 }
 }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -8,35 +8,80 @@
 #define AudioNode_h_
 
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Attributes.h"
 #include "EnableWebAudioCheck.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioContext.h"
+#include "MediaStreamGraph.h"
 
 struct JSContext;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
+/**
+ * The DOM object representing a Web Audio AudioNode.
+ *
+ * Each AudioNode has a MediaStream representing the actual
+ * real-time processing and output of this AudioNode.
+ *
+ * We track the incoming and outgoing connections to other AudioNodes.
+ * All connections are strong and thus rely on cycle collection to break them.
+ * However, we also track whether an AudioNode is capable of producing output
+ * in the future. If it isn't, then we break its connections to its inputs
+ * and outputs, allowing nodes to be immediately disconnected. This
+ * disconnection is done internally, invisible to DOM users.
+ *
+ * We say that a node cannot produce output in the future if it has no inputs
+ * that can, and it is not producing output itself without any inputs, and
+ * either it can never have any inputs or it has no JS wrapper. (If it has a
+ * JS wrapper and can accept inputs, then a new input could be added in
+ * the future.)
+ */
 class AudioNode : public nsISupports,
                   public EnableWebAudioCheck
 {
 public:
   explicit AudioNode(AudioContext* aContext);
   virtual ~AudioNode();
 
+  // This should be idempotent (safe to call multiple times).
+  // This should be called in the destructor of every class that overrides
+  // this method.
+  virtual void DestroyMediaStream()
+  {
+    if (mStream) {
+      mStream->Destroy();
+      mStream = nullptr;
+    }
+  }
+
+  // This method should be overridden to return true in nodes
+  // which support being hooked up to the Media Stream graph.
+  virtual bool SupportsMediaStreams() const
+  {
+    return false;
+  }
+
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioNode)
 
+  void JSBindingFinalized()
+  {
+    NS_ASSERTION(!mJSBindingFinalized, "JS binding already finalized");
+    mJSBindingFinalized = true;
+    UpdateOutputEnded();
+  }
+
   AudioContext* GetParentObject() const
   {
     return mContext;
   }
 
   AudioContext* Context() const
   {
     return mContext;
@@ -48,35 +93,77 @@ public:
   void Disconnect(uint32_t aOutput, ErrorResult& aRv);
 
   // The following two virtual methods must be implemented by each node type
   // to provide their number of input and output ports. These numbers are
   // constant for the lifetime of the node. Both default to 1.
   virtual uint32_t NumberOfInputs() const { return 1; }
   virtual uint32_t NumberOfOutputs() const { return 1; }
 
+  // This could possibly delete 'this'.
+  void UpdateOutputEnded();
+  bool IsOutputEnded() const { return mOutputEnded; }
+
   struct InputNode {
+    ~InputNode()
+    {
+      if (mStreamPort) {
+        mStreamPort->Destroy();
+      }
+    }
+
     // Strong reference.
     // May be null if the source node has gone away.
     nsRefPtr<AudioNode> mInputNode;
+    nsRefPtr<MediaInputPort> mStreamPort;
     // The index of the input port this node feeds into.
     uint32_t mInputPort;
     // The index of the output port this node comes out of.
     uint32_t mOutputPort;
   };
 
+  MediaStream* Stream() { return mStream; }
+
+  // Set this to true when the node can produce its own output even if there
+  // are no inputs.
+  void SetProduceOwnOutput(bool aCanProduceOwnOutput)
+  {
+    mCanProduceOwnOutput = aCanProduceOwnOutput;
+    if (!aCanProduceOwnOutput) {
+      UpdateOutputEnded();
+    }
+  }
+
+protected:
+  static void Callback(AudioNode* aNode) { /* not implemented */ }
+
 private:
   nsRefPtr<AudioContext> mContext;
 
+protected:
+  // Must be set in the constructor. Must not be null.
+  // If MaxNumberOfInputs() is > 0, then mStream must be a ProcessedMediaStream.
+  nsRefPtr<MediaStream> mStream;
+
+private:
   // For every InputNode, there is a corresponding entry in mOutputNodes of the
   // InputNode's mInputNode.
   nsTArray<InputNode> mInputNodes;
   // For every mOutputNode entry, there is a corresponding entry in mInputNodes
   // of the mOutputNode entry. We won't necessarily be able to identify the
   // exact matching entry, since mOutputNodes doesn't include the port
   // identifiers and the same node could be connected on multiple ports.
   nsTArray<nsRefPtr<AudioNode> > mOutputNodes;
+  // True if the JS binding has been finalized (so script no longer has
+  // a reference to this node).
+  bool mJSBindingFinalized;
+  // True if this node can produce its own output even when all inputs
+  // have ended their output.
+  bool mCanProduceOwnOutput;
+  // True if this node can never produce anything except silence in the future.
+  // Updated by UpdateOutputEnded().
+  bool mOutputEnded;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/AudioParam.cpp
+++ b/content/media/webaudio/AudioParam.cpp
@@ -8,27 +8,29 @@
 #include "nsContentUtils.h"
 #include "nsIDOMWindow.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/AudioParamBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(AudioParam, mContext)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(AudioParam, mNode)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release)
 
-AudioParam::AudioParam(AudioContext* aContext,
+AudioParam::AudioParam(AudioNode* aNode,
+                       AudioParam::CallbackType aCallback,
                        float aDefaultValue,
                        float aMinValue,
                        float aMaxValue)
   : AudioParamTimeline(aDefaultValue)
-  , mContext(aContext)
+  , mNode(aNode)
+  , mCallback(aCallback)
   , mDefaultValue(aDefaultValue)
   , mMinValue(aMinValue)
   , mMaxValue(aMaxValue)
 {
   MOZ_ASSERT(aDefaultValue >= aMinValue);
   MOZ_ASSERT(aDefaultValue <= aMaxValue);
   MOZ_ASSERT(aMinValue < aMaxValue);
   SetIsDOMBinding();
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -8,59 +8,95 @@
 #define AudioParam_h_
 
 #include "AudioEventTimeline.h"
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCOMPtr.h"
 #include "EnableWebAudioCheck.h"
 #include "nsAutoPtr.h"
-#include "AudioContext.h"
+#include "AudioNode.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/Util.h"
+#include "mozilla/ErrorResult.h"
 
 struct JSContext;
 class nsIDOMWindow;
 
 namespace mozilla {
 
-class ErrorResult;
-
 namespace dom {
 
 typedef AudioEventTimeline<ErrorResult> AudioParamTimeline;
 
 class AudioParam MOZ_FINAL : public nsWrapperCache,
                              public EnableWebAudioCheck,
                              public AudioParamTimeline
 {
 public:
-  AudioParam(AudioContext* aContext,
+  typedef void (*CallbackType)(AudioNode*);
+
+  AudioParam(AudioNode* aNode,
+             CallbackType aCallback,
              float aDefaultValue,
              float aMinValue,
              float aMaxValue);
   virtual ~AudioParam();
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioParam)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioParam)
 
   AudioContext* GetParentObject() const
   {
-    return mContext;
+    return mNode->Context();
   }
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
                                bool* aTriedToWrap);
 
   // We override SetValueCurveAtTime to convert the Float32Array to the wrapper
   // object.
   void SetValueCurveAtTime(JSContext* cx, const Float32Array& aValues, double aStartTime, double aDuration, ErrorResult& aRv)
   {
     AudioParamTimeline::SetValueCurveAtTime(aValues.Data(), aValues.Length(),
                                             aStartTime, aDuration, aRv);
+    mCallback(mNode);
+  }
+
+  // We override the rest of the mutating AudioParamTimeline methods in order to make
+  // sure that the callback is called every time that this object gets mutated.
+  void SetValue(float aValue)
+  {
+    AudioParamTimeline::SetValue(aValue);
+    mCallback(mNode);
+  }
+  void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
+  {
+    AudioParamTimeline::SetValueAtTime(aValue, aStartTime, aRv);
+    mCallback(mNode);
+  }
+  void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
+  {
+    AudioParamTimeline::LinearRampToValueAtTime(aValue, aEndTime, aRv);
+    mCallback(mNode);
+  }
+  void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
+  {
+    AudioParamTimeline::ExponentialRampToValueAtTime(aValue, aEndTime, aRv);
+    mCallback(mNode);
+  }
+  void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
+  {
+    AudioParamTimeline::SetTargetAtTime(aTarget, aStartTime, aTimeConstant, aRv);
+    mCallback(mNode);
+  }
+  void CancelScheduledValues(double aStartTime)
+  {
+    AudioParamTimeline::CancelScheduledValues(aStartTime);
+    mCallback(mNode);
   }
 
   float MinValue() const
   {
     return mMinValue;
   }
 
   float MaxValue() const
@@ -69,17 +105,18 @@ public:
   }
 
   float DefaultValue() const
   {
     return mDefaultValue;
   }
 
 private:
-  nsRefPtr<AudioContext> mContext;
+  nsRefPtr<AudioNode> mNode;
+  CallbackType mCallback;
   const float mDefaultValue;
   const float mMinValue;
   const float mMaxValue;
 };
 
 }
 }
 
--- a/content/media/webaudio/BiquadFilterNode.cpp
+++ b/content/media/webaudio/BiquadFilterNode.cpp
@@ -17,27 +17,25 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode)
 
 static float
 Nyquist(AudioContext* aContext)
 {
-  // TODO: Replace the hardcoded 44100 here with AudioContext::SampleRate()
-  // when we implement that.
-  return 0.5f * 44100;
+  return 0.5f * aContext->SampleRate();
 }
 
 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
   : AudioNode(aContext)
   , mType(BiquadTypeEnum::LOWPASS)
-  , mFrequency(new AudioParam(aContext, 350.f, 10.f, Nyquist(aContext)))
-  , mQ(new AudioParam(aContext, 1.f, 0.0001f, 1000.f))
-  , mGain(new AudioParam(aContext, 0.f, -40.f, 40.f))
+  , mFrequency(new AudioParam(this, Callback, 350.f, 10.f, Nyquist(aContext)))
+  , mQ(new AudioParam(this, Callback, 1.f, 0.0001f, 1000.f))
+  , mGain(new AudioParam(this, Callback, 0.f, -40.f, 40.f))
 {
 }
 
 JSObject*
 BiquadFilterNode::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return BiquadFilterNodeBinding::Wrap(aCx, aScope, this);
 }
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -16,17 +16,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_1(Del
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DelayNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode)
 
 DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
   : AudioNode(aContext)
-  , mDelay(new AudioParam(aContext, 0.0f, 0.0f, float(aMaxDelay)))
+  , mDelay(new AudioParam(this, Callback, 0.0f, 0.0f, float(aMaxDelay)))
 {
 }
 
 JSObject*
 DelayNode::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return DelayNodeBinding::Wrap(aCx, aScope, this);
 }
--- a/content/media/webaudio/DynamicsCompressorNode.cpp
+++ b/content/media/webaudio/DynamicsCompressorNode.cpp
@@ -21,22 +21,22 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_6(Dyn
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(DynamicsCompressorNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(DynamicsCompressorNode, AudioNode)
 
 DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
   : AudioNode(aContext)
-  , mThreshold(new AudioParam(aContext, -24.f, -100.f, 0.f))
-  , mKnee(new AudioParam(aContext, 30.f, 0.f, 40.f))
-  , mRatio(new AudioParam(aContext, 12.f, 1.f, 20.f))
-  , mReduction(new AudioParam(aContext, 0.f, -20.f, 0.f))
-  , mAttack(new AudioParam(aContext, 0.003f, 0.f, 1.f))
-  , mRelease(new AudioParam(aContext, 0.25f, 0.f, 1.f))
+  , mThreshold(new AudioParam(this, Callback, -24.f, -100.f, 0.f))
+  , mKnee(new AudioParam(this, Callback, 30.f, 0.f, 40.f))
+  , mRatio(new AudioParam(this, Callback, 12.f, 1.f, 20.f))
+  , mReduction(new AudioParam(this, Callback, 0.f, -20.f, 0.f))
+  , mAttack(new AudioParam(this, Callback, 0.003f, 0.f, 1.f))
+  , mRelease(new AudioParam(this, Callback, 0.25f, 0.f, 1.f))
 {
 }
 
 JSObject*
 DynamicsCompressorNode::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return DynamicsCompressorNodeBinding::Wrap(aCx, aScope, this);
 }
--- a/content/media/webaudio/GainNode.cpp
+++ b/content/media/webaudio/GainNode.cpp
@@ -16,17 +16,17 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED_1(Gai
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GainNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(GainNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(GainNode, AudioNode)
 
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext)
-  , mGain(new AudioParam(aContext, 1.0f, 0.0f, 1.0f))
+  , mGain(new AudioParam(this, Callback, 1.0f, 0.0f, 1.0f))
 {
 }
 
 JSObject*
 GainNode::WrapObject(JSContext* aCx, JSObject* aScope)
 {
   return GainNodeBinding::Wrap(aCx, aScope, this);
 }
--- a/content/media/webaudio/Makefile.in
+++ b/content/media/webaudio/Makefile.in
@@ -60,8 +60,11 @@ PARALLEL_DIRS := test
 
 ifdef ENABLE_TESTS
 TOOL_DIRS += compiledtest
 endif
 
 FORCE_STATIC_LIB := 1
 
 include $(topsrcdir)/config/rules.mk
+
+CFLAGS   += $(GSTREAMER_CFLAGS)
+CXXFLAGS += $(GSTREAMER_CFLAGS)
--- a/content/media/webaudio/test/test_AudioBuffer.html
+++ b/content/media/webaudio/test/test_AudioBuffer.html
@@ -7,23 +7,23 @@
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
-  var ac = new AudioContext();
-  var buffer = ac.createBuffer(2, 2048, 44100);
+  var context = new AudioContext();
+  var buffer = context.createBuffer(2, 2048, context.sampleRate);
   SpecialPowers.gc(); // Make sure that our channels are accessible after GC
   ok(buffer, "Buffer was allocated successfully");
-  is(buffer.sampleRate, 44100, "Correct sample rate");
+  is(buffer.sampleRate, context.sampleRate, "Correct sample rate");
   is(buffer.length, 2048, "Correct length");
-  ok(Math.abs(buffer.duration - 2048 / 44100) < 0.0001, "Correct duration");
+  ok(Math.abs(buffer.duration - 2048 / context.sampleRate) < 0.0001, "Correct duration");
   is(buffer.numberOfChannels, 2, "Correct number of channels");
   for (var i = 0; i < buffer.numberOfChannels; ++i) {
     var buf = buffer.getChannelData(i);
     ok(buf, "Buffer index " + i + " exists");
     ok(buf instanceof Float32Array, "Result is a typed array");
     is(buf.length, buffer.length, "Correct length");
     var foundNonZero = false;
     for (var j = 0; j < buf.length; ++j) {
--- a/content/media/webaudio/test/test_AudioContext.html
+++ b/content/media/webaudio/test/test_AudioContext.html
@@ -17,17 +17,17 @@ addLoadEvent(function() {
     new AudioContext();
   } catch (e) {
     accessThrows = true;
   }
   ok(accessThrows, "AudioContext should be hidden behind a pref");
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
   var ac = new AudioContext();
   ok(ac, "Create a AudioContext object");
-  is(ac.sampleRate, 44100, "Correct sample rate");
+  is(ac.sampleRate, 48000, "Correct sample rate");
   SpecialPowers.clearUserPref("media.webaudio.enabled");
   SimpleTest.finish();
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/webaudio/test/test_biquadFilterNode.html
+++ b/content/media/webaudio/test/test_biquadFilterNode.html
@@ -14,36 +14,36 @@ function near(a, b, msg) {
   ok(Math.abs(a - b) < 1e-3, msg);
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
 
   var source = context.createBufferSource();
 
   var filter = context.createBiquadFilter();
 
   source.buffer = buffer;
 
   source.connect(filter);
   filter.connect(destination);
 
   // Verify default values
   is(filter.type, 0, "Correct default value for type");
   near(filter.frequency.minValue, 10, "Correct min value for filter frequency");
-  near(filter.frequency.maxValue, 22050, "Correct max value for filter frequency");
+  near(filter.frequency.maxValue, context.sampleRate/2, "Correct max value for filter frequency");
   near(filter.frequency.defaultValue, 350, "Correct default value for filter frequency");
   near(filter.Q.minValue, 0.001, "Correct min value for filter Q");
   near(filter.Q.maxValue, 1000, "Correct max value for filter Q");
   near(filter.Q.defaultValue, 1, "Correct default value for filter Q");
   near(filter.gain.minValue, -40, "Correct min value for filter gain");
   near(filter.gain.maxValue, 40, "Correct max value for filter gain");
   near(filter.gain.defaultValue, 0, "Correct default value for filter gain");
 
--- a/content/media/webaudio/test/test_decodeAudioData.html
+++ b/content/media/webaudio/test/test_decodeAudioData.html
@@ -249,15 +249,20 @@ expectTypeError(function() {
 });
 expectTypeError(function() {
   cx.decodeAudioData("buffer", callbackShouldNeverRun, callbackShouldNeverRun);
 });
 expectTypeError(function() {
   cx.decodeAudioData(new Uint8Array(100), callbackShouldNeverRun, callbackShouldNeverRun);
 });
 
-// Now, let's get real!
-runNextTest();
+if (cx.sampleRate == 44100) {
+  // Now, let's get real!
+  runNextTest();
+} else {
+  todo(false, "Decoded data tests disabled; context sampleRate " + cx.sampleRate + " not supported");
+  SimpleTest.finish();
+}
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/webaudio/test/test_delayNode.html
+++ b/content/media/webaudio/test/test_delayNode.html
@@ -10,19 +10,19 @@
 <script src="webaudio.js" type="text/javascript"></script>
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
 
   var source = context.createBufferSource();
 
   var delay = context.createDelay();
 
--- a/content/media/webaudio/test/test_dynamicsCompressorNode.html
+++ b/content/media/webaudio/test/test_dynamicsCompressorNode.html
@@ -13,19 +13,19 @@ function near(a, b, msg) {
   ok(Math.abs(a - b) < 1e-4, msg);
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
 
   var source = context.createBufferSource();
 
   var compressor = context.createDynamicsCompressor();
 
--- a/content/media/webaudio/test/test_gainNode.html
+++ b/content/media/webaudio/test/test_gainNode.html
@@ -9,19 +9,19 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
 
   var source = context.createBufferSource();
 
   var gain = context.createGain();
 
--- a/content/media/webaudio/test/test_pannerNode.html
+++ b/content/media/webaudio/test/test_pannerNode.html
@@ -13,19 +13,19 @@ function near(a, b, msg) {
   ok(Math.abs(a - b) < 1e-4, msg);
 }
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
 
   var source = context.createBufferSource();
 
   var panner = context.createPanner();
 
--- a/content/media/webaudio/test/test_singleSourceDest.html
+++ b/content/media/webaudio/test/test_singleSourceDest.html
@@ -9,19 +9,19 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   SpecialPowers.setBoolPref("media.webaudio.enabled", true);
 
   var context = new AudioContext();
-  var buffer = context.createBuffer(1, 2048, 44100);
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
   for (var i = 0; i < 2048; ++i) {
-    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / 44100);
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
   }
 
   var destination = context.destination;
   is(destination.context, context, "Destination node has proper context");
   is(destination.context, context, "Destination node has proper context");
   is(destination.numberOfInputs, 1, "Destination node has 1 inputs");
   is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
 
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -57,16 +57,18 @@ static PRCList gSHistoryList;
 // Max viewers allowed total, across all SHistory objects - negative default
 // means we will calculate how many viewers to cache based on total memory
 int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
 
 // A counter that is used to be able to know the order in which
 // entries were touched, so that we can evict older entries first.
 static uint32_t gTouchCounter = 0;
 
+#ifdef PR_LOGGING
+
 static PRLogModuleInfo*
 GetSHistoryLog()
 {
   static PRLogModuleInfo *sLog;
   if (!sLog)
     sLog = PR_NewLogModule("nsSHistory");
   return sLog;
 }
@@ -100,16 +102,24 @@ GetSHistoryLog()
   PR_BEGIN_MACRO                                           \
     if (PR_LOG_TEST(GetSHistoryLog(), PR_LOG_DEBUG)) {     \
       nsCOMPtr<nsIURI> uri;                                \
       shentry->GetURI(getter_AddRefs(uri));                \
       LOG_SPEC(format, uri);                               \
     }                                                      \
   PR_END_MACRO
 
+#else // !PR_LOGGING
+
+#define LOG(format)
+#define LOG_SPEC(format, uri)
+#define LOG_SHENTRY_SPEC(format, shentry)
+
+#endif // PR_LOGGING
+
 // Iterates over all registered session history listeners.
 #define ITERATE_LISTENERS(body)                            \
   PR_BEGIN_MACRO                                           \
   {                                                        \
     nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \
       iter(mListeners);                                    \
     while (iter.HasMore()) {                               \
       nsCOMPtr<nsISHistoryListener> listener =             \
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -20,18 +20,16 @@ include $(DEPTH)/config/autoconf.mk
 
 XPCSHELL_TESTS = unit
 # FIXME/bug 575918: out-of-process xpcshell is broken on OS X
 ifneq ($(OS_ARCH),Darwin)
 XPCSHELL_TESTS += unit_ipc
 endif
 
 MOCHITEST_FILES = \
-		test_bug94514.html \
-		bug94514-postpage.html \
 		test_bug123696.html \
 		bug123696-subframe.html \
 		test_bug344861.html \
 		test_bug369814.html \
 		bug369814.zip \
 		bug369814.jar \
 		test_bug384014.html \
 		test_bug387979.html \
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -476,16 +476,17 @@ WebappsApplication.prototype = {
   },
 
   launch: function(aStartPoint) {
     let request = this.createRequest();
     cpmm.sendAsyncMessage("Webapps:Launch", { origin: this.origin,
                                               manifestURL: this.manifestURL,
                                               startPoint: aStartPoint || "",
                                               oid: this._id,
+                                              timestamp: Date.now(),
                                               requestID: this.getRequestId(request) });
     return request;
   },
 
   clearBrowserData: function() {
     let browserChild =
       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
     if (browserChild) {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -93,16 +93,17 @@ DOMInterfaces = {
 
 'AudioContext': {
     'implicitJSContext': [ 'createBuffer' ],
     'nativeOwnership': 'refcounted',
     'resultNotAddRefed': [ 'destination', 'listener' ],
 },
 
 'AudioBufferSourceNode': {
+    'implicitJSContext': [ 'start' ],
     'wrapperCache': False
 },
 
 'AudioListener' : {
     'nativeOwnership': 'refcounted'
 },
 
 'AudioNode' : {
--- a/dom/browser-element/BrowserElementScrolling.js
+++ b/dom/browser-element/BrowserElementScrolling.js
@@ -556,17 +556,17 @@ const ContentPanning = {
 
     return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9)); 
   }
 };
 
 ContentPanning.init();
 
 // Min/max velocity of kinetic panning. This is in pixels/millisecond.
-const kMinVelocity = 0.4;
+const kMinVelocity = 0.2;
 const kMaxVelocity = 6;
 
 // Constants that affect the "friction" of the scroll pane.
 const kExponentialC = 1000;
 const kPolynomialC = 100 / 1000000;
 
 // How often do we change the position of the scroll pane?
 // Too often and panning may jerk near the end.
@@ -586,37 +586,49 @@ const KineticPanning = {
     return this.target !== null;
   },
 
   target: null,
   start: function kp_start(target) {
     this.target = target;
 
     // Calculate the initial velocity of the movement based on user input
-    let momentums = this.momentums.slice(-kSamples);
+    let momentums = this.momentums;
+    let flick = momentums[momentums.length - 1].time - momentums[0].time < 300;
+
+    // Calculate the panning based on the last moves.
+    momentums = momentums.slice(-kSamples);
 
     let distance = new Point(0, 0);
     momentums.forEach(function(momentum) {
       distance.add(momentum.dx, momentum.dy);
     });
 
-    let elapsed = momentums[momentums.length - 1].time - momentums[0].time;
-
     function clampFromZero(x, min, max) {
       if (x >= 0)
         return Math.max(min, Math.min(max, x));
       return Math.min(-min, Math.max(-max, x));
     }
 
+    let elapsed = momentums[momentums.length - 1].time - momentums[0].time;
     let velocityX = clampFromZero(distance.x / elapsed, 0, kMaxVelocity);
     let velocityY = clampFromZero(distance.y / elapsed, 0, kMaxVelocity);
 
     let velocity = this._velocity;
-    velocity.set(Math.abs(velocityX) < kMinVelocity ? 0 : velocityX,
-                 Math.abs(velocityY) < kMinVelocity ? 0 : velocityY);
+    if (flick) {
+      // Very fast pan action that does not generate a click are very often pan
+      // action. If this is a small gesture then it will not move the view a lot
+      // and so it will be above the minimun threshold and not generate any
+      // kinetic panning. This does not look on a device since this is often
+      // a real gesture, so let's lower the velocity threshold for such moves.
+      velocity.set(velocityX, velocityY);
+    } else {
+      velocity.set(Math.abs(velocityX) < kMinVelocity ? 0 : velocityX,
+                   Math.abs(velocityY) < kMinVelocity ? 0 : velocityY);
+    }
     this.momentums = [];
 
     // Set acceleration vector to opposite signs of velocity
     function sign(x) {
       return x ? (x > 0 ? 1 : -1) : 0;
     }
 
     this._acceleration.set(velocity.clone().map(sign).scale(-kPolynomialC));
--- a/dom/indexedDB/TransactionThreadPool.cpp
+++ b/dom/indexedDB/TransactionThreadPool.cpp
@@ -22,42 +22,16 @@ namespace {
 
 const uint32_t kThreadLimit = 20;
 const uint32_t kIdleThreadLimit = 5;
 const uint32_t kIdleThreadTimeoutMs = 30000;
 
 TransactionThreadPool* gInstance = nullptr;
 bool gShutdown = false;
 
-inline
-nsresult
-CheckOverlapAndMergeObjectStores(nsTArray<nsString>& aLockedStores,
-                                 const nsTArray<nsString>& aObjectStores,
-                                 bool aShouldMerge,
-                                 bool* aStoresOverlap)
-{
-  uint32_t length = aObjectStores.Length();
-
-  bool overlap = false;
-
-  for (uint32_t index = 0; index < length; index++) {
-    const nsString& storeName = aObjectStores[index];
-    if (aLockedStores.Contains(storeName)) {
-      overlap = true;
-    }
-    else if (aShouldMerge && !aLockedStores.AppendElement(storeName)) {
-      NS_WARNING("Out of memory!");
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  *aStoresOverlap = overlap;
-  return NS_OK;
-}
-
 } // anonymous namespace
 
 BEGIN_INDEXEDDB_NAMESPACE
 
 class FinishTransactionRunnable MOZ_FINAL : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
@@ -175,16 +149,38 @@ TransactionThreadPool::Cleanup()
     // And make sure they get processed.
     rv = NS_ProcessPendingEvents(nullptr);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
+// static
+PLDHashOperator
+TransactionThreadPool::MaybeUnblockTransaction(nsPtrHashKey<TransactionInfo>* aKey,
+                                               void* aUserArg)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  TransactionInfo* maybeUnblockedInfo = aKey->GetKey();
+  TransactionInfo* finishedInfo = static_cast<TransactionInfo*>(aUserArg);
+
+  NS_ASSERTION(maybeUnblockedInfo->blockedOn.Contains(finishedInfo),
+               "Huh?");
+  maybeUnblockedInfo->blockedOn.RemoveEntry(finishedInfo);
+  if (!maybeUnblockedInfo->blockedOn.Count() &&
+      !maybeUnblockedInfo->transaction->IsAborted()) {
+    // Let this transaction run.
+    maybeUnblockedInfo->queue->Unblock();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 void
 TransactionThreadPool::FinishTransaction(IDBTransaction* aTransaction)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aTransaction, "Null pointer!");
 
   // AddRef here because removing from the hash will call Release.
   nsRefPtr<IDBTransaction> transaction(aTransaction);
@@ -192,265 +188,173 @@ TransactionThreadPool::FinishTransaction
   nsIAtom* databaseId = aTransaction->mDatabase->Id();
 
   DatabaseTransactionInfo* dbTransactionInfo;
   if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
     NS_ERROR("We don't know anyting about this database?!");
     return;
   }
 
-  nsTArray<TransactionInfo>& transactionsInProgress =
+  DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
     dbTransactionInfo->transactions;
 
-  uint32_t transactionCount = transactionsInProgress.Length();
+  uint32_t transactionCount = transactionsInProgress.Count();
 
 #ifdef DEBUG
   if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
     NS_ASSERTION(transactionCount == 1,
                  "More transactions running than should be!");
   }
 #endif
 
   if (transactionCount == 1) {
 #ifdef DEBUG
     {
-      TransactionInfo& info = transactionsInProgress[0];
-      NS_ASSERTION(info.transaction == aTransaction, "Transaction mismatch!");
+      const TransactionInfo* info = transactionsInProgress.Get(aTransaction);
+      NS_ASSERTION(info->transaction == aTransaction, "Transaction mismatch!");
     }
 #endif
     mTransactionsInProgress.Remove(databaseId);
 
     // See if we need to fire any complete callbacks.
     uint32_t index = 0;
     while (index < mCompleteCallbacks.Length()) {
       if (MaybeFireCallback(mCompleteCallbacks[index])) {
         mCompleteCallbacks.RemoveElementAt(index);
       }
       else {
         index++;
       }
     }
+
+    return;
   }
-  else {
-    // We need to rebuild the locked object store list.
-    nsTArray<nsString> storesWriting, storesReading;
-
-    for (uint32_t index = 0, count = transactionCount; index < count; index++) {
-      IDBTransaction* transaction = transactionsInProgress[index].transaction;
-      if (transaction == aTransaction) {
-        NS_ASSERTION(count == transactionCount, "More than one match?!");
-
-        transactionsInProgress.RemoveElementAt(index);
-        index--;
-        count--;
-
-        continue;
-      }
-
-      const nsTArray<nsString>& objectStores = transaction->mObjectStoreNames;
+  TransactionInfo* info = transactionsInProgress.Get(aTransaction);
+  NS_ASSERTION(info, "We've never heard of this transaction?!?");
 
-      bool dummy;
-      if (transaction->mMode == IDBTransaction::READ_WRITE) {
-        if (NS_FAILED(CheckOverlapAndMergeObjectStores(storesWriting,
-                                                       objectStores,
-                                                       true, &dummy))) {
-          NS_WARNING("Out of memory!");
-        }
-      }
-      else if (transaction->mMode == IDBTransaction::READ_ONLY) {
-        if (NS_FAILED(CheckOverlapAndMergeObjectStores(storesReading,
-                                                       objectStores,
-                                                       true, &dummy))) {
-          NS_WARNING("Out of memory!");
-        }
-      }
-      else {
-        NS_NOTREACHED("Unknown mode!");
-      }
+  const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
+  for (uint32_t index = 0, count = objectStoreNames.Length(); index < count;
+       index++) {
+    TransactionInfoPair* blockInfo =
+      dbTransactionInfo->blockingTransactions.Get(objectStoreNames[index]);
+    NS_ASSERTION(blockInfo, "Huh?");
+
+    if (aTransaction->mMode == IDBTransaction::READ_WRITE &&
+        blockInfo->lastBlockingReads == info) {
+      blockInfo->lastBlockingReads = nullptr;
     }
 
-    NS_ASSERTION(transactionsInProgress.Length() == transactionCount - 1,
-                 "Didn't find the transaction we were looking for!");
-
-    dbTransactionInfo->storesWriting.SwapElements(storesWriting);
-    dbTransactionInfo->storesReading.SwapElements(storesReading);
+    uint32_t i = blockInfo->lastBlockingWrites.IndexOf(info);
+    if (i != blockInfo->lastBlockingWrites.NoIndex) {
+      blockInfo->lastBlockingWrites.RemoveElementAt(i);
+    }
   }
 
-  // Try to dispatch all the queued transactions again.
-  nsTArray<QueuedDispatchInfo> queuedDispatch;
-  queuedDispatch.SwapElements(mDelayedDispatchQueue);
+  info->blocking.EnumerateEntries(MaybeUnblockTransaction, info);
 
-  transactionCount = queuedDispatch.Length();
-  for (uint32_t index = 0; index < transactionCount; index++) {
-    if (NS_FAILED(Dispatch(queuedDispatch[index]))) {
-      NS_WARNING("Dispatch failed!");
-    }
-  }
+  transactionsInProgress.Remove(aTransaction);
 }
 
-nsresult
-TransactionThreadPool::TransactionCanRun(IDBTransaction* aTransaction,
-                                         bool* aCanRun,
-                                         TransactionQueue** aExistingQueue)
+TransactionThreadPool::TransactionQueue&
+TransactionThreadPool::GetQueueForTransaction(IDBTransaction* aTransaction)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aTransaction, "Null pointer!");
-  NS_ASSERTION(aCanRun, "Null pointer!");
-  NS_ASSERTION(aExistingQueue, "Null pointer!");
 
   nsIAtom* databaseId = aTransaction->mDatabase->Id();
   const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
   const uint16_t mode = aTransaction->mMode;
 
   // See if we can run this transaction now.
   DatabaseTransactionInfo* dbTransactionInfo;
   if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
-    // First transaction for this database, fine to run.
-    *aCanRun = true;
-    *aExistingQueue = nullptr;
-    return NS_OK;
+    // First transaction for this database.
+    dbTransactionInfo = new DatabaseTransactionInfo();
+    mTransactionsInProgress.Put(databaseId, dbTransactionInfo);
+  }
+
+  DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
+    dbTransactionInfo->transactions;
+  TransactionInfo* info = transactionsInProgress.Get(aTransaction);
+  if (info) {
+    // We recognize this one.
+    return *info->queue;
   }
 
-  nsTArray<TransactionInfo>& transactionsInProgress =
-    dbTransactionInfo->transactions;
+  TransactionInfo* transactionInfo = new TransactionInfo(aTransaction,
+                                                         objectStoreNames);
+
+  dbTransactionInfo->transactions.Put(aTransaction, transactionInfo);;
 
-  uint32_t transactionCount = transactionsInProgress.Length();
-  NS_ASSERTION(transactionCount, "Should never be 0!");
+  for (uint32_t index = 0, count = objectStoreNames.Length(); index < count;
+       index++) {
+    TransactionInfoPair* blockInfo =
+      dbTransactionInfo->blockingTransactions.Get(objectStoreNames[index]);
+    if (!blockInfo) {
+      blockInfo = new TransactionInfoPair();
+      blockInfo->lastBlockingReads = nullptr;
+      dbTransactionInfo->blockingTransactions.Put(objectStoreNames[index],
+                                                  blockInfo);
+    }
 
-  for (uint32_t index = 0; index < transactionCount; index++) {
-    // See if this transaction is in out list of current transactions.
-    const TransactionInfo& info = transactionsInProgress[index];
-    if (info.transaction == aTransaction) {
-      *aCanRun = true;
-      *aExistingQueue = info.queue;
-      return NS_OK;
+    // Mark what we are blocking on.
+    if (blockInfo->lastBlockingReads) {
+      TransactionInfo* blockingInfo = blockInfo->lastBlockingReads;
+      transactionInfo->blockedOn.PutEntry(blockingInfo);
+      blockingInfo->blocking.PutEntry(transactionInfo);
+    }
+
+    if (mode == IDBTransaction::READ_WRITE &&
+        blockInfo->lastBlockingWrites.Length()) {
+      for (uint32_t index = 0,
+           count = blockInfo->lastBlockingWrites.Length(); index < count;
+           index++) {
+        TransactionInfo* blockingInfo = blockInfo->lastBlockingWrites[index];
+        transactionInfo->blockedOn.PutEntry(blockingInfo);
+        blockingInfo->blocking.PutEntry(transactionInfo);
+      }
+    }
+
+    if (mode == IDBTransaction::READ_WRITE) {
+      blockInfo->lastBlockingReads = transactionInfo;
+      blockInfo->lastBlockingWrites.Clear();
+    }
+    else {
+      blockInfo->lastBlockingWrites.AppendElement(transactionInfo);
     }
   }
 
-  NS_ASSERTION(mode != IDBTransaction::VERSION_CHANGE, "How did we get here?");
-
-  bool writeOverlap;
-  nsresult rv =
-    CheckOverlapAndMergeObjectStores(dbTransactionInfo->storesWriting,
-                                     objectStoreNames,
-                                     mode == IDBTransaction::READ_WRITE,
-                                     &writeOverlap);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  bool readOverlap;
-  rv = CheckOverlapAndMergeObjectStores(dbTransactionInfo->storesReading,
-                                        objectStoreNames,
-                                        mode == IDBTransaction::READ_ONLY,
-                                        &readOverlap);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (writeOverlap ||
-      (readOverlap && mode == IDBTransaction::READ_WRITE)) {
-    *aCanRun = false;
-    *aExistingQueue = nullptr;
-    return NS_OK;
+  if (!transactionInfo->blockedOn.Count()) {
+    transactionInfo->queue->Unblock();
   }
 
-  *aCanRun = true;
-  *aExistingQueue = nullptr;
-  return NS_OK;
+  return *transactionInfo->queue;
 }
 
 nsresult
 TransactionThreadPool::Dispatch(IDBTransaction* aTransaction,
                                 nsIRunnable* aRunnable,
                                 bool aFinish,
                                 nsIRunnable* aFinishRunnable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aTransaction, "Null pointer!");
   NS_ASSERTION(aRunnable, "Null pointer!");
 
   if (aTransaction->mDatabase->IsInvalidated() && !aFinish) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  bool canRun;
-  TransactionQueue* existingQueue;
-  nsresult rv = TransactionCanRun(aTransaction, &canRun, &existingQueue);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!canRun) {
-    QueuedDispatchInfo* info = mDelayedDispatchQueue.AppendElement();
-    NS_ENSURE_TRUE(info, NS_ERROR_OUT_OF_MEMORY);
-
-    info->transaction = aTransaction;
-    info->runnable = aRunnable;
-    info->finish = aFinish;
-    info->finishRunnable = aFinishRunnable;
-
-    return NS_OK;
-  }
-
-  if (existingQueue) {
-    existingQueue->Dispatch(aRunnable);
-    if (aFinish) {
-      existingQueue->Finish(aFinishRunnable);
-    }
-    return NS_OK;
-  }
-
-  nsIAtom* databaseId = aTransaction->mDatabase->Id();
-
-#ifdef DEBUG
-  if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
-    NS_ASSERTION(!mTransactionsInProgress.Get(databaseId, nullptr),
-                 "Shouldn't have anything in progress!");
-  }
-#endif
-
-  DatabaseTransactionInfo* dbTransactionInfo;
-  nsAutoPtr<DatabaseTransactionInfo> autoDBTransactionInfo;
+  TransactionQueue& queue = GetQueueForTransaction(aTransaction);
 
-  if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
-    // Make a new struct for this transaction.
-    autoDBTransactionInfo = new DatabaseTransactionInfo();
-    dbTransactionInfo = autoDBTransactionInfo;
-  }
-
-  const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
-
-  nsTArray<nsString>& storesInUse =
-    aTransaction->mMode == IDBTransaction::READ_WRITE ?
-    dbTransactionInfo->storesWriting :
-    dbTransactionInfo->storesReading;
-
-  if (!storesInUse.AppendElements(objectStoreNames)) {
-    NS_WARNING("Out of memory!");
-    return NS_ERROR_OUT_OF_MEMORY;
+  queue.Dispatch(aRunnable);
+  if (aFinish) {
+    queue.Finish(aFinishRunnable);
   }
-
-  nsTArray<TransactionInfo>& transactionInfoArray =
-    dbTransactionInfo->transactions;
-
-  TransactionInfo* transactionInfo = transactionInfoArray.AppendElement();
-  NS_ENSURE_TRUE(transactionInfo, NS_ERROR_OUT_OF_MEMORY);
-
-  transactionInfo->transaction = aTransaction;
-  transactionInfo->queue = new TransactionQueue(aTransaction, aRunnable);
-  if (aFinish) {
-    transactionInfo->queue->Finish(aFinishRunnable);
-  }
-
-  if (!transactionInfo->objectStoreNames.AppendElements(objectStoreNames)) {
-    NS_WARNING("Out of memory!");
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (autoDBTransactionInfo) {
-    mTransactionsInProgress.Put(databaseId, autoDBTransactionInfo);
-    autoDBTransactionInfo.forget();
-  }
-
-  return mThreadPool->Dispatch(transactionInfo->queue, NS_DISPATCH_NORMAL);
+  return NS_OK;
 }
 
 bool
 TransactionThreadPool::WaitForAllDatabasesToComplete(
                                             nsTArray<IDBDatabase*>& aDatabases,
                                             nsIRunnable* aCallback)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -470,91 +374,107 @@ TransactionThreadPool::WaitForAllDatabas
 
   if (MaybeFireCallback(*callback)) {
     mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
   }
 
   return true;
 }
 
+// static
+PLDHashOperator
+TransactionThreadPool::CollectTransactions(IDBTransaction* aKey,
+                                           TransactionInfo* aValue,
+                                           void* aUserArg)
+{
+  nsAutoTArray<nsRefPtr<IDBTransaction>, 50>* transactionArray =
+    static_cast<nsAutoTArray<nsRefPtr<IDBTransaction>, 50>*>(aUserArg);
+  transactionArray->AppendElement(aKey);
+
+  return PL_DHASH_NEXT;
+}
+
 void
 TransactionThreadPool::AbortTransactionsForDatabase(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aDatabase, "Null pointer!");
 
   // Get list of transactions for this database id
   DatabaseTransactionInfo* dbTransactionInfo;
   if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
-    // If there are no running transactions, there can't be any pending ones
+    // If there are no transactions, we're done.
     return;
   }
 
-  nsAutoTArray<nsRefPtr<IDBTransaction>, 50> transactions;
-
   // Collect any running transactions
-  nsTArray<TransactionInfo>& transactionsInProgress =
+  DatabaseTransactionInfo::TransactionHashtable& transactionsInProgress =
     dbTransactionInfo->transactions;
 
-  uint32_t transactionCount = transactionsInProgress.Length();
-  NS_ASSERTION(transactionCount, "Should never be 0!");
+  NS_ASSERTION(transactionsInProgress.Count(), "Should never be 0!");
 
-  for (uint32_t index = 0; index < transactionCount; index++) {
-    // See if any transaction belongs to this IDBDatabase instance
-    IDBTransaction* transaction = transactionsInProgress[index].transaction;
-    if (transaction->Database() == aDatabase) {
-      transactions.AppendElement(transaction);
-    }
-  }
-
-  // Collect any pending transactions.
-  for (uint32_t index = 0; index < mDelayedDispatchQueue.Length(); index++) {
-    // See if any transaction belongs to this IDBDatabase instance
-    IDBTransaction* transaction = mDelayedDispatchQueue[index].transaction;
-    if (transaction->Database() == aDatabase) {
-      transactions.AppendElement(transaction);
-    }
-  }
+  nsAutoTArray<nsRefPtr<IDBTransaction>, 50> transactions;
+  transactionsInProgress.EnumerateRead(CollectTransactions, &transactions);
 
   // Abort transactions. Do this after collecting the transactions in case
   // calling Abort() modifies the data structures we're iterating above.
   for (uint32_t index = 0; index < transactions.Length(); index++) {
+    if (transactions[index]->Database() != aDatabase) {
+      continue;
+    }
+
     // This can fail, for example if the transaction is in the process of
     // being comitted. That is expected and fine, so we ignore any returned
     // errors.
     transactions[index]->Abort();
   }
 }
 
+struct NS_STACK_CLASS TransactionSearchInfo
+{
+  TransactionSearchInfo(IDBDatabase* aDatabase)
+    : db(aDatabase), found(false)
+  {
+  }
+
+  IDBDatabase* db;
+  bool found;
+};
+
+// static
+PLDHashOperator
+TransactionThreadPool::FindTransaction(IDBTransaction* aKey,
+                                       TransactionInfo* aValue,
+                                       void* aUserArg)
+{
+  TransactionSearchInfo* info = static_cast<TransactionSearchInfo*>(aUserArg);
+
+  if (aKey->Database() == info->db) {
+    info->found = true;
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
 bool
 TransactionThreadPool::HasTransactionsForDatabase(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aDatabase, "Null pointer!");
 
-  // Get list of transactions for this database id
-  DatabaseTransactionInfo* dbTransactionInfo;
-  if (!mTransactionsInProgress.Get(aDatabase->Id(), &dbTransactionInfo)) {
+  DatabaseTransactionInfo* dbTransactionInfo = nullptr;
+  dbTransactionInfo = mTransactionsInProgress.Get(aDatabase->Id());
+  if (!dbTransactionInfo) {
     return false;
   }
 
-  nsTArray<TransactionInfo>& transactionsInProgress =
-    dbTransactionInfo->transactions;
-
-  uint32_t transactionCount = transactionsInProgress.Length();
-  NS_ASSERTION(transactionCount, "Should never be 0!");
+  TransactionSearchInfo info(aDatabase);
+  dbTransactionInfo->transactions.EnumerateRead(FindTransaction, &info);
 
-  for (uint32_t index = 0; index < transactionCount; index++) {
-    // See if any transaction belongs to this IDBDatabase instance
-    if (transactionsInProgress[index].transaction->Database() == aDatabase) {
-      return true;
-    }
-  }
-
-  return false;
+  return info.found;
 }
 
 bool
 TransactionThreadPool::MaybeFireCallback(DatabasesCompleteCallback& aCallback)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   for (uint32_t index = 0; index < aCallback.mDatabases.Length(); index++) {
@@ -564,25 +484,33 @@ TransactionThreadPool::MaybeFireCallback
     }
   }
 
   aCallback.mCallback->Run();
   return true;
 }
 
 TransactionThreadPool::
-TransactionQueue::TransactionQueue(IDBTransaction* aTransaction,
-                                   nsIRunnable* aRunnable)
+TransactionQueue::TransactionQueue(IDBTransaction* aTransaction)
 : mMonitor("TransactionQueue::mMonitor"),
   mTransaction(aTransaction),
   mShouldFinish(false)
 {
   NS_ASSERTION(aTransaction, "Null pointer!");
-  NS_ASSERTION(aRunnable, "Null pointer!");
-  mQueue.AppendElement(aRunnable);
+}
+
+void
+TransactionThreadPool::TransactionQueue::Unblock()
+{
+  MonitorAutoLock lock(mMonitor);
+
+  // NB: Finish may be called before Unblock.
+
+  TransactionThreadPool::Get()->mThreadPool->
+    Dispatch(this, NS_DISPATCH_NORMAL);
 }
 
 void
 TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable* aRunnable)
 {
   MonitorAutoLock lock(mMonitor);
 
   NS_ASSERTION(!mShouldFinish, "Dispatch called after Finish!");
@@ -610,17 +538,17 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(Transactio
 
 NS_IMETHODIMP
 TransactionThreadPool::TransactionQueue::Run()
 {
   nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queue;
   nsCOMPtr<nsIRunnable> finishRunnable;
   bool shouldFinish = false;
 
-  while(!shouldFinish) {
+  do {
     NS_ASSERTION(queue.IsEmpty(), "Should have cleared this!");
 
     {
       MonitorAutoLock lock(mMonitor);
       while (!mShouldFinish && mQueue.IsEmpty()) {
         if (NS_FAILED(mMonitor.Wait())) {
           NS_ERROR("Failed to wait!");
         }
@@ -638,17 +566,17 @@ TransactionThreadPool::TransactionQueue:
       nsCOMPtr<nsIRunnable>& runnable = queue[index];
       runnable->Run();
       runnable = nullptr;
     }
 
     if (count) {
       queue.Clear();
     }
-  }
+  } while (!shouldFinish);
 
   nsCOMPtr<nsIRunnable> finishTransactionRunnable =
     new FinishTransactionRunnable(mTransaction, finishRunnable);
   if (NS_FAILED(NS_DispatchToMainThread(finishTransactionRunnable,
                                         NS_DISPATCH_NORMAL))) {
     NS_WARNING("Failed to dispatch finishTransactionRunnable!");
   }
 
--- a/dom/indexedDB/TransactionThreadPool.h
+++ b/dom/indexedDB/TransactionThreadPool.h
@@ -58,88 +58,135 @@ public:
 
 protected:
   class TransactionQueue MOZ_FINAL : public nsIRunnable
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
 
-    inline TransactionQueue(IDBTransaction* aTransaction,
-                            nsIRunnable* aRunnable);
+    TransactionQueue(IDBTransaction* aTransaction);
+
+    void Unblock();
 
-    inline void Dispatch(nsIRunnable* aRunnable);
+    void Dispatch(nsIRunnable* aRunnable);
 
-    inline void Finish(nsIRunnable* aFinishRunnable);
+    void Finish(nsIRunnable* aFinishRunnable);
 
   private:
     mozilla::Monitor mMonitor;
     IDBTransaction* mTransaction;
     nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> mQueue;
     nsCOMPtr<nsIRunnable> mFinishRunnable;
     bool mShouldFinish;
   };
 
+  friend class TransactionQueue;
+
   struct TransactionInfo
   {
+    TransactionInfo(IDBTransaction* aTransaction,
+                    const nsTArray<nsString>& aObjectStoreNames)
+    {
+      MOZ_COUNT_CTOR(TransactionInfo);
+
+      blockedOn.Init();
+      blocking.Init();
+
+      transaction = aTransaction;
+      queue = new TransactionQueue(aTransaction);
+      objectStoreNames.AppendElements(aObjectStoreNames);
+    }
+
+    ~TransactionInfo()
+    {
+      MOZ_COUNT_DTOR(TransactionInfo);
+    }
+
     nsRefPtr<IDBTransaction> transaction;
     nsRefPtr<TransactionQueue> queue;
     nsTArray<nsString> objectStoreNames;
+    nsTHashtable<nsPtrHashKey<TransactionInfo> > blockedOn;
+    nsTHashtable<nsPtrHashKey<TransactionInfo> > blocking;
+  };
+
+  struct TransactionInfoPair
+  {
+    TransactionInfoPair()
+      : lastBlockingReads(nullptr)
+    {
+      MOZ_COUNT_CTOR(TransactionInfoPair);
+    }
+
+    ~TransactionInfoPair()
+    {
+      MOZ_COUNT_DTOR(TransactionInfoPair);
+    }
+    // Multiple reading transactions can block future writes.
+    nsTArray<TransactionInfo*> lastBlockingWrites;
+    // But only a single writing transaction can block future reads.
+    TransactionInfo* lastBlockingReads;
   };
 
   struct DatabaseTransactionInfo
   {
-    nsTArray<TransactionInfo> transactions;
-    nsTArray<nsString> storesReading;
-    nsTArray<nsString> storesWriting;
+    DatabaseTransactionInfo()
+    {
+      MOZ_COUNT_CTOR(DatabaseTransactionInfo);
+
+      transactions.Init();
+      blockingTransactions.Init();
+    }
+
+    ~DatabaseTransactionInfo()
+    {
+      MOZ_COUNT_DTOR(DatabaseTransactionInfo);
+    }
+
+    typedef nsClassHashtable<nsPtrHashKey<IDBTransaction>, TransactionInfo >
+      TransactionHashtable;
+    TransactionHashtable transactions;
+    nsClassHashtable<nsStringHashKey, TransactionInfoPair> blockingTransactions;
   };
 
-  struct QueuedDispatchInfo
-  {
-    QueuedDispatchInfo()
-    : finish(false)
-    { }
+  static PLDHashOperator
+  CollectTransactions(IDBTransaction* aKey,
+                      TransactionInfo* aValue,
+                      void* aUserArg);
 
-    nsRefPtr<IDBTransaction> transaction;
-    nsCOMPtr<nsIRunnable> runnable;
-    nsCOMPtr<nsIRunnable> finishRunnable;
-    bool finish;
-  };
+  static PLDHashOperator
+  FindTransaction(IDBTransaction* aKey,
+                  TransactionInfo* aValue,
+                  void* aUserArg);
+
+  static PLDHashOperator
+  MaybeUnblockTransaction(nsPtrHashKey<TransactionInfo>* aKey,
+                          void* aUserArg);
 
   struct DatabasesCompleteCallback
   {
     nsTArray<IDBDatabase*> mDatabases;
     nsCOMPtr<nsIRunnable> mCallback;
   };
 
   TransactionThreadPool();
   ~TransactionThreadPool();
 
   nsresult Init();
   nsresult Cleanup();
 
   void FinishTransaction(IDBTransaction* aTransaction);
 
-  nsresult TransactionCanRun(IDBTransaction* aTransaction,
-                             bool* aCanRun,
-                             TransactionQueue** aExistingQueue);
-
-  nsresult Dispatch(const QueuedDispatchInfo& aInfo)
-  {
-    return Dispatch(aInfo.transaction, aInfo.runnable, aInfo.finish,
-                    aInfo.finishRunnable);
-  }
+  TransactionQueue& GetQueueForTransaction(IDBTransaction* aTransaction);
 
   bool MaybeFireCallback(DatabasesCompleteCallback& aCallback);
 
   nsCOMPtr<nsIThreadPool> mThreadPool;
 
   nsClassHashtable<nsISupportsHashKey, DatabaseTransactionInfo>
     mTransactionsInProgress;
 
-  nsTArray<QueuedDispatchInfo> mDelayedDispatchQueue;
-
   nsTArray<DatabasesCompleteCallback> mCompleteCallbacks;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_transactionthreadpool_h__
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -16,17 +16,16 @@
 #include "nsIIPCSerializableInputStream.h"
 #include "nsIRemoteBlob.h"
 #include "nsISeekableStream.h"
 
 #include "mozilla/Monitor.h"
 #include "mozilla/unused.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "nsDOMFile.h"
-#include "nsDOMBlobBuilder.h"
 #include "nsThreadUtils.h"
 
 #include "ContentChild.h"
 #include "ContentParent.h"
 
 #define PRIVATE_REMOTE_INPUT_STREAM_IID \
   {0x30c7699f, 0x51d2, 0x48c8, {0xad, 0x56, 0xc0, 0x16, 0xd7, 0x6f, 0x71, 0x27}}
 
@@ -626,61 +625,42 @@ BlobTraits<Parent>::BaseType::NoteRunnab
       return;
     }
   }
 
   MOZ_NOT_REACHED("Runnable not in our array!");
 }
 
 template <ActorFlavorEnum ActorFlavor>
-class RemoteBlobBase : public nsIRemoteBlob
+struct RemoteBlobBase;
+
+template <>
+struct RemoteBlobBase<Parent>
+{
+  InputStreamParams mInputStreamParams;
+};
+
+template <>
+struct RemoteBlobBase<Child>
+{ };
+
+template <ActorFlavorEnum ActorFlavor>
+class RemoteBlob : public RemoteBlobBase<ActorFlavor>,
+                   public nsDOMFile,
+                   public nsIRemoteBlob
 {
 public:
   typedef RemoteBlob<ActorFlavor> SelfType;
   typedef Blob<ActorFlavor> ActorType;
   typedef InputStreamActor<ActorFlavor> StreamActorType;
-
-  void
-  SetPBlob(void* aActor) MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(!aActor || !mActor);
-    mActor = static_cast<ActorType*>(aActor);
-  }
-
-  virtual void*
-  GetPBlob() MOZ_OVERRIDE
-  {
-    return static_cast<typename ActorType::ProtocolType*>(mActor);
-  }
+  typedef typename ActorType::ConstructorParamsType ConstructorParamsType;
 
 private:
   ActorType* mActor;
 
-protected:
-  RemoteBlobBase()
-  : mActor(nullptr)
-  { }
-
-  virtual
-  ~RemoteBlobBase()
-  {
-    if (mActor) {
-      mActor->NoteDyingRemoteBlob();
-    }
-  }
-
-  ActorType* Actor() const { return mActor; }
-};
-
-template <ActorFlavorEnum ActorFlavor>
-class RemoteBlob : public nsDOMFile,
-                   public RemoteBlobBase<ActorFlavor>
-{
-  typedef Blob<ActorFlavor> ActorType;
-public:
   class StreamHelper : public nsRunnable
   {
     typedef Blob<ActorFlavor> ActorType;
     typedef InputStreamActor<ActorFlavor> StreamActorType;
 
     mozilla::Monitor mMonitor;
     ActorType* mActor;
     nsCOMPtr<nsIDOMBlob> mSourceBlob;
@@ -787,17 +767,16 @@ public:
 
   public:
     SliceHelper(ActorType* aActor)
     : mMonitor("RemoteBlob::SliceHelper::mMonitor"), mActor(aActor), mStart(0),
       mLength(0), mDone(false)
     {
       // This may be created on any thread.
       MOZ_ASSERT(aActor);
-      MOZ_ASSERT(aActor->HasManager());
     }
 
     nsresult
     GetSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
              nsIDOMBlob** aSlice)
     {
       // This may be called on any thread.
       MOZ_ASSERT(aSlice);
@@ -854,30 +833,33 @@ public:
       MOZ_ASSERT(mActor);
       MOZ_ASSERT(!mSlice);
       MOZ_ASSERT(!mDone);
 
       NormalBlobConstructorParams normalParams;
       normalParams.contentType() = mContentType;
       normalParams.length() = mLength;
 
-      BlobConstructorNoMultipartParams params(normalParams);
+      typename ActorType::ConstructorParamsType params;
+      ActorType::BaseType::SetBlobConstructorParams(params, normalParams);
 
       ActorType* newActor = ActorType::Create(params);
       MOZ_ASSERT(newActor);
-      mActor->PropagateManager(newActor);
 
       SlicedBlobConstructorParams slicedParams;
       slicedParams.contentType() = mContentType;
       slicedParams.begin() = mStart;
       slicedParams.end() = mStart + mLength;
       SetBlobOnParams(mActor, slicedParams);
 
-      BlobConstructorNoMultipartParams params2(slicedParams);
-      if (mActor->ConstructPBlobOnManager(newActor, params2)) {
+      typename ActorType::OtherSideConstructorParamsType otherSideParams;
+      ActorType::BaseType::SetBlobConstructorParams(otherSideParams,
+                                                    slicedParams);
+
+      if (mActor->Manager()->SendPBlobConstructor(newActor, otherSideParams)) {
         mSlice = newActor->GetBlob();
       }
 
       mActor = nullptr;
 
       if (aNotify) {
         MonitorAutoLock lock(mMonitor);
         mDone = true;
@@ -889,156 +871,76 @@ public:
     }
   };
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   RemoteBlob(const nsAString& aName, const nsAString& aContentType,
              uint64_t aLength, uint64_t aModDate)
-  : nsDOMFile(aName, aContentType, aLength, aModDate)
-  {
-    mImmutable = true;
-  }
-
-  RemoteBlob(const nsAString& aName, const nsAString& aContentType,
-             uint64_t aLength)
-  : nsDOMFile(aName, aContentType, aLength)
+  : nsDOMFile(aName, aContentType, aLength, aModDate), mActor(nullptr)
   {
     mImmutable = true;
   }
 
   RemoteBlob(const nsAString& aContentType, uint64_t aLength)
-  : nsDOMFile(aContentType, aLength)
+  : nsDOMFile(aContentType, aLength), mActor(nullptr)
   {
     mImmutable = true;
   }
 
   RemoteBlob()
   : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX)
+  , mActor(nullptr)
   {
     mImmutable = true;
   }
 
+  virtual ~RemoteBlob()
+  {
+    if (mActor) {
+      mActor->NoteDyingRemoteBlob();
+    }
+  }
+
+  void
+  SetActor(ActorType* aActor)
+  {
+    MOZ_ASSERT(!aActor || !mActor);
+    mActor = aActor;
+  }
+
+  void
+  MaybeSetInputStream(const ConstructorParamsType& aParams);
+
   virtual already_AddRefed<nsIDOMBlob>
   CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType)
               MOZ_OVERRIDE
   {
-    ActorType* actor = RemoteBlobBase<ActorFlavor>::Actor();
-    if (!actor) {
+    if (!mActor) {
       return nullptr;
     }
 
-    nsRefPtr<SliceHelper> helper = new SliceHelper(actor);
+    nsRefPtr<SliceHelper> helper = new SliceHelper(mActor);
 
     nsCOMPtr<nsIDOMBlob> slice;
     nsresult rv =
       helper->GetSlice(aStart, aLength, aContentType, getter_AddRefs(slice));
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     return slice.forget();
   }
 
   NS_IMETHOD
-  GetInternalStream(nsIInputStream** aStream) MOZ_OVERRIDE
-  {
-    ActorType* actor = RemoteBlobBase<ActorFlavor>::Actor();
-    if (!actor) {
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    nsRefPtr<StreamHelper> helper = new StreamHelper(actor, this);
-    return helper->GetStream(aStream);
-  }
-
-  NS_IMETHOD
-  GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate)
-  {
-    if (IsDateUnknown()) {
-      aLastModifiedDate->setNull();
-    } else {
-      JSObject* date = JS_NewDateObjectMsec(cx, mLastModificationDate);
-      if (!date) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-      aLastModifiedDate->setObject(*date);
-    }
-    return NS_OK;
-  }
-};
-
-template <ActorFlavorEnum ActorFlavor>
-class RemoteMemoryBlob : public nsDOMMemoryFile,
-                         public RemoteBlobBase<ActorFlavor>
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-
-  RemoteMemoryBlob(void* aMemoryBuffer,
-                   uint64_t aLength,
-                   const nsAString& aName,
-                   const nsAString& aContentType,
-                   uint64_t aModDate)
-  : nsDOMMemoryFile(aMemoryBuffer, aLength, aName, aContentType, aModDate)
-  {
-    mImmutable = true;
-  }
+  GetInternalStream(nsIInputStream** aStream) MOZ_OVERRIDE;
 
-  RemoteMemoryBlob(void* aMemoryBuffer,
-                   uint64_t aLength,
-                   const nsAString& aName,
-                   const nsAString& aContentType)
-  : nsDOMMemoryFile(aMemoryBuffer, aLength, aName, aContentType)
-  {
-    mImmutable = true;
-  }
-
-  RemoteMemoryBlob(void* aMemoryBuffer,
-                   uint64_t aLength,
-                   const nsAString& aContentType)
-  : nsDOMMemoryFile(aMemoryBuffer, aLength, EmptyString(), aContentType)
-  {
-    mImmutable = true;
-  }
-
-  NS_IMETHOD
-  GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate)
+  virtual void*
+  GetPBlob() MOZ_OVERRIDE
   {
-    if (IsDateUnknown()) {
-      aLastModifiedDate->setNull();
-    } else {
-      JSObject* date = JS_NewDateObjectMsec(cx, mLastModificationDate);
-      if (!date) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-      aLastModifiedDate->setObject(*date);
-    }
-    return NS_OK;
-  }
-};
-
-template <ActorFlavorEnum ActorFlavor>
-class RemoteMultipartBlob : public nsDOMMultipartFile,
-                            public RemoteBlobBase<ActorFlavor>
-{
-
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-
-  RemoteMultipartBlob(const nsAString& aName,
-                      const nsAString& aContentType)
-  : nsDOMMultipartFile(aName, aContentType)
-  {
-    mImmutable = true;
-  }
-
-  RemoteMultipartBlob(const nsAString& aName)
-  : nsDOMMultipartFile(aName)
-  {
-    mImmutable = true;
+    return static_cast<typename ActorType::ProtocolType*>(mActor);
   }
 
   NS_IMETHOD
   GetLastModifiedDate(JSContext* cx, JS::Value* aLastModifiedDate)
   {
     if (IsDateUnknown()) {
       aLastModifiedDate->setNull();
     } else {
@@ -1047,255 +949,138 @@ public:
         return NS_ERROR_OUT_OF_MEMORY;
       }
       aLastModifiedDate->setObject(*date);
     }
     return NS_OK;
   }
 };
 
+template <>
+void
+RemoteBlob<Parent>::MaybeSetInputStream(const ConstructorParamsType& aParams)
+{
+  if (aParams.optionalInputStreamParams().type() ==
+      OptionalInputStreamParams::TInputStreamParams) {
+    mInputStreamParams =
+      aParams.optionalInputStreamParams().get_InputStreamParams();
+  }
+  else {
+    MOZ_ASSERT(aParams.blobParams().type() ==
+               ChildBlobConstructorParams::TMysteryBlobConstructorParams);
+  }
+}
+
+template <>
+void
+RemoteBlob<Child>::MaybeSetInputStream(const ConstructorParamsType& aParams)
+{
+  // Nothing needed on the child side!
+}
+
+template <>
+NS_IMETHODIMP
+RemoteBlob<Parent>::GetInternalStream(nsIInputStream** aStream)
+{
+  if (mInputStreamParams.type() != InputStreamParams::T__None) {
+    nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(mInputStreamParams);
+    if (!stream) {
+      NS_WARNING("Failed to deserialize stream!");
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    stream.forget(aStream);
+    return NS_OK;
+  }
+
+  if (!mActor) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<StreamHelper> helper = new StreamHelper(mActor, this);
+  return helper->GetStream(aStream);
+}
+
+template <>
+NS_IMETHODIMP
+RemoteBlob<Child>::GetInternalStream(nsIInputStream** aStream)
+{
+  if (!mActor) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<StreamHelper> helper = new StreamHelper(mActor, this);
+  return helper->GetStream(aStream);
+}
+
 template <ActorFlavorEnum ActorFlavor>
 Blob<ActorFlavor>::Blob(nsIDOMBlob* aBlob)
-: mBlob(aBlob), mRemoteBlob(nullptr), mRemoteMemoryBlob(nullptr),
-  mRemoteMultipartBlob(nullptr), mContentManager(nullptr),
-  mBlobManager(nullptr), mOwnsBlob(true), mBlobIsFile(false)
+: mBlob(aBlob), mRemoteBlob(nullptr), mOwnsBlob(true), mBlobIsFile(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aBlob);
   aBlob->AddRef();
 
   nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
   mBlobIsFile = !!file;
 }
 
 template <ActorFlavorEnum ActorFlavor>
-Blob<ActorFlavor>::Blob(nsRefPtr<RemoteBlobType>& aBlob,
-                        bool aBlobIsFile)
-: mBlob(nullptr), mRemoteBlob(nullptr), mRemoteMemoryBlob(nullptr),
-  mRemoteMultipartBlob(nullptr), mContentManager(nullptr),
-  mBlobManager(nullptr), mOwnsBlob(true), mBlobIsFile(aBlobIsFile)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mBlob);
-  MOZ_ASSERT(!mRemoteBlob);
-  MOZ_ASSERT(aBlob);
-
-  if (NS_FAILED(aBlob->SetMutable(false))) {
-    MOZ_NOT_REACHED("Failed to make remote blob immutable!");
-  }
-
-  aBlob->SetPBlob(this);
-  aBlob.forget(&mRemoteBlob);
-
-  mBlob = mRemoteBlob;
-}
-
-template <ActorFlavorEnum ActorFlavor>
-Blob<ActorFlavor>::Blob(nsRefPtr<RemoteMemoryBlobType>& aBlob,
-                        bool aBlobIsFile)
-: mBlob(nullptr), mRemoteBlob(nullptr), mRemoteMemoryBlob(nullptr),
-  mRemoteMultipartBlob(nullptr), mContentManager(nullptr),
-  mBlobManager(nullptr), mOwnsBlob(true), mBlobIsFile(aBlobIsFile)
+Blob<ActorFlavor>::Blob(const ConstructorParamsType& aParams)
+: mBlob(nullptr), mRemoteBlob(nullptr), mOwnsBlob(false), mBlobIsFile(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mBlob);
-  MOZ_ASSERT(!mRemoteMemoryBlob);
-  MOZ_ASSERT(aBlob);
+
+  ChildBlobConstructorParams::Type paramType =
+    BaseType::GetBlobConstructorParams(aParams).type();
 
-  if (NS_FAILED(aBlob->SetMutable(false))) {
-    MOZ_NOT_REACHED("Failed to make remote blob immutable!");
-  }
-
-  aBlob->SetPBlob(this);
-  aBlob.forget(&mRemoteMemoryBlob);
-
-  mBlob = mRemoteMemoryBlob;
-}
+  mBlobIsFile =
+    paramType == ChildBlobConstructorParams::TFileBlobConstructorParams ||
+    paramType == ChildBlobConstructorParams::TMysteryBlobConstructorParams;
 
-template <ActorFlavorEnum ActorFlavor>
-Blob<ActorFlavor>::Blob(nsRefPtr<RemoteMultipartBlobType>& aBlob,
-                        bool aBlobIsFile)
-: mBlob(nullptr), mRemoteBlob(nullptr), mRemoteMemoryBlob(nullptr),
-  mRemoteMultipartBlob(nullptr), mContentManager(nullptr),
-  mBlobManager(nullptr), mOwnsBlob(true), mBlobIsFile(aBlobIsFile)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mBlob);
-  MOZ_ASSERT(!mRemoteMultipartBlob);
-  MOZ_ASSERT(aBlob);
+  nsRefPtr<RemoteBlobType> remoteBlob = CreateRemoteBlob(aParams);
+  MOZ_ASSERT(remoteBlob);
 
-  if (NS_FAILED(aBlob->SetMutable(false))) {
-    MOZ_NOT_REACHED("Failed to make remote blob immutable!");
-  }
+  remoteBlob->SetActor(this);
+  remoteBlob->MaybeSetInputStream(aParams);
+  remoteBlob.forget(&mRemoteBlob);
 
-  aBlob->SetPBlob(this);
-  aBlob.forget(&mRemoteMultipartBlob);
-
-  mBlob = mRemoteMultipartBlob;
+  mBlob = mRemoteBlob;
+  mOwnsBlob = true;
 }
 
 template <ActorFlavorEnum ActorFlavor>
 Blob<ActorFlavor>*
-Blob<ActorFlavor>::Create(const BlobConstructorParams& aParams)
+Blob<ActorFlavor>::Create(const ConstructorParamsType& aParams)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  switch (aParams.type()) {
-    case BlobConstructorParams::TBlobConstructorNoMultipartParams: {
-      const BlobConstructorNoMultipartParams& params =
-        aParams.get_BlobConstructorNoMultipartParams();
-
-      switch (params.type()) {
-        case BlobConstructorNoMultipartParams::TBlobOrFileConstructorParams: {
-          const BlobOrFileConstructorParams& fileParams =
-            params.get_BlobOrFileConstructorParams();
-          nsRefPtr<RemoteBlobType> remoteBlob;
-          bool isFile = false;
-
-          switch (fileParams.type()) {
-            case BlobOrFileConstructorParams::TNormalBlobConstructorParams: {
-              const NormalBlobConstructorParams& normalParams =
-                fileParams.get_NormalBlobConstructorParams();
-              remoteBlob = new RemoteBlobType(normalParams.contentType(),
-                                              normalParams.length());
-              break;
-            }
-
-            case BlobOrFileConstructorParams::TFileBlobConstructorParams: {
-              const FileBlobConstructorParams& fbParams =
-                fileParams.get_FileBlobConstructorParams();
-              remoteBlob =
-                new RemoteBlobType(fbParams.name(), fbParams.contentType(),
-                                   fbParams.length(), fbParams.modDate());
-              isFile = true;
-              break;
-            }
-
-            default:
-              MOZ_NOT_REACHED("Unknown params!");
-          }
+  const ChildBlobConstructorParams& blobParams =
+    BaseType::GetBlobConstructorParams(aParams);
 
-          return new Blob<ActorFlavor>(remoteBlob, isFile);
-        }
-        case BlobConstructorNoMultipartParams::TMemoryBlobOrFileConstructorParams: {
-          const MemoryBlobOrFileConstructorParams& memoryParams =
-            params.get_MemoryBlobOrFileConstructorParams();
-          const BlobOrFileConstructorParams& internalParams =
-            memoryParams.constructorParams();
-          nsRefPtr<RemoteMemoryBlobType> remoteMemoryBlob;
-          bool isFile = false;
-
-          switch (internalParams.type()) {
-            case BlobOrFileConstructorParams::TNormalBlobConstructorParams: {
-              const NormalBlobConstructorParams& normalParams =
-                internalParams.get_NormalBlobConstructorParams();
-              MOZ_ASSERT(normalParams.length() == memoryParams.data().Length());
-
-              void* data =
-                BlobTraits<ActorFlavor>::Allocate(memoryParams.data().Length());
-              if (!data) {
-                return nullptr;
-              }
-              memcpy(data,
-                     memoryParams.data().Elements(),
-                     memoryParams.data().Length());
-              remoteMemoryBlob =
-                new RemoteMemoryBlobType(data,
-                                         memoryParams.data().Length(),
-                                         normalParams.contentType());
-              break;
-            }
-
-            case BlobOrFileConstructorParams::TFileBlobConstructorParams: {
-              const FileBlobConstructorParams& fbParams =
-                internalParams.get_FileBlobConstructorParams();
-              MOZ_ASSERT(fbParams.length() == memoryParams.data().Length());
+  switch (blobParams.type()) {
+    case ChildBlobConstructorParams::TNormalBlobConstructorParams:
+    case ChildBlobConstructorParams::TFileBlobConstructorParams:
+    case ChildBlobConstructorParams::TMysteryBlobConstructorParams:
+      return new Blob<ActorFlavor>(aParams);
 
-              void* data =
-                BlobTraits<ActorFlavor>::Allocate(memoryParams.data().Length());
-              if (!data) {
-                return nullptr;
-              }
-              memcpy(data,
-                     memoryParams.data().Elements(),
-                     memoryParams.data().Length());
-              remoteMemoryBlob =
-                new RemoteMemoryBlobType(data,
-                                         memoryParams.data().Length(),
-                                         fbParams.name(),
-                                         fbParams.contentType(),
-                                         fbParams.modDate());
-              isFile = true;
-              break;
-            }
+    case ChildBlobConstructorParams::TSlicedBlobConstructorParams: {
+      const SlicedBlobConstructorParams& params =
+        blobParams.get_SlicedBlobConstructorParams();
 
-            default:
-              MOZ_NOT_REACHED("Unknown params!");
-          }
-
-          return new Blob<ActorFlavor>(remoteMemoryBlob, isFile);
-        }
-        case BlobConstructorNoMultipartParams::TMysteryBlobConstructorParams: {
-          nsRefPtr<RemoteBlobType> remoteBlob = new RemoteBlobType();
-          return new Blob<ActorFlavor>(remoteBlob, true);
-        }
-
-        case BlobConstructorNoMultipartParams::TSlicedBlobConstructorParams: {
-          const SlicedBlobConstructorParams& slicedParams =
-            params.get_SlicedBlobConstructorParams();
-
-          nsCOMPtr<nsIDOMBlob> source =
-            GetBlobFromParams<ActorFlavor>(slicedParams);
-          MOZ_ASSERT(source);
+      nsCOMPtr<nsIDOMBlob> source = GetBlobFromParams<ActorFlavor>(params);
+      MOZ_ASSERT(source);
 
-          nsCOMPtr<nsIDOMBlob> slice;
-          nsresult rv =
-            source->Slice(slicedParams.begin(), slicedParams.end(),
-                          slicedParams.contentType(), 3,
-                          getter_AddRefs(slice));
-          NS_ENSURE_SUCCESS(rv, nullptr);
-
-          return new Blob<ActorFlavor>(slice);
-        }
-
-        default:
-          MOZ_NOT_REACHED("Unknown params!");
-      }
-    }
-
-    case BlobConstructorParams::TMultipartBlobOrFileConstructorParams: {
-      const MultipartBlobOrFileConstructorParams& params =
-        aParams.get_MultipartBlobOrFileConstructorParams();
+      nsCOMPtr<nsIDOMBlob> slice;
+      nsresult rv =
+        source->Slice(params.begin(), params.end(), params.contentType(), 3,
+                      getter_AddRefs(slice));
+      NS_ENSURE_SUCCESS(rv, nullptr);
 
-      nsRefPtr<RemoteMultipartBlobType> file;
-      bool isFile = false;
-      const BlobOrFileConstructorParams& internalParams =
-        params.constructorParams();
-      switch (internalParams.type()) {
-        case BlobOrFileConstructorParams::TNormalBlobConstructorParams: {
-          const NormalBlobConstructorParams& normalParams =
-            internalParams.get_NormalBlobConstructorParams();
-          file = new RemoteMultipartBlobType(normalParams.contentType());
-          break;
-        }
-
-        case BlobOrFileConstructorParams::TFileBlobConstructorParams: {
-          const FileBlobConstructorParams& fbParams =
-            internalParams.get_FileBlobConstructorParams();
-          file = new RemoteMultipartBlobType(fbParams.name(),
-                                             fbParams.contentType());
-          isFile = true;
-          break;
-        }
-
-        default:
-          MOZ_NOT_REACHED("Unknown params!");
-      }
-
-      return new Blob<ActorFlavor>(file, isFile);
+      return new Blob<ActorFlavor>(slice);
     }
 
     default:
       MOZ_NOT_REACHED("Unknown params!");
   }
 
   return nullptr;
 }
@@ -1307,18 +1092,17 @@ Blob<ActorFlavor>::GetBlob()
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
 
   nsCOMPtr<nsIDOMBlob> blob;
 
   // Remote blobs are held alive until the first call to GetBlob. Thereafter we
   // only hold a weak reference. Normal blobs are held alive until the actor is
   // destroyed.
-  if (mOwnsBlob &&
-      (mRemoteBlob || mRemoteMemoryBlob || mRemoteMultipartBlob)) {
+  if (mRemoteBlob && mOwnsBlob) {
     blob = dont_AddRef(mBlob);
     mOwnsBlob = false;
   }
   else {
     blob = mBlob;
   }
 
   MOZ_ASSERT(blob);
@@ -1363,71 +1147,67 @@ Blob<ActorFlavor>::SetMysteryBlobInfo(co
   ToConcreteBlob(mBlob)->SetLazyData(voidString, aContentType,
                                      aLength, UINT64_MAX);
 
   NormalBlobConstructorParams params(aContentType, aLength);
   return ProtocolType::SendResolveMystery(params);
 }
 
 template <ActorFlavorEnum ActorFlavor>
-typename Blob<ActorFlavor>::ProtocolType*
-Blob<ActorFlavor>::ConstructPBlobOnManager(ProtocolType* aActor,
-                                           const BlobConstructorParams& aParams)
+already_AddRefed<typename Blob<ActorFlavor>::RemoteBlobType>
+Blob<ActorFlavor>::CreateRemoteBlob(const ConstructorParamsType& aParams)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(HasManager());
+
+  const ChildBlobConstructorParams& blobParams =
+    BaseType::GetBlobConstructorParams(aParams);
+
+  nsRefPtr<RemoteBlobType> remoteBlob;
+
+  switch (blobParams.type()) {
+    case ChildBlobConstructorParams::TNormalBlobConstructorParams: {
+      const NormalBlobConstructorParams& params =
+        blobParams.get_NormalBlobConstructorParams();
+      remoteBlob = new RemoteBlobType(params.contentType(), params.length());
+      break;
+    }
 
-  if (mContentManager) {
-    return mContentManager->SendPBlobConstructor(aActor, aParams);
-  }
+    case ChildBlobConstructorParams::TFileBlobConstructorParams: {
+      const FileBlobConstructorParams& params =
+        blobParams.get_FileBlobConstructorParams();
+      remoteBlob =
+        new RemoteBlobType(params.name(), params.contentType(),
+                           params.length(), params.modDate());
+      break;
+    }
 
-  if (mBlobManager) {
-    return mBlobManager->SendPBlobConstructor(aActor, aParams);
+    case ChildBlobConstructorParams::TMysteryBlobConstructorParams: {
+      remoteBlob = new RemoteBlobType();
+      break;
+    }
+
+    default:
+      MOZ_NOT_REACHED("Unknown params!");
   }
 
-  MOZ_NOT_REACHED("Why don't I have a manager?!");
-  return nullptr;
-}
-
-template <ActorFlavorEnum ActorFlavor>
-void
-Blob<ActorFlavor>::SetManager(ContentManagerType* aManager)
-{
-  MOZ_ASSERT(!mContentManager && !mBlobManager);
-  MOZ_ASSERT(aManager);
-
-  mContentManager = aManager;
-}
+  MOZ_ASSERT(remoteBlob);
 
-template <ActorFlavorEnum ActorFlavor>
-void
-Blob<ActorFlavor>::SetManager(BlobManagerType* aManager)
-{
-  MOZ_ASSERT(!mContentManager && !mBlobManager);
-  MOZ_ASSERT(aManager);
+  if (NS_FAILED(remoteBlob->SetMutable(false))) {
+    MOZ_NOT_REACHED("Failed to make remote blob immutable!");
+  }
 
-  mBlobManager = aManager;
-}
-
-template <ActorFlavorEnum ActorFlavor>
-void
-Blob<ActorFlavor>::PropagateManager(Blob<ActorFlavor>* aActor) const
-{
-  MOZ_ASSERT(HasManager());
-
-  aActor->mContentManager = mContentManager;
-  aActor->mBlobManager = mBlobManager;
+  return remoteBlob.forget();
 }
 
 template <ActorFlavorEnum ActorFlavor>
 void
 Blob<ActorFlavor>::NoteDyingRemoteBlob()
 {
   MOZ_ASSERT(mBlob);
-  MOZ_ASSERT(mRemoteBlob || mRemoteMemoryBlob || mRemoteMultipartBlob);
+  MOZ_ASSERT(mRemoteBlob);
   MOZ_ASSERT(!mOwnsBlob);
 
   // This may be called on any thread due to the fact that RemoteBlob is
   // designed to be passed between threads. We must start the shutdown process
   // on the main thread, so we proxy here if necessary.
   if (!NS_IsMainThread()) {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewNonOwningRunnableMethod(this,
@@ -1437,55 +1217,43 @@ Blob<ActorFlavor>::NoteDyingRemoteBlob()
     }
 
     return;
   }
 
   // Must do this before calling Send__delete__ or we'll crash there trying to
   // access a dangling pointer.
   mRemoteBlob = nullptr;
-  mRemoteMemoryBlob = nullptr;
-  mRemoteMultipartBlob = nullptr;
 
   mozilla::unused << ProtocolType::Send__delete__(this);
 }
 
 template <ActorFlavorEnum ActorFlavor>
 void
 Blob<ActorFlavor>::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
 
   if (mRemoteBlob) {
-    mRemoteBlob->SetPBlob(nullptr);
-  }
-
-  if (mRemoteMemoryBlob) {
-    mRemoteMemoryBlob->SetPBlob(nullptr);
-  }
-
-  if (mRemoteMultipartBlob) {
-    mRemoteMultipartBlob->SetPBlob(nullptr);
+    mRemoteBlob->SetActor(nullptr);
   }
 
   if (mOwnsBlob) {
     mBlob->Release();
   }
 }
 
 template <ActorFlavorEnum ActorFlavor>
 bool
 Blob<ActorFlavor>::RecvResolveMystery(const ResolveMysteryParams& aParams)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
   MOZ_ASSERT(!mRemoteBlob);
-  MOZ_ASSERT(!mRemoteMemoryBlob);
-  MOZ_ASSERT(!mRemoteMultipartBlob);
   MOZ_ASSERT(mOwnsBlob);
 
   if (!mBlobIsFile) {
     MOZ_ASSERT(false, "Must always be a file!");
     return false;
   }
 
   nsDOMFileBase* blob = ToConcreteBlob(mBlob);
@@ -1518,18 +1286,16 @@ Blob<ActorFlavor>::RecvResolveMystery(co
 
 template <>
 bool
 Blob<Parent>::RecvPBlobStreamConstructor(StreamType* aActor)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
   MOZ_ASSERT(!mRemoteBlob);
-  MOZ_ASSERT(!mRemoteMemoryBlob);
-  MOZ_ASSERT(!mRemoteMultipartBlob);
 
   nsCOMPtr<nsIInputStream> stream;
   nsresult rv = mBlob->GetInternalStream(getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsCOMPtr<nsIIPCSerializableInputStream> serializableStream;
 
   nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryInterface(mBlob);
@@ -1575,18 +1341,16 @@ Blob<Parent>::RecvPBlobStreamConstructor
 
 template <>
 bool
 Blob<Child>::RecvPBlobStreamConstructor(StreamType* aActor)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mBlob);
   MOZ_ASSERT(!mRemoteBlob);
-  MOZ_ASSERT(!mRemoteMemoryBlob);
-  MOZ_ASSERT(!mRemoteMultipartBlob);
 
   nsCOMPtr<nsIInputStream> stream;
   nsresult rv = mBlob->GetInternalStream(getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsCOMPtr<nsIIPCSerializableInputStream> serializable =
     do_QueryInterface(stream);
   if (!serializable) {
@@ -1598,35 +1362,16 @@ Blob<Child>::RecvPBlobStreamConstructor(
   serializable->Serialize(params);
 
   MOZ_ASSERT(params.type() != InputStreamParams::T__None);
 
   return aActor->Send__delete__(aActor, params);
 }
 
 template <ActorFlavorEnum ActorFlavor>
-bool
-Blob<ActorFlavor>::RecvPBlobConstructor(ProtocolType* aActor,
-                                        const BlobConstructorParams& aParams)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  Blob<ActorFlavor>* subBlobActor = static_cast<Blob<ActorFlavor>*>(aActor);
-
-  if (!subBlobActor->ManagerIs(this)) {
-    // Somebody screwed up!
-    return false;
-  }
-
-  nsCOMPtr<nsIDOMBlob> blob = subBlobActor->GetBlob();
-  static_cast<nsDOMMultipartFile*>(mBlob)->AddBlob(blob);
-  return true;
-}
-
-template <ActorFlavorEnum ActorFlavor>
 typename Blob<ActorFlavor>::StreamType*
 Blob<ActorFlavor>::AllocPBlobStream()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return new InputStreamActor<ActorFlavor>();
 }
 
 template <ActorFlavorEnum ActorFlavor>
@@ -1634,61 +1379,24 @@ bool
 Blob<ActorFlavor>::DeallocPBlobStream(StreamType* aActor)
 {
   MOZ_ASSERT(NS_IsMainThread());
   delete aActor;
   return true;
 }
 
 template <ActorFlavorEnum ActorFlavor>
-typename Blob<ActorFlavor>::ProtocolType*
-Blob<ActorFlavor>::AllocPBlob(const BlobConstructorParams& aParams)
-{
-  Blob<ActorFlavor>* actor = Blob<ActorFlavor>::Create(aParams);
-  actor->SetManager(this);
-  return actor;
-}
-
-template <ActorFlavorEnum ActorFlavor>
-bool
-Blob<ActorFlavor>::DeallocPBlob(ProtocolType* aActor)
-{
-  delete aActor;
-  return true;
-}
-
-template <ActorFlavorEnum ActorFlavor>
 NS_IMPL_ADDREF_INHERITED(RemoteBlob<ActorFlavor>, nsDOMFile)
 
 template <ActorFlavorEnum ActorFlavor>
 NS_IMPL_RELEASE_INHERITED(RemoteBlob<ActorFlavor>, nsDOMFile)
 
 template <ActorFlavorEnum ActorFlavor>
 NS_IMPL_QUERY_INTERFACE_INHERITED1(RemoteBlob<ActorFlavor>, nsDOMFile,
                                                             nsIRemoteBlob)
 
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_ADDREF_INHERITED(RemoteMemoryBlob<ActorFlavor>, nsDOMMemoryFile)
-
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_RELEASE_INHERITED(RemoteMemoryBlob<ActorFlavor>, nsDOMMemoryFile)
-
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_QUERY_INTERFACE_INHERITED1(RemoteMemoryBlob<ActorFlavor>, nsDOMMemoryFile,
-                                                                  nsIRemoteBlob)
-
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_ADDREF_INHERITED(RemoteMultipartBlob<ActorFlavor>, nsDOMMultipartFile)
-
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_RELEASE_INHERITED(RemoteMultipartBlob<ActorFlavor>, nsDOMMultipartFile)
-
-template <ActorFlavorEnum ActorFlavor>
-NS_IMPL_QUERY_INTERFACE_INHERITED1(RemoteMultipartBlob<ActorFlavor>, nsDOMMultipartFile,
-                                                                     nsIRemoteBlob)
-
 // Explicit instantiation of both classes.
 template class Blob<Parent>;
 template class Blob<Child>;
 
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/Blob.h
+++ b/dom/ipc/Blob.h
@@ -33,229 +33,204 @@ namespace ipc {
 
 enum ActorFlavorEnum
 {
   Parent = 0,
   Child
 };
 
 template <ActorFlavorEnum>
-struct BlobTraits
-{ };
+struct BlobTraits;
 
 template <>
 struct BlobTraits<Parent>
 {
   typedef mozilla::dom::PBlobParent ProtocolType;
   typedef mozilla::dom::PBlobStreamParent StreamType;
-  typedef mozilla::dom::PContentParent ContentManagerType;
+  typedef mozilla::dom::ParentBlobConstructorParams ConstructorParamsType;
+  typedef mozilla::dom::ChildBlobConstructorParams
+          OtherSideConstructorParamsType;
   typedef mozilla::dom::ContentParent ConcreteContentManagerType;
-  typedef ProtocolType BlobManagerType;
 
   // BaseType on the parent side is a bit more complicated than for the child
   // side. In the case of nsIInputStreams backed by files we need to ensure that
   // the files are actually opened and closed on a background thread before we
   // can send their file handles across to the child. The child process could
   // crash during this process so we need to make sure we cancel the intended
   // response in such a case. We do that by holding an array of
   // nsRevocableEventPtr. If the child crashes then this actor will be destroyed
   // and the nsRevocableEventPtr destructor will cancel any stream events that
   // are currently in flight.
   class BaseType : public ProtocolType
   {
+  public:
+    static const ChildBlobConstructorParams&
+    GetBlobConstructorParams(const ConstructorParamsType& aParams)
+    {
+      return aParams.blobParams();
+    }
+
+    static void
+    SetBlobConstructorParams(ConstructorParamsType& aParams,
+                             const ChildBlobConstructorParams& aBlobParams)
+    {
+      aParams.blobParams() = aBlobParams;
+      aParams.optionalInputStreamParams() = mozilla::void_t();
+    }
+
+    static void
+    SetBlobConstructorParams(OtherSideConstructorParamsType& aParams,
+                             const ChildBlobConstructorParams& aBlobParams)
+    {
+      aParams = aBlobParams;
+    }
+
   protected:
     BaseType();
     virtual ~BaseType();
 
     class OpenStreamRunnable;
     friend class OpenStreamRunnable;
 
     void
     NoteRunnableCompleted(OpenStreamRunnable* aRunnable);
 
     nsTArray<nsRevocableEventPtr<OpenStreamRunnable> > mOpenStreamRunnables;
   };
-
-  static void*
-  Allocate(size_t aSize)
-  {
-    // We want fallible allocation in the parent.
-    return moz_malloc(aSize);
-  }
 };
 
 template <>
 struct BlobTraits<Child>
 {
   typedef mozilla::dom::PBlobChild ProtocolType;
   typedef mozilla::dom::PBlobStreamChild StreamType;
-  typedef mozilla::dom::PContentChild ContentManagerType;
+  typedef mozilla::dom::ChildBlobConstructorParams ConstructorParamsType;
+  typedef mozilla::dom::ParentBlobConstructorParams
+          OtherSideConstructorParamsType;
   typedef mozilla::dom::ContentChild ConcreteContentManagerType;
-  typedef ProtocolType BlobManagerType;
+
 
   class BaseType : public ProtocolType
   {
+  public:
+    static const ChildBlobConstructorParams&
+    GetBlobConstructorParams(const ConstructorParamsType& aParams)
+    {
+      return aParams;
+    }
+
+    static void
+    SetBlobConstructorParams(ConstructorParamsType& aParams,
+                             const ChildBlobConstructorParams& aBlobParams)
+    {
+      aParams = aBlobParams;
+    }
+
+    static void
+    SetBlobConstructorParams(OtherSideConstructorParamsType& aParams,
+                             const ChildBlobConstructorParams& aBlobParams)
+    {
+      aParams.blobParams() = aBlobParams;
+      aParams.optionalInputStreamParams() = mozilla::void_t();
+    }
+
   protected:
     BaseType()
     { }
 
     virtual ~BaseType()
     { }
   };
-
-  static void*
-  Allocate(size_t aSize)
-  {
-    // We want infallible allocation in the child.
-    return moz_xmalloc(aSize);
-  }
 };
 
 template <ActorFlavorEnum>
-class RemoteBlobBase;
-template <ActorFlavorEnum>
 class RemoteBlob;
-template <ActorFlavorEnum>
-class RemoteMemoryBlob;
-template <ActorFlavorEnum>
-class RemoteMultipartBlob;
 
 template <ActorFlavorEnum ActorFlavor>
 class Blob : public BlobTraits<ActorFlavor>::BaseType
 {
-  friend class RemoteBlobBase<ActorFlavor>;
+  friend class RemoteBlob<ActorFlavor>;
 
 public:
   typedef typename BlobTraits<ActorFlavor>::ProtocolType ProtocolType;
   typedef typename BlobTraits<ActorFlavor>::StreamType StreamType;
+  typedef typename BlobTraits<ActorFlavor>::ConstructorParamsType
+          ConstructorParamsType;
+  typedef typename BlobTraits<ActorFlavor>::OtherSideConstructorParamsType
+          OtherSideConstructorParamsType;
   typedef typename BlobTraits<ActorFlavor>::BaseType BaseType;
-  typedef typename BlobTraits<ActorFlavor>::ContentManagerType ContentManagerType;
-  typedef typename BlobTraits<ActorFlavor>::BlobManagerType BlobManagerType;
   typedef RemoteBlob<ActorFlavor> RemoteBlobType;
-  typedef RemoteMemoryBlob<ActorFlavor> RemoteMemoryBlobType;
-  typedef RemoteMultipartBlob<ActorFlavor> RemoteMultipartBlobType;
   typedef mozilla::ipc::IProtocolManager<
                       mozilla::ipc::RPCChannel::RPCListener>::ActorDestroyReason
           ActorDestroyReason;
-  typedef mozilla::dom::BlobConstructorParams BlobConstructorParams;
 
 protected:
   nsIDOMBlob* mBlob;
   RemoteBlobType* mRemoteBlob;
-  RemoteMemoryBlobType* mRemoteMemoryBlob;
-  RemoteMultipartBlobType* mRemoteMultipartBlob;
-
-  ContentManagerType* mContentManager;
-  BlobManagerType* mBlobManager;
-
   bool mOwnsBlob;
   bool mBlobIsFile;
 
 public:
   // This create function is called on the sending side.
   static Blob*
   Create(nsIDOMBlob* aBlob)
   {
     return new Blob(aBlob);
   }
 
   // This create function is called on the receiving side.
   static Blob*
-  Create(const BlobConstructorParams& aParams);
+  Create(const ConstructorParamsType& aParams);
 
   // Get the blob associated with this actor. This may always be called on the
   // sending side. It may also be called on the receiving side unless this is a
   // "mystery" blob that has not yet received a SetMysteryBlobInfo() call.
   already_AddRefed<nsIDOMBlob>
   GetBlob();
 
   // Use this for files.
   bool
   SetMysteryBlobInfo(const nsString& aName, const nsString& aContentType,
                      uint64_t aLength, uint64_t aLastModifiedDate);
 
   // Use this for non-file blobs.
   bool
   SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength);
 
-  ProtocolType*
-  ConstructPBlobOnManager(ProtocolType* aActor,
-                          const BlobConstructorParams& aParams);
-
-  bool
-  ManagerIs(const ContentManagerType* aManager) const
-  {
-    return aManager == mContentManager;
-  }
-
-  bool
-  ManagerIs(const BlobManagerType* aManager) const
-  {
-    return aManager == mBlobManager;
-  }
-
-#ifdef DEBUG
-  bool
-  HasManager() const
-  {
-    return !!mContentManager || !!mBlobManager;
-  }
-#endif
-
-  void
-  SetManager(ContentManagerType* aManager);
-  void
-  SetManager(BlobManagerType* aManager);
-
-  void
-  PropagateManager(Blob<ActorFlavor>* aActor) const;
-
 private:
   // This constructor is called on the sending side.
   Blob(nsIDOMBlob* aBlob);
 
-  // These constructors are called on the receiving side.
-  Blob(nsRefPtr<RemoteBlobType>& aBlob,
-       bool aIsFile);
-  Blob(nsRefPtr<RemoteMemoryBlobType>& aBlob,
-       bool aIsFile);
-  Blob(nsRefPtr<RemoteMultipartBlobType>& aBlob,
-       bool aIsFile);
+  // This constructor is called on the receiving side.
+  Blob(const ConstructorParamsType& aParams);
+
+  static already_AddRefed<RemoteBlobType>
+  CreateRemoteBlob(const ConstructorParamsType& aParams);
 
   void
   NoteDyingRemoteBlob();
 
   // These methods are only called by the IPDL message machinery.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool
   RecvResolveMystery(const ResolveMysteryParams& aParams) MOZ_OVERRIDE;
 
   virtual bool
   RecvPBlobStreamConstructor(StreamType* aActor) MOZ_OVERRIDE;
 
-  virtual bool
-  RecvPBlobConstructor(ProtocolType* aActor,
-                       const BlobConstructorParams& aParams) MOZ_OVERRIDE;
-
   virtual StreamType*
   AllocPBlobStream() MOZ_OVERRIDE;
 
   virtual bool
   DeallocPBlobStream(StreamType* aActor) MOZ_OVERRIDE;
-
-  virtual ProtocolType*
-  AllocPBlob(const BlobConstructorParams& params) MOZ_OVERRIDE;
-
-  virtual bool
-  DeallocPBlob(ProtocolType* actor) MOZ_OVERRIDE;
 };
 
 } // namespace ipc
 
 typedef mozilla::dom::ipc::Blob<mozilla::dom::ipc::Child> BlobChild;
 typedef mozilla::dom::ipc::Blob<mozilla::dom::ipc::Parent> BlobParent;
 
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_ipc_Blob_h
+#endif // mozilla_dom_ipc_Blob_h
\ No newline at end of file
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -86,16 +86,17 @@
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
 
 #include "mozilla/dom/indexedDB/PIndexedDBChild.h"
 #include "mozilla/dom/sms/SmsChild.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 #include "mozilla/dom/bluetooth/PBluetoothChild.h"
+#include "mozilla/ipc/InputStreamUtils.h"
 
 #include "nsDOMFile.h"
 #include "nsIRemoteBlob.h"
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
 #include "URIUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsContentUtils.h"
@@ -557,114 +558,36 @@ ContentChild::DeallocPBrowser(PBrowserCh
     TabChild* child = static_cast<TabChild*>(iframe);
     NS_RELEASE(child);
     return true;
 }
 
 PBlobChild*
 ContentChild::AllocPBlob(const BlobConstructorParams& aParams)
 {
-  BlobChild* actor = BlobChild::Create(aParams);
-  actor->SetManager(this);
-  return actor;
+  return BlobChild::Create(aParams);
 }
 
 bool
 ContentChild::DeallocPBlob(PBlobChild* aActor)
 {
   delete aActor;
   return true;
 }
 
-bool
-ContentChild::GetParamsForBlob(nsDOMFileBase* aBlob,
-                               BlobConstructorParams* aOutParams)
-{
-  BlobConstructorParams resultParams;
-
-  if (!(aBlob->IsMemoryBacked() || aBlob->GetSubBlobs()) &&
-      (aBlob->IsSizeUnknown() || aBlob->IsDateUnknown())) {
-    // We don't want to call GetSize or GetLastModifiedDate
-    // yet since that may stat a file on the main thread
-    // here. Instead we'll learn the size lazily from the
-    // other process.
-    resultParams = MysteryBlobConstructorParams();
-  }
-  else {
-    BlobOrFileConstructorParams params;
-
-    nsString contentType;
-    nsresult rv = aBlob->GetType(contentType);
-    NS_ENSURE_SUCCESS(rv, false);
-
-    uint64_t length;
-    rv = aBlob->GetSize(&length);
-    NS_ENSURE_SUCCESS(rv, false);
-
-    nsCOMPtr<nsIDOMFile> file;
-    static_cast<nsIDOMBlob*>(aBlob)->QueryInterface(NS_GET_IID(nsIDOMFile),
-                                          (void**)getter_AddRefs(file));
-    if (file) {
-      FileBlobConstructorParams fileParams;
-
-      rv = file->GetName(fileParams.name());
-      NS_ENSURE_SUCCESS(rv, false);
-
-      rv = file->GetMozLastModifiedDate(&fileParams.modDate());
-      NS_ENSURE_SUCCESS(rv, false);
-
-      fileParams.contentType() = contentType;
-      fileParams.length() = length;
-
-      params = fileParams;
-    } else {
-      NormalBlobConstructorParams blobParams;
-      blobParams.contentType() = contentType;
-      blobParams.length() = length;
-      params = blobParams;
-    }
-
-    MOZ_ASSERT(!(aBlob->IsMemoryBacked() &&
-                 aBlob->GetSubBlobs()), "Can't be both!");
-
-    if (aBlob->IsMemoryBacked()) {
-      const nsDOMMemoryFile* memoryBlob =
-        static_cast<const nsDOMMemoryFile*>(aBlob);
-
-      InfallibleTArray<uint8_t> data;
-      data.SetLength(memoryBlob->GetLength());
-      memcpy(data.Elements(), memoryBlob->GetData(), memoryBlob->GetLength());
-
-      MemoryBlobOrFileConstructorParams memoryParams(params, data);
-      resultParams = memoryParams;
-    }
-    else if (aBlob->GetSubBlobs()) {
-      MultipartBlobOrFileConstructorParams multipartParams(params);
-      resultParams = multipartParams;
-    }
-    else {
-      resultParams = BlobConstructorNoMultipartParams(params);
-    }
-  }
-
-  *aOutParams = resultParams;
-
-  return true;
-}
-
 BlobChild*
 ContentChild::GetOrCreateActorForBlob(nsIDOMBlob* aBlob)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aBlob, "Null pointer!");
 
   // XXX This is only safe so long as all blob implementations in our tree
   //     inherit nsDOMFileBase. If that ever changes then this will need to grow
   //     a real interface or something.
-  nsDOMFileBase* blob = static_cast<nsDOMFileBase*>(aBlob);
+  const nsDOMFileBase* blob = static_cast<nsDOMFileBase*>(aBlob);
 
   // All blobs shared between processes must be immutable.
   nsCOMPtr<nsIMutable> mutableBlob = do_QueryInterface(aBlob);
   if (!mutableBlob || NS_FAILED(mutableBlob->SetMutable(false))) {
     NS_WARNING("Failed to make blob immutable!");
     return nullptr;
   }
 
@@ -672,44 +595,73 @@ ContentChild::GetOrCreateActorForBlob(ns
   if (remoteBlob) {
     BlobChild* actor =
       static_cast<BlobChild*>(static_cast<PBlobChild*>(remoteBlob->GetPBlob()));
     NS_ASSERTION(actor, "Null actor?!");
 
     return actor;
   }
 
-  BlobConstructorParams params;
-  if (!GetParamsForBlob(blob, &params)) {
-    return nullptr;
+  ParentBlobConstructorParams params;
+
+  if (blob->IsSizeUnknown() || blob->IsDateUnknown()) {
+    // We don't want to call GetSize or GetLastModifiedDate
+    // yet since that may stat a file on the main thread
+    // here. Instead we'll learn the size lazily from the
+    // other process.
+    params.blobParams() = MysteryBlobConstructorParams();
+    params.optionalInputStreamParams() = void_t();
   }
+  else {
+    nsString contentType;
+    nsresult rv = aBlob->GetType(contentType);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    uint64_t length;
+    rv = aBlob->GetSize(&length);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    nsCOMPtr<nsIInputStream> stream;
+    rv = aBlob->GetInternalStream(getter_AddRefs(stream));
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    InputStreamParams inputStreamParams;
+    SerializeInputStream(stream, inputStreamParams);
+
+    params.optionalInputStreamParams() = inputStreamParams;
+
+    nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
+    if (file) {
+      FileBlobConstructorParams fileParams;
+
+      rv = file->GetName(fileParams.name());
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
+      rv = file->GetMozLastModifiedDate(&fileParams.modDate());
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
+      fileParams.contentType() = contentType;
+      fileParams.length() = length;
+
+      params.blobParams() = fileParams;
+    } else {
+      NormalBlobConstructorParams blobParams;
+      blobParams.contentType() = contentType;
+      blobParams.length() = length;
+      params.blobParams() = blobParams;
+    }
+    }
 
   BlobChild* actor = BlobChild::Create(aBlob);
   NS_ENSURE_TRUE(actor, nullptr);
 
-  SendPBlobConstructor(actor, params);
-
-  actor->SetManager(this);
-
-  if (const nsTArray<nsCOMPtr<nsIDOMBlob> >* subBlobs = blob->GetSubBlobs()) {
-    for (uint32_t i = 0; i < subBlobs->Length(); ++i) {
-      BlobConstructorParams subParams;
-      nsDOMFileBase* subBlob =
-        static_cast<nsDOMFileBase*>(subBlobs->ElementAt(i).get());
-      if (!GetParamsForBlob(subBlob, &subParams)) {
+  if (!SendPBlobConstructor(actor, params)) {
         return nullptr;
       }
 
-      BlobChild* subActor = BlobChild::Create(aBlob);
-      actor->SendPBlobConstructor(subActor, subParams);
-
-      subActor->SetManager(actor);
-    }
-  }
-
   return actor;
 }
 
 PCrashReporterChild*
 ContentChild::AllocPCrashReporter(const mozilla::dom::NativeThreadId& id,
                                   const uint32_t& processType)
 {
 #ifdef MOZ_CRASHREPORTER
@@ -1180,9 +1132,9 @@ ContentChild::RecvFileSystemUpdate(const
     unused << aName;
     unused << aState;
     unused << aMountGeneration;
 #endif
     return true;
 }
 
 } // namespace dom
-} // namespace mozilla
+} // namespace mozilla
\ No newline at end of file
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -12,17 +12,16 @@
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/ipc/Blob.h"
 
 #include "nsTArray.h"
 #include "nsIConsoleListener.h"
 
 struct ChromePackage;
 class nsIDOMBlob;
-class nsDOMFileBase;
 class nsIObserver;
 struct ResourceMapping;
 struct OverrideMapping;
 
 namespace mozilla {
 
 namespace ipc {
 class OptionalURIParams;
@@ -198,18 +197,16 @@ public:
     // cache the value
     nsString &GetIndexedDBPath();
 
     uint64_t GetID() { return mID; }
 
     bool IsForApp() { return mIsForApp; }
     bool IsForBrowser() { return mIsForBrowser; }
 
-    bool GetParamsForBlob(nsDOMFileBase* aBlob,
-                          BlobConstructorParams* aOutParams);
     BlobChild* GetOrCreateActorForBlob(nsIDOMBlob* aBlob);
 
 private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     /**
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1455,168 +1455,103 @@ ContentParent::DeallocPDeviceStorageRequ
   DeviceStorageRequestParent *parent = static_cast<DeviceStorageRequestParent*>(doomed);
   NS_RELEASE(parent);
   return true;
 }
 
 PBlobParent*
 ContentParent::AllocPBlob(const BlobConstructorParams& aParams)
 {
-  BlobParent* actor = BlobParent::Create(aParams);
-  actor->SetManager(this);
-  return actor;
+  return BlobParent::Create(aParams);
 }
 
 bool
 ContentParent::DeallocPBlob(PBlobParent* aActor)
 {
   delete aActor;
   return true;
 }
 
-bool
-ContentParent::GetParamsForBlob(nsDOMFileBase* aBlob,
-                                BlobConstructorParams* aOutParams)
-{
-  BlobConstructorParams resultParams;
-
-  if (!(aBlob->IsMemoryBacked() || aBlob->GetSubBlobs()) &&
-      (aBlob->IsSizeUnknown() || aBlob->IsDateUnknown())) {
-    // We don't want to call GetSize or GetLastModifiedDate
-    // yet since that may stat a file on the main thread