bug 490705 - Support Audio Data API: Get, Manipulate, Play & Save. r=smaug+kinetik+peterv, sr=vlad, a=vlad
☠☠ backed out by eaa833618eaa ☠ ☠
authorDavid Humphrey <david.humphrey@senecac.on.ca>
Tue, 17 Aug 2010 09:40:00 -0400
changeset 50809 1362f0ca86d2e16b0341e45e8d5d9be8345a44f0
parent 50808 ca457b5758e018c5a2197b3ba71bf604205c8254
child 50811 444cb6c82ea18193964b3495fc1e43101d5a6d1d
child 50821 eaa833618eaab81c9a1aad2516434196b47e9664
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, vlad, vlad
bugs490705
milestone2.0b5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 490705 - Support Audio Data API: Get, Manipulate, Play & Save. r=smaug+kinetik+peterv, sr=vlad, a=vlad
content/base/src/nsContentUtils.cpp
content/base/src/nsGkAtomList.h
content/base/src/nsNodeUtils.cpp
content/events/public/nsIEventListenerManager.h
content/events/public/nsIPrivateDOMEvent.h
content/events/src/Makefile.in
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsDOMNotifyAudioAvailableEvent.cpp
content/events/src/nsDOMNotifyAudioAvailableEvent.h
content/events/src/nsEventDispatcher.cpp
content/events/src/nsEventListenerManager.cpp
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLAudioElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/media/Makefile.in
content/media/nsAudioAvailableEventManager.cpp
content/media/nsAudioAvailableEventManager.h
content/media/nsAudioStream.cpp
content/media/nsAudioStream.h
content/media/nsBuiltinDecoder.cpp
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/nsBuiltinDecoderStateMachine.h
content/media/nsMediaDecoder.cpp
content/media/nsMediaDecoder.h
content/media/test/Makefile.in
content/media/test/file_a4_tone.ogg
content/media/test/file_audio_event_adopt_iframe.html
content/media/test/test_a4_tone.html
content/media/test/test_audio_event_adopt.html
content/media/test/test_audiowrite.html
content/media/wave/nsWaveDecoder.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/base/nsGlobalWindow.cpp
dom/base/nsPIDOMWindow.h
dom/interfaces/events/Makefile.in
dom/interfaces/events/nsIDOMNotifyAudioAvailableEvent.idl
dom/interfaces/html/nsIDOMHTMLAudioElement.idl
dom/interfaces/html/nsIDOMHTMLMediaElement.idl
js/src/xpconnect/src/dom_quickstubs.qsconf
widget/public/nsGUIEvent.h
xpcom/glue/nsCycleCollectionParticipant.h
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -664,16 +664,17 @@ nsContentUtils::InitializeEventTable() {
     { nsGkAtoms::oncanplaythrough,              NS_CANPLAYTHROUGH, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onseeking,                     NS_SEEKING, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onseeked,                      NS_SEEKED, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::ontimeupdate,                  NS_TIMEUPDATE, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onended,                       NS_ENDED, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onratechange,                  NS_RATECHANGE, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::ondurationchange,              NS_DURATIONCHANGE, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onvolumechange,                NS_VOLUMECHANGE, EventNameType_HTML, NS_EVENT_NULL },
+    { nsGkAtoms::onMozAudioAvailable,           NS_MOZAUDIOAVAILABLE, EventNameType_None, NS_EVENT_NULL },
 #endif // MOZ_MEDIA
     { nsGkAtoms::onMozAfterPaint,               NS_AFTERPAINT, EventNameType_None, NS_EVENT },
     { nsGkAtoms::onMozBeforePaint,              NS_BEFOREPAINT, EventNameType_None, NS_EVENT_NULL },
 
     { nsGkAtoms::onMozScrolledAreaChanged,      NS_SCROLLEDAREACHANGED, EventNameType_None, NS_SCROLLAREA_EVENT },
 
     // Simple gesture events
     { nsGkAtoms::onMozSwipeGesture,             NS_SIMPLE_GESTURE_SWIPE, EventNameType_None, NS_SIMPLE_GESTURE_EVENT },
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1660,16 +1660,17 @@ GK_ATOM(oncanplay, "oncanplay")
 GK_ATOM(oncanplaythrough, "oncanplaythrough")
 GK_ATOM(onseeking, "onseeking")
 GK_ATOM(onseeked, "onseeked")
 GK_ATOM(ontimeupdate, "ontimeupdate")
 GK_ATOM(onended, "onended")
 GK_ATOM(onratechange, "onratechange")
 GK_ATOM(ondurationchange, "ondurationchange")
 GK_ATOM(onvolumechange, "onvolumechange")
+GK_ATOM(onMozAudioAvailable, "onMozAudioAvailable")
 GK_ATOM(loadstart, "loadstart")
 GK_ATOM(progress, "progress")
 GK_ATOM(suspend, "suspend")
 GK_ATOM(emptied, "emptied")
 GK_ATOM(stalled, "stalled")
 GK_ATOM(play, "play")
 GK_ATOM(pause, "pause")
 GK_ATOM(loadedmetadata, "loadedmetadata")
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -506,16 +506,21 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
       nsPIDOMWindow* window = newDoc->GetInnerWindow();
       if (window) {
         nsIEventListenerManager* elm = aNode->GetListenerManager(PR_FALSE);
         if (elm) {
           window->SetMutationListeners(elm->MutationListenerBits());
           if (elm->MayHavePaintEventListener()) {
             window->SetHasPaintEventListeners();
           }
+#ifdef MOZ_MEDIA
+          if (elm->MayHaveAudioAvailableEventListener()) {
+            window->SetHasAudioAvailableEventListeners();
+          }
+#endif
         }
       }
     }
 
 #ifdef MOZ_MEDIA
     if (wasRegistered && oldDoc != newDoc) {
       nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aNode));
       if (domMediaElem) {
--- a/content/events/public/nsIEventListenerManager.h
+++ b/content/events/public/nsIEventListenerManager.h
@@ -63,16 +63,17 @@ class nsIEventListenerManager : public n
 
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IEVENTLISTENERMANAGER_IID)
 
   nsIEventListenerManager() : mMayHavePaintEventListener(PR_FALSE),
     mMayHaveMutationListeners(PR_FALSE),
     mMayHaveCapturingListeners(PR_FALSE),
     mMayHaveSystemGroupListeners(PR_FALSE),
+    mMayHaveAudioAvailableEventListener(PR_FALSE),
     mNoListenerForEvent(0)
   {}
 
   /**
   * Sets events listeners of all types.
   * @param an event listener
   */
   NS_IMETHOD AddEventListenerByIID(nsIDOMEventListener *aListener,
@@ -200,22 +201,30 @@ public:
 
 
   /**
    * Returns PR_TRUE if there may be a paint event listener registered,
    * PR_FALSE if there definitely isn't.
    */
   PRBool MayHavePaintEventListener() { return mMayHavePaintEventListener; }
 
+  /**
+   * Returns PR_TRUE if there may be a MozAudioAvailable event listener registered,
+   * PR_FALSE if there definitely isn't.
+   */
+  PRBool MayHaveAudioAvailableEventListener() { return mMayHaveAudioAvailableEventListener; }
+
+
 protected:
   PRUint32 mMayHavePaintEventListener : 1;
   PRUint32 mMayHaveMutationListeners : 1;
   PRUint32 mMayHaveCapturingListeners : 1;
   PRUint32 mMayHaveSystemGroupListeners : 1;
-  PRUint32 mNoListenerForEvent : 28;
+  PRUint32 mMayHaveAudioAvailableEventListener : 1;
+  PRUint32 mNoListenerForEvent : 27;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIEventListenerManager,
                               NS_IEVENTLISTENERMANAGER_IID)
 
 nsresult
 NS_NewEventListenerManager(nsIEventListenerManager** aInstancePtrResult);
 
--- a/content/events/public/nsIPrivateDOMEvent.h
+++ b/content/events/public/nsIPrivateDOMEvent.h
@@ -117,16 +117,23 @@ nsresult
 NS_NewDOMProgressEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsEvent* aEvent);
 // This empties aInvalidateRequests.
 nsresult
 NS_NewDOMNotifyPaintEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext,
                           nsEvent* aEvent,
                           PRUint32 aEventType = 0,
                           nsInvalidateRequestList* aInvalidateRequests = nsnull);
 nsresult
+NS_NewDOMAudioAvailableEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext,
+                             nsEvent* aEvent,
+                             PRUint32 aEventType = 0,
+                             float* aFrameBuffer = nsnull,
+                             PRUint32 aFrameBufferLength = 0,
+                             float aTime = 0);
+nsresult
 NS_NewDOMSimpleGestureEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsSimpleGestureEvent* aEvent);
 nsresult
 NS_NewDOMScrollAreaEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsScrollAreaEvent* aEvent);
 nsresult
 NS_NewDOMTransitionEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsTransitionEvent* aEvent);
 nsresult
 NS_NewDOMCloseEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsEvent* aEvent);
 nsresult
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -73,16 +73,17 @@ CPPSRCS		= \
 		nsPLDOMEvent.cpp \
 		nsEventDispatcher.cpp \
 		nsIMEStateManager.cpp \
 		nsContentEventHandler.cpp \
 		nsEventListenerService.cpp \
 		nsDOMProgressEvent.cpp \
 		nsDOMDataTransfer.cpp \
 		nsDOMNotifyPaintEvent.cpp \
+		nsDOMNotifyAudioAvailableEvent.cpp \
 		nsDOMSimpleGestureEvent.cpp \
 		nsDOMMozTouchEvent.cpp \
 		nsDOMEventTargetHelper.cpp \
 		nsDOMScrollAreaEvent.cpp \
 		nsDOMTransitionEvent.cpp \
 		nsDOMPopStateEvent.cpp \
 		nsDOMCloseEvent.cpp \
 		$(NULL)
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -83,17 +83,17 @@ static const char* const sEventNames[] =
 #endif // MOZ_SVG
 #ifdef MOZ_SMIL
   "beginEvent", "endEvent", "repeatEvent",
 #endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
   "loadstart", "progress", "suspend", "emptied", "stalled", "play", "pause",
   "loadedmetadata", "loadeddata", "waiting", "playing", "canplay",
   "canplaythrough", "seeking", "seeked", "timeupdate", "ended", "ratechange",
-  "durationchange", "volumechange",
+  "durationchange", "volumechange", "MozAudioAvailable",
 #endif // MOZ_MEDIA
   "MozAfterPaint",
   "MozBeforePaint",
   "MozSwipeGesture",
   "MozMagnifyGestureStart",
   "MozMagnifyGestureUpdate",
   "MozMagnifyGesture",
   "MozRotateGestureStart",
@@ -1295,16 +1295,18 @@ const char* nsDOMEvent::GetEventName(PRU
   case NS_ENDED:
     return sEventNames[eDOMEvents_ended];
   case NS_RATECHANGE:
     return sEventNames[eDOMEvents_ratechange];
   case NS_DURATIONCHANGE:
     return sEventNames[eDOMEvents_durationchange];
   case NS_VOLUMECHANGE:
     return sEventNames[eDOMEvents_volumechange];
+  case NS_MOZAUDIOAVAILABLE:
+    return sEventNames[eDOMEvents_mozaudioavailable];
 #endif
   case NS_AFTERPAINT:
     return sEventNames[eDOMEvents_afterpaint];
   case NS_BEFOREPAINT:
     return sEventNames[eDOMEvents_beforepaint];
   case NS_SIMPLE_GESTURE_SWIPE:
     return sEventNames[eDOMEvents_MozSwipeGesture];
   case NS_SIMPLE_GESTURE_MAGNIFY_START:
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -163,16 +163,17 @@ public:
     eDOMEvents_canplaythrough,
     eDOMEvents_seeking,
     eDOMEvents_seeked,
     eDOMEvents_timeupdate,
     eDOMEvents_ended,
     eDOMEvents_ratechange,
     eDOMEvents_durationchange,
     eDOMEvents_volumechange,
+    eDOMEvents_mozaudioavailable,
 #endif
     eDOMEvents_afterpaint,
     eDOMEvents_beforepaint,
     eDOMEvents_MozSwipeGesture,
     eDOMEvents_MozMagnifyGestureStart,
     eDOMEvents_MozMagnifyGestureUpdate,
     eDOMEvents_MozMagnifyGesture,
     eDOMEvents_MozRotateGestureStart,
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMNotifyAudioAvailableEvent.cpp
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Humphrey <david.humphrey@senecac.on.ca>
+ *  Yury Delendik
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMNotifyAudioAvailableEvent.h"
+#include "jstypedarray.h"
+
+nsDOMNotifyAudioAvailableEvent::nsDOMNotifyAudioAvailableEvent(nsPresContext* aPresContext,
+                                                               nsEvent* aEvent,
+                                                               PRUint32 aEventType,
+                                                               float* aFrameBuffer,
+                                                               PRUint32 aFrameBufferLength,
+                                                               float aTime)
+  : nsDOMEvent(aPresContext, aEvent),
+    mFrameBuffer(aFrameBuffer),
+    mFrameBufferLength(aFrameBufferLength),
+    mTime(aTime),
+    mCachedArray(nsnull),
+    mAllowAudioData(PR_FALSE)
+{
+  if (mEvent) {
+    mEvent->message = aEventType;
+  }
+}
+
+DOMCI_DATA(NotifyAudioAvailableEvent, nsDOMNotifyAudioAvailableEvent)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMNotifyAudioAvailableEvent)
+
+NS_IMPL_ADDREF_INHERITED(nsDOMNotifyAudioAvailableEvent, nsDOMEvent)
+NS_IMPL_RELEASE_INHERITED(nsDOMNotifyAudioAvailableEvent, nsDOMEvent)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_BEGIN(nsDOMNotifyAudioAvailableEvent)
+  if (tmp->mCachedArray) {
+    NS_DROP_JS_OBJECTS(tmp, nsDOMNotifyAudioAvailableEvent);
+    tmp->mCachedArray = nsnull;
+  }
+NS_IMPL_CYCLE_COLLECTION_ROOT_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMNotifyAudioAvailableEvent, nsDOMEvent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMNotifyAudioAvailableEvent, nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMNotifyAudioAvailableEvent)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedArray)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMNotifyAudioAvailableEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMNotifyAudioAvailableEvent)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NotifyAudioAvailableEvent)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
+
+nsDOMNotifyAudioAvailableEvent::~nsDOMNotifyAudioAvailableEvent()
+{
+  if (mCachedArray) {
+    NS_DROP_JS_OBJECTS(this, nsDOMNotifyAudioAvailableEvent);
+    mCachedArray = nsnull;
+  }
+}
+
+NS_IMETHODIMP
+nsDOMNotifyAudioAvailableEvent::GetFrameBuffer(JSContext* aCx, jsval* aResult)
+{
+  if (!mAllowAudioData) {
+    // Media is not same-origin, don't allow the data out.
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  if (mCachedArray) {
+    *aResult = OBJECT_TO_JSVAL(mCachedArray);
+    return NS_OK;
+  }
+
+  // Cache this array so we don't recreate on next call.
+  NS_HOLD_JS_OBJECTS(this, nsDOMNotifyAudioAvailableEvent);
+
+  mCachedArray = js_CreateTypedArray(aCx, js::TypedArray::TYPE_FLOAT32, mFrameBufferLength);
+  if (!mCachedArray) {
+    NS_DROP_JS_OBJECTS(this, nsDOMNotifyAudioAvailableEvent);
+    NS_ERROR("Failed to get audio signal!");
+    return NS_ERROR_FAILURE;
+  }
+
+  js::TypedArray *tdest = js::TypedArray::fromJSObject(mCachedArray);
+  memcpy(tdest->data, mFrameBuffer.get(), mFrameBufferLength * sizeof(float));
+
+  *aResult = OBJECT_TO_JSVAL(mCachedArray);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMNotifyAudioAvailableEvent::GetTime(float *aRetVal)
+{
+  *aRetVal = mTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMNotifyAudioAvailableEvent::InitAudioAvailableEvent(const nsAString& aType,
+                                                        PRBool aCanBubble,
+                                                        PRBool aCancelable,
+                                                        float* aFrameBuffer,
+                                                        PRUint32 aFrameBufferLength,
+                                                        float aTime,
+                                                        PRBool aAllowAudioData)
+{
+  nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mFrameBuffer = aFrameBuffer;
+  mFrameBufferLength = aFrameBufferLength;
+  mTime = aTime;
+  mAllowAudioData = aAllowAudioData;
+  return NS_OK;
+}
+
+nsresult NS_NewDOMAudioAvailableEvent(nsIDOMEvent** aInstancePtrResult,
+                                      nsPresContext* aPresContext,
+                                      nsEvent *aEvent,
+                                      PRUint32 aEventType,
+                                      float* aFrameBuffer,
+                                      PRUint32 aFrameBufferLength,
+                                      float aTime)
+{
+  nsDOMNotifyAudioAvailableEvent* it =
+    new nsDOMNotifyAudioAvailableEvent(aPresContext, aEvent, aEventType,
+                                       aFrameBuffer, aFrameBufferLength, aTime);
+  if (nsnull == it) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return CallQueryInterface(it, aInstancePtrResult);
+}
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMNotifyAudioAvailableEvent.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Humphrey <david.humphrey@senecac.on.ca>
+ *  Yury Delendik
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsDOMNotifyAudioAvailableEvent_h_
+#define nsDOMNotifyAudioAvailableEvent_h_
+
+#include "nsIDOMNotifyAudioAvailableEvent.h"
+#include "nsDOMEvent.h"
+#include "nsPresContext.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsDOMNotifyAudioAvailableEvent : public nsDOMEvent,
+                                       public nsIDOMNotifyAudioAvailableEvent
+{
+public:
+  nsDOMNotifyAudioAvailableEvent(nsPresContext* aPresContext, nsEvent* aEvent,
+                                 PRUint32 aEventType, float * aFrameBuffer,
+                                 PRUint32 aFrameBufferLength, float aTime);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMNotifyAudioAvailableEvent,
+                                                         nsDOMEvent)
+
+  NS_DECL_NSIDOMNOTIFYAUDIOAVAILABLEEVENT
+  NS_FORWARD_NSIDOMEVENT(nsDOMEvent::)
+
+  nsresult NS_NewDOMAudioAvailableEvent(nsIDOMEvent** aInstancePtrResult,
+                                        nsPresContext* aPresContext,
+                                        nsEvent *aEvent,
+                                        PRUint32 aEventType,
+                                        float * aFrameBuffer,
+                                        PRUint32 aFrameBufferLength,
+                                        float aTime);
+
+  ~nsDOMNotifyAudioAvailableEvent();
+
+private:
+  nsAutoArrayPtr<float> mFrameBuffer;
+  PRUint32 mFrameBufferLength;
+  float mTime;
+  JSObject* mCachedArray;
+  PRPackedBool mAllowAudioData;
+};
+
+#endif // nsDOMNotifyAudioAvailableEvent_h_
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -835,13 +835,15 @@ nsEventDispatcher::CreateEvent(nsPresCon
   if (aEventType.LowerCaseEqualsLiteral("scrollareaevent"))
     return NS_NewDOMScrollAreaEvent(aDOMEvent, aPresContext, nsnull);
   // FIXME: Should get spec to say what the right string is here!  This
   // is probably wrong!
   if (aEventType.LowerCaseEqualsLiteral("transitionevent"))
     return NS_NewDOMTransitionEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("popstateevent"))
     return NS_NewDOMPopStateEvent(aDOMEvent, aPresContext, nsnull);
+  if (aEventType.LowerCaseEqualsLiteral("mozaudioavailableevent"))
+    return NS_NewDOMAudioAvailableEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("closeevent"))
     return NS_NewDOMCloseEvent(aDOMEvent, aPresContext, nsnull);
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -469,16 +469,22 @@ nsEventListenerManager::AddEventListener
   }
 
   if (aType == NS_AFTERPAINT) {
     mMayHavePaintEventListener = PR_TRUE;
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (window) {
       window->SetHasPaintEventListeners();
     }
+  } else if (aType == NS_MOZAUDIOAVAILABLE) {
+    mMayHaveAudioAvailableEventListener = PR_TRUE;
+    nsPIDOMWindow* window = GetInnerWindowForTarget();
+    if (window) {
+      window->SetHasAudioAvailableEventListeners();
+    }
   } else if (aType >= NS_MUTATION_START && aType <= NS_MUTATION_END) {
     // For mutation listeners, we need to update the global bit on the DOM window.
     // Otherwise we won't actually fire the mutation event.
     mMayHaveMutationListeners = PR_TRUE;
     // Go from our target to the nearest enclosing DOM window.
     nsPIDOMWindow* window = GetInnerWindowForTarget();
     if (window) {
       // If aType is NS_MUTATION_SUBTREEMODIFIED, we need to listen all
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -45,16 +45,18 @@
 #include "nsIHttpChannel.h"
 #include "nsThreadUtils.h"
 #include "nsIDOMRange.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsILoadGroup.h"
 #include "nsIObserver.h"
 #include "ImageLayers.h"
 
+#include "nsAudioStream.h"
+
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLMediaElement : public nsGenericHTMLElement,
                            public nsIObserver
@@ -122,17 +124,17 @@ public:
    * Call this to reevaluate whether we should start/stop due to our owner
    * document being active or inactive.
    */
   void NotifyOwnerDocumentActivityChanged();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
-  void MetadataLoaded();
+  void MetadataLoaded(PRUint32 aChannels, PRUint32 aRate);
 
   // Called by the video decoder object, on the main thread,
   // when it has read the first frame of the video
   // aResourceFullyLoaded should be true if the resource has been
   // fully loaded and the caller will call ResourceLoaded next.
   void FirstFrameLoaded(PRBool aResourceFullyLoaded);
 
   // Called by the video decoder object, on the main thread,
@@ -181,16 +183,19 @@ public:
   // a static document and we're not actually playing video
   gfxASurface* GetPrintSurface() { return mPrintSurface; }
 
   // Dispatch events
   nsresult DispatchSimpleEvent(const nsAString& aName);
   nsresult DispatchProgressEvent(const nsAString& aName);
   nsresult DispatchAsyncSimpleEvent(const nsAString& aName);
   nsresult DispatchAsyncProgressEvent(const nsAString& aName);
+  nsresult DispatchAudioAvailableEvent(float* aFrameBuffer,
+                                       PRUint32 aFrameBufferLength,
+                                       PRUint64 aTime);
 
   // Called by the decoder when some data has been downloaded or
   // buffering/seeking has ended. aNextFrameAvailable is true when
   // the data for the next frame is available. This method will
   // decide whether to set the ready state to HAVE_CURRENT_DATA,
   // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
   enum NextFrameStatus {
     // The next frame of audio/video is available
@@ -280,16 +285,28 @@ public:
   void NotifyAddedSource();
 
   /**
    * Called when there's been an error fetching the resource. This decides
    * whether it's appropriate to fire an error event.
    */
   void NotifyLoadError();
 
+  /**
+   * Called when data has been written to the underlying audio stream.
+   */
+  void NotifyAudioAvailable(float* aFrameBuffer, PRUint32 aFrameBufferLength,
+                            PRUint64 aTime);
+
+  /**
+   * Called in order to check whether some node (this window, its document,
+   * or content in that document) has a MozAudioAvailable event listener.
+   */
+  PRBool MayHaveAudioAvailableEventListener();
+
   virtual PRBool IsNodeOfType(PRUint32 aFlags) const;
 
   /**
    * Returns the current load ID. Asynchronous events store the ID that was
    * current when they were enqueued, and if it has changed when they come to
    * fire, they consider themselves cancelled, and don't fire.
    */
   PRUint32 GetCurrentLoadID() { return mCurrentLoadID; }
@@ -477,20 +494,33 @@ protected:
 
   // When the load algorithm is waiting for more src/<source>, this denotes
   // what type of waiting we're doing.
   LoadAlgorithmState mLoadWaitStatus;
 
   // Current audio volume
   float mVolume;
 
+  // Current number of audio channels.
+  PRUint32 mChannels;
+
+  // Current audio sample rate.
+  PRUint32 mRate;
+
   // Size of the media. Updated by the decoder on the main thread if
   // it changes. Defaults to a width and height of -1 if not set.
   nsIntSize mMediaSize;
 
+  // An audio stream for writing audio directly from JS.
+  nsAutoPtr<nsAudioStream> mAudioStream;
+
+  // PR_TRUE if MozAudioAvailable events can be safely dispatched, based on
+  // a media and element same-origin check.
+  PRBool mAllowAudioData;
+
   // If true then we have begun downloading the media content.
   // Set to false when completed, or not yet started.
   PRPackedBool mBegun;
 
   // True when the decoder has loaded enough data to display the
   // first frame of the content.
   PRPackedBool mLoadedFirstFrame;
 
@@ -566,12 +596,15 @@ protected:
   // alive while no-one is referencing it but the element may still fire
   // events of its own accord.
   PRPackedBool mHasSelfReference;
 
   // PR_TRUE if we've received a notification that the engine is shutting
   // down.
   PRPackedBool mShuttingDown;
 
+  // PR_TRUE if a same-origin check has been done for the media element and resource.
+  PRPackedBool mMediaSecurityVerified;
+
   nsRefPtr<gfxASurface> mPrintSurface;
 };
 
 #endif
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -52,16 +52,18 @@
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
+#include "jscntxt.h"
+#include "jstypedarray.h"
 #include "nsJSUtils.h"
 
 #include "nsIRenderingContext.h"
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMDocumentEvent.h"
 #include "nsIDOMProgressEvent.h"
@@ -142,16 +144,109 @@ nsHTMLAudioElement::Initialize(nsISuppor
     return rv;
 
   // We have been specified with a src URL. Begin a load.
   QueueSelectResourceTask();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHTMLAudioElement::MozSetup(PRUint32 aChannels, PRUint32 aRate)
+{
+  // If there is already a src provided, don't setup another stream
+  if (mDecoder) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // MozWriteAudio divides by mChannels, so validate now.
+  if (0 == aChannels) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mAudioStream) {
+    mAudioStream->Shutdown();
+  }
+
+  mAudioStream = new nsAudioStream();
+  nsresult rv = mAudioStream->Init(aChannels, aRate,
+                                   nsAudioStream::FORMAT_FLOAT32);
+  if (NS_FAILED(rv)) {
+    mAudioStream->Shutdown();
+    mAudioStream = nsnull;
+    return rv;
+  }
+
+  MetadataLoaded(aChannels, aRate);
+  mAudioStream->SetVolume(mVolume);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLAudioElement::MozWriteAudio(const jsval &aData, JSContext *aCx, PRUint32 *aRetVal)
+{
+  if (!mAudioStream) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (JSVAL_IS_PRIMITIVE(aData)) {
+    return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
+  }
+
+  JSObject *darray = JSVAL_TO_OBJECT(aData);
+  js::AutoValueRooter tsrc_tvr(aCx);
+  js::TypedArray *tsrc = NULL;
+
+  // Allow either Float32Array or plain JS Array
+  if (darray->getClass() == &js::TypedArray::fastClasses[js::TypedArray::TYPE_FLOAT32])
+  {
+    tsrc = js::TypedArray::fromJSObject(darray);
+  } else if (JS_IsArrayObject(aCx, darray)) {
+    JSObject *nobj = js_CreateTypedArrayWithArray(aCx, js::TypedArray::TYPE_FLOAT32, darray);
+    if (!nobj) {
+      return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
+    }
+    *tsrc_tvr.jsval_addr() = OBJECT_TO_JSVAL(nobj);
+    tsrc = js::TypedArray::fromJSObject(nobj);
+  } else {
+    return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
+  }
+
+  PRUint32 dataLength = tsrc->length;
+
+  // Make sure that we are going to write the correct amount of data based
+  // on number of channels.
+  if (dataLength % mChannels != 0) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  // Don't write more than can be written without blocking.
+  PRUint32 writeLen = NS_MIN(mAudioStream->Available(), dataLength);
+
+  nsresult rv = mAudioStream->Write(tsrc->data, writeLen, PR_TRUE);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Return the actual amount written.
+  *aRetVal = writeLen;
+  return rv;
+}
+
+NS_IMETHODIMP
+nsHTMLAudioElement::MozCurrentSampleOffset(PRUint64 *aRetVal)
+{
+  if (!mAudioStream) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  *aRetVal = mAudioStream->GetSampleOffset();
+  return NS_OK;
+}
+
   
 nsresult nsHTMLAudioElement::SetAcceptHeader(nsIHttpChannel* aChannel)
 {
     nsCAutoString value(
 #ifdef MOZ_WEBM
       "audio/webm,"
 #endif
 #ifdef MOZ_OGG
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -84,16 +84,19 @@
 #include "nsICachingChannel.h"
 #include "nsLayoutUtils.h"
 #include "nsVideoFrame.h"
 #include "BasicLayers.h"
 #include <limits>
 #include "nsIDocShellTreeItem.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
+#include "nsIPrivateDOMEvent.h"
+#include "nsIDOMNotifyAudioAvailableEvent.h"
+
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
 #ifdef MOZ_WEBM
 #include "nsWebMDecoder.h"
@@ -111,16 +114,18 @@ static PRLogModuleInfo* gMediaElementEve
 #define LOG(type, msg)
 #define LOG_EVENT(type, msg)
 #endif
 
 #include "nsIContentSecurityPolicy.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 
+#define MS_PER_SECOND 1000
+
 using namespace mozilla::layers;
 
 // Under certain conditions there may be no-one holding references to
 // a media element from script, DOM parent, etc, but the element may still
 // fire meaningful events in the future so we can't destroy it yet:
 // 1) If the element is delaying the load event (or would be, if it were
 // in a document), then events up to loadeddata or error could be fired,
 // so we need to stay alive.
@@ -187,20 +192,22 @@ private:
   PRPackedBool mProgress;
 
 public:
   nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement, PRBool aProgress) :
     nsMediaEvent(aElement), mName(aName), mProgress(aProgress)
   {
   }
 
-  NS_IMETHOD Run() {
+  NS_IMETHOD Run()
+  {
     // Silently cancel if our load has been cancelled.
     if (IsCancelled())
       return NS_OK;
+
     return mProgress ?
       mElement->DispatchProgressEvent(mName) :
       mElement->DispatchSimpleEvent(mName);
   }
 };
 
 class nsHTMLMediaElement::LoadNextSourceEvent : public nsMediaEvent {
 public:
@@ -625,16 +632,50 @@ void nsHTMLMediaElement::NotifyLoadError
 {
   if (mIsLoadingFromSrcAttribute) {
     NoSupportedMediaSourceError();
   } else {
     QueueLoadFromSourceTask();
   }
 }
 
+void nsHTMLMediaElement::NotifyAudioAvailable(float* aFrameBuffer,
+                                              PRUint32 aFrameBufferLength,
+                                              PRUint64 aTime)
+{
+  // Do same-origin check on element and media before allowing MozAudioAvailable events.
+  if (!mMediaSecurityVerified) {
+    nsCOMPtr<nsIPrincipal> principal = GetCurrentPrincipal();
+    nsresult rv = NodePrincipal()->Subsumes(principal, &mAllowAudioData);
+    if (NS_FAILED(rv)) {
+      mAllowAudioData = PR_FALSE;
+    }
+  }
+
+  DispatchAudioAvailableEvent(aFrameBuffer, aFrameBufferLength, aTime);
+}
+
+PRBool nsHTMLMediaElement::MayHaveAudioAvailableEventListener()
+{
+  // Determine if the current element is focused, if it is not focused
+  // then we should not try to blur.  Note: we allow for the case of
+  // |var a = new Audio()| with no parent document.
+  nsIDocument *document = GetDocument();
+  if (!document) {
+    return PR_TRUE;
+  }
+
+  nsPIDOMWindow *window = document->GetInnerWindow();
+  if (!window) {
+    return PR_TRUE;
+  }
+
+  return window->HasAudioAvailableEventListeners();
+}
+
 void nsHTMLMediaElement::LoadFromSourceChildren()
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   while (PR_TRUE) {
     nsresult rv;
     nsCOMPtr<nsIURI> uri = GetNextSource();
     if (!uri) {
@@ -657,16 +698,23 @@ void nsHTMLMediaElement::LoadFromSourceC
 }
 
 nsresult nsHTMLMediaElement::LoadResource(nsIURI* aURI)
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   nsresult rv;
 
+  // If a previous call to mozSetup() was made, kill that media stream
+  // in order to use this new src instead.
+  if (mAudioStream) {
+    mAudioStream->Shutdown();
+    mAudioStream = nsnull;
+  }
+
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
   }
 
   PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
   rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
                                  aURI,
@@ -929,24 +977,71 @@ NS_IMETHODIMP nsHTMLMediaElement::SetVol
   if (aVolume < 0.0f || aVolume > 1.0f)
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
 
   if (aVolume == mVolume)
     return NS_OK;
 
   mVolume = aVolume;
 
-  if (mDecoder && !mMuted)
+  if (mDecoder && !mMuted) {
     mDecoder->SetVolume(mVolume);
+  } else if (mAudioStream && !mMuted) {
+    mAudioStream->SetVolume(mVolume);
+  }
 
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHTMLMediaElement::GetMozChannels(PRUint32 *aMozChannels)
+{
+  if (!mDecoder && !mAudioStream) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  *aMozChannels = mChannels;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLMediaElement::GetMozSampleRate(PRUint32 *aMozSampleRate)
+{
+  if (!mDecoder && !mAudioStream) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  *aMozSampleRate = mRate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLMediaElement::GetMozFrameBufferLength(PRUint32 *aMozFrameBufferLength)
+{
+  // The framebuffer (via MozAudioAvailable events) is only available
+  // when reading vs. writing audio directly.
+  if (!mDecoder) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  *aMozFrameBufferLength = mDecoder->GetFrameBufferLength();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTMLMediaElement::SetMozFrameBufferLength(PRUint32 aMozFrameBufferLength)
+{
+  if (!mDecoder)
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+
+  return mDecoder->RequestFrameBufferLength(aMozFrameBufferLength);
+}
+
 /* attribute boolean muted; */
 NS_IMETHODIMP nsHTMLMediaElement::GetMuted(PRBool *aMuted)
 {
   *aMuted = mMuted;
 
   return NS_OK;
 }
 
@@ -954,32 +1049,37 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMut
 {
   if (aMuted == mMuted)
     return NS_OK;
 
   mMuted = aMuted;
 
   if (mDecoder) {
     mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
+  } else if (mAudioStream) {
+    mAudioStream->SetVolume(mMuted ? 0.0 : mVolume);
   }
 
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("volumechange"));
 
   return NS_OK;
 }
 
 nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo,
                                        PRUint32 aFromParser)
   : nsGenericHTMLElement(aNodeInfo),
     mCurrentLoadID(0),
     mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
     mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING),
     mLoadWaitStatus(NOT_WAITING),
     mVolume(1.0),
+    mChannels(0),
+    mRate(0),
     mMediaSize(-1,-1),
+    mAllowAudioData(PR_FALSE),
     mBegun(PR_FALSE),
     mLoadedFirstFrame(PR_FALSE),
     mAutoplaying(PR_TRUE),
     mAutoplayEnabled(PR_TRUE),
     mPaused(PR_TRUE),
     mMuted(PR_FALSE),
     mIsDoneAddingChildren(!aFromParser),
     mPlayingBeforeSeek(PR_FALSE),
@@ -989,17 +1089,18 @@ nsHTMLMediaElement::nsHTMLMediaElement(a
     mIsRunningLoadMethod(PR_FALSE),
     mIsLoadingFromSrcAttribute(PR_FALSE),
     mDelayingLoadEvent(PR_FALSE),
     mIsRunningSelectResource(PR_FALSE),
     mSuspendedAfterFirstFrame(PR_FALSE),
     mAllowSuspendAfterFirstFrame(PR_TRUE),
     mHasPlayedOrSeeked(PR_FALSE),
     mHasSelfReference(PR_FALSE),
-    mShuttingDown(PR_FALSE)
+    mShuttingDown(PR_FALSE),
+    mMediaSecurityVerified(PR_FALSE)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
@@ -1018,16 +1119,20 @@ nsHTMLMediaElement::~nsHTMLMediaElement(
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nsnull;
   }
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
   }
+  if (mAudioStream) {
+    mAudioStream->Shutdown();
+    mAudioStream = nsnull;
+  }
 }
 
 void nsHTMLMediaElement::StopSuspendingAfterFirstFrame()
 {
   mAllowSuspendAfterFirstFrame = PR_FALSE;
   if (!mSuspendedAfterFirstFrame)
     return;
   mSuspendedAfterFirstFrame = PR_FALSE;
@@ -1564,16 +1669,19 @@ nsresult nsHTMLMediaElement::InitializeD
 
   return FinishDecoderSetup(decoder);
 }
 
 nsresult nsHTMLMediaElement::FinishDecoderSetup(nsMediaDecoder* aDecoder)
 {
   mDecoder = aDecoder;
 
+  // Force a same-origin check before allowing events for this media resource.
+  mMediaSecurityVerified = PR_FALSE;
+
   // The new stream has not been suspended by us.
   mPausedForInactiveDocument = PR_FALSE;
   // But we may want to suspend it now.
   // This will also do an AddRemoveSelfReference.
   NotifyOwnerDocumentActivityChanged();
 
   nsresult rv = NS_OK;
 
@@ -1619,18 +1727,20 @@ nsresult nsHTMLMediaElement::NewURIFromS
     // decode it.
     NS_RELEASE(*aURI);
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   return NS_OK;
 }
 
-void nsHTMLMediaElement::MetadataLoaded()
+void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
 {
+  mChannels = aChannels;
+  mRate = aRate;
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("loadedmetadata"));
 }
 
 void nsHTMLMediaElement::FirstFrameLoaded(PRBool aResourceFullyLoaded)
 {
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
@@ -1873,16 +1983,39 @@ ImageContainer* nsHTMLMediaElement::GetI
   nsRefPtr<LayerManager> manager = nsContentUtils::LayerManagerForDocument(GetOwnerDoc());
   if (!manager)
     return nsnull;
 
   mImageContainer = manager->CreateImageContainer();
   return mImageContainer;
 }
 
+nsresult nsHTMLMediaElement::DispatchAudioAvailableEvent(float* aFrameBuffer,
+                                                         PRUint32 aFrameBufferLength,
+                                                         PRUint64 aTime)
+{
+  nsCOMPtr<nsIDOMDocumentEvent> docEvent(do_QueryInterface(GetOwnerDoc()));
+  nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(static_cast<nsIContent*>(this)));
+  NS_ENSURE_TRUE(docEvent && target, NS_ERROR_INVALID_ARG);
+
+  nsCOMPtr<nsIDOMEvent> event;
+  nsresult rv = docEvent->CreateEvent(NS_LITERAL_STRING("MozAudioAvailableEvent"),
+                                      getter_AddRefs(event));
+  nsCOMPtr<nsIDOMNotifyAudioAvailableEvent> audioavailableEvent(do_QueryInterface(event));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = audioavailableEvent->InitAudioAvailableEvent(NS_LITERAL_STRING("MozAudioAvailable"),
+                                                    PR_TRUE, PR_TRUE, aFrameBuffer, aFrameBufferLength,
+                                                    (float)aTime / MS_PER_SECOND, mAllowAudioData);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool dummy;
+  return target->DispatchEvent(event, &dummy);
+}
+
 nsresult nsHTMLMediaElement::DispatchSimpleEvent(const nsAString& aName)
 {
   LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching simple event %s", this,
                           NS_ConvertUTF16toUTF8(aName).get()));
 
   return nsContentUtils::DispatchTrustedEvent(GetOwnerDoc(),
                                               static_cast<nsIContent*>(this),
                                               aName,
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -48,26 +48,28 @@ LIBXUL_LIBRARY = 1
 EXPORTS = \
   nsMediaDecoder.h \
   nsMediaStream.h \
   nsMediaCache.h \
   nsBuiltinDecoder.h \
   nsBuiltinDecoderStateMachine.h \
   nsBuiltinDecoderReader.h \
   VideoUtils.h \
+  nsAudioAvailableEventManager.h \
   $(NULL)
 
 CPPSRCS = \
   nsMediaDecoder.cpp \
   nsMediaCache.cpp \
   nsMediaStream.cpp \
   nsBuiltinDecoder.cpp \
   nsBuiltinDecoderStateMachine.cpp \
   nsBuiltinDecoderReader.cpp \
   VideoUtils.cpp \
+  nsAudioAvailableEventManager.cpp \
   $(NULL)
 
 ifdef MOZ_SYDNEYAUDIO
 EXPORTS += \
   nsAudioStream.h \
   $(NULL)
 CPPSRCS += \
   nsAudioStream.cpp \
new file mode 100644
--- /dev/null
+++ b/content/media/nsAudioAvailableEventManager.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Humphrey <david.humphrey@senecac.on.ca>
+ *  Yury Delendik
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsTArray.h"
+#include "nsAudioAvailableEventManager.h"
+
+#define MILLISECONDS_PER_SECOND 1000
+
+using namespace mozilla;
+
+class nsAudioAvailableEventRunner : public nsRunnable
+{
+private:
+  nsCOMPtr<nsBuiltinDecoder> mDecoder;
+  nsAutoArrayPtr<float> mFrameBuffer;
+public:
+  nsAudioAvailableEventRunner(nsBuiltinDecoder* aDecoder, float* aFrameBuffer,
+                              PRUint32 aFrameBufferLength, PRUint64 aTime) :
+    mDecoder(aDecoder),
+    mFrameBuffer(aFrameBuffer),
+    mFrameBufferLength(aFrameBufferLength),
+    mTime(aTime)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    mDecoder->AudioAvailable(mFrameBuffer.forget(), mFrameBufferLength, mTime);
+    return NS_OK;
+  }
+
+  const PRUint32 mFrameBufferLength;
+  const PRUint64 mTime;
+};
+
+
+nsAudioAvailableEventManager::nsAudioAvailableEventManager(nsBuiltinDecoder* aDecoder) :
+  mDecoder(aDecoder),
+  mSignalBuffer(new float[mDecoder->GetFrameBufferLength()]),
+  mSignalBufferLength(mDecoder->GetFrameBufferLength()),
+  mSignalBufferPosition(0),
+  mMonitor("media.audioavailableeventmanager")
+{
+}
+
+void nsAudioAvailableEventManager::Init(PRUint32 aChannels, PRUint32 aRate)
+{
+  mSamplesPerSecond = aChannels * aRate;
+}
+
+void nsAudioAvailableEventManager::DispatchPendingEvents(PRUint64 aCurrentTime)
+{
+  MonitorAutoEnter mon(mMonitor);
+
+  while (mPendingEvents.Length() > 0) {
+    nsAudioAvailableEventRunner* e =
+      (nsAudioAvailableEventRunner*)mPendingEvents[0].get();
+    if (e->mTime > aCurrentTime) {
+      break;
+    }
+    nsCOMPtr<nsIRunnable> event = mPendingEvents[0];
+    mPendingEvents.RemoveElementAt(0);
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  }
+}
+
+void nsAudioAvailableEventManager::QueueWrittenAudioData(float* aAudioData,
+                                                         PRUint32 aAudioDataLength,
+                                                         PRUint64 aEndTimeSampleOffset)
+{
+  PRUint32 currentBufferSize = mDecoder->GetFrameBufferLength();
+  if (!mSignalBuffer ||
+      (mSignalBufferPosition == 0 && mSignalBufferLength != currentBufferSize)) {
+    if (!mSignalBuffer || (mSignalBufferLength < currentBufferSize)) {
+      // Only resize if buffer is empty or smaller.
+      mSignalBuffer = new float[currentBufferSize];
+    }
+    mSignalBufferLength = currentBufferSize;
+  }
+  float* audioData = aAudioData;
+  PRUint32 audioDataLength = aAudioDataLength;
+  PRUint32 signalBufferTail = mSignalBufferLength - mSignalBufferPosition;
+
+  // Group audio samples into optimal size for event dispatch, and queue.
+  while (signalBufferTail <= audioDataLength) {
+    PRUint64 time = 0;
+    // Guard against unsigned number overflow during first frame time calculation.
+    if (aEndTimeSampleOffset > mSignalBufferPosition + audioDataLength) {
+      time = MILLISECONDS_PER_SECOND * (aEndTimeSampleOffset -
+             mSignalBufferPosition - audioDataLength) / mSamplesPerSecond;
+    }
+
+    // Fill the signalBuffer.
+    memcpy(mSignalBuffer.get() + mSignalBufferPosition,
+           audioData, sizeof(float) * signalBufferTail);
+    audioData += signalBufferTail;
+    audioDataLength -= signalBufferTail;
+
+    MonitorAutoEnter mon(mMonitor);
+
+    if (mPendingEvents.Length() > 0) {
+      // Check last event timecode to make sure that all queued events
+      // are in non-decending sequence.
+      nsAudioAvailableEventRunner* lastPendingEvent =
+        (nsAudioAvailableEventRunner*)mPendingEvents[mPendingEvents.Length() - 1].get();
+      if (lastPendingEvent->mTime > time) {
+        // Clear the queue to start a fresh sequence.
+        mPendingEvents.Clear();
+      }
+    }
+
+    // Inform the element that we've written sound data.
+    nsCOMPtr<nsIRunnable> event =
+      new nsAudioAvailableEventRunner(mDecoder, mSignalBuffer.forget(),
+                                      mSignalBufferLength, time);
+    mPendingEvents.AppendElement(event);
+
+    // Reset the buffer
+    mSignalBufferLength = currentBufferSize;
+    mSignalBuffer = new float[currentBufferSize];
+    mSignalBufferPosition = 0;
+    signalBufferTail = currentBufferSize;
+    NS_ASSERTION(audioDataLength >= 0, "Past new signal data length.");
+  }
+
+  NS_ASSERTION(mSignalBufferPosition + audioDataLength < mSignalBufferLength,
+               "Intermediate signal buffer must fit at least one more item.");
+
+  if (audioDataLength > 0) {
+    // Add data to the signalBuffer.
+    memcpy(mSignalBuffer.get() + mSignalBufferPosition,
+           audioData, sizeof(float) * audioDataLength);
+    mSignalBufferPosition += audioDataLength;
+  }
+}
+
+void nsAudioAvailableEventManager::Clear()
+{
+  MonitorAutoEnter mon(mMonitor);
+
+  mPendingEvents.Clear();
+  mSignalBufferPosition = 0;
+}
+
+void nsAudioAvailableEventManager::Drain(PRUint64 aEndTime)
+{
+  MonitorAutoEnter mon(mMonitor);
+
+  // Force all pending events to go now.
+  for (PRUint32 i = 0; i < mPendingEvents.Length(); ++i) {
+    nsCOMPtr<nsIRunnable> event = mPendingEvents[i];
+    NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+  }
+  mPendingEvents.Clear();
+
+  // If there is anything left in the signal buffer, put it in an event and fire.
+  if (0 == mSignalBufferPosition)
+    return;
+
+  // Zero-pad the end of the signal buffer so it's complete.
+  memset(mSignalBuffer.get() + mSignalBufferPosition, 0,
+         (mSignalBufferLength - mSignalBufferPosition) * sizeof(float));
+
+  // Force this last event to go now.
+  nsCOMPtr<nsIRunnable> lastEvent =
+    new nsAudioAvailableEventRunner(mDecoder, mSignalBuffer.forget(),
+                                    mSignalBufferLength, aEndTime);
+  NS_DispatchToMainThread(lastEvent, NS_DISPATCH_NORMAL);
+
+  mSignalBufferPosition = 0;
+}
new file mode 100644
--- /dev/null
+++ b/content/media/nsAudioAvailableEventManager.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Humphrey <david.humphrey@senecac.on.ca>
+ *  Yury Delendik
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsAudioAvailableEventManager_h__
+#define nsAudioAvailableEventManager_h__
+
+#include "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsBuiltinDecoder.h"
+#include "nsBuiltinDecoderReader.h"
+
+using namespace mozilla;
+
+class nsAudioAvailableEventManager
+{
+public:
+  nsAudioAvailableEventManager(nsBuiltinDecoder* aDecoder);
+
+  // Initialize the event manager with audio metadata.  Called before
+  // audio begins to get queued or events are dispatched.
+  void Init(PRUint32 aChannels, PRUint32 aRate);
+
+  // Dispatch pending MozAudioAvailable events in the queue.  Called
+  // from the state machine thread.
+  void DispatchPendingEvents(PRUint64 aCurrentTime);
+
+  // Queues audio sample data and re-packages it into equal sized
+  // framebuffers.  Called from the audio thread.
+  void QueueWrittenAudioData(float* aAudioData, PRUint32 aAudioDataLength,
+                             PRUint64 aEndTimeSampleOffset);
+
+  // Clears the queue of any existing events.  Called from both the state
+  // machine and audio threads.
+  void Clear();
+
+  // Fires one last event for any extra samples that didn't fit in a whole
+  // framebuffer. This is meant to be called only once when the audio finishes.
+  // Called from the state machine thread.
+  void Drain(PRUint64 aTime);
+
+private:
+  // The decoder associated with the event manager.  The event manager shares
+  // the same lifetime as the decoder (the decoder holds a reference to the
+  // manager).
+  nsBuiltinDecoder* mDecoder;
+
+  // The number of samples per second.
+  PRUint64 mSamplesPerSecond;
+
+  // A buffer for audio data to be dispatched in DOM events.
+  nsAutoArrayPtr<float> mSignalBuffer;
+
+  // The current size of the signal buffer, may change due to DOM calls.
+  PRUint32 mSignalBufferLength;
+
+  // The position of the first available item in mSignalBuffer
+  PRUint32 mSignalBufferPosition;
+
+  // The MozAudioAvailable events to be dispatched.  This queue is shared
+  // between the state machine and audio threads.
+  nsTArray< nsCOMPtr<nsIRunnable> > mPendingEvents;
+
+  // Monitor for shared access to mPendingEvents queue.
+  Monitor mMonitor;
+};
+
+#endif
--- a/content/media/nsAudioStream.cpp
+++ b/content/media/nsAudioStream.cpp
@@ -53,16 +53,17 @@ extern "C" {
 
 using mozilla::TimeStamp;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gAudioStreamLog = nsnull;
 #endif
 
 #define FAKE_BUFFER_SIZE 176400
+#define MILLISECONDS_PER_SECOND 1000
 
 void nsAudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("nsAudioStream");
 #endif
 }
 
@@ -80,60 +81,62 @@ nsAudioStream::nsAudioStream() :
 {
 }
 
 nsAudioStream::~nsAudioStream()
 {
   Shutdown();
 }
 
-void nsAudioStream::Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat)
+nsresult nsAudioStream::Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat)
 {
   mRate = aRate;
   mChannels = aNumChannels;
   mFormat = aFormat;
   if (sa_stream_create_pcm(reinterpret_cast<sa_stream_t**>(&mAudioHandle),
                            NULL, 
                            SA_MODE_WRONLY, 
                            SA_PCM_FORMAT_S16_NE,
                            aRate,
                            aNumChannels) != SA_SUCCESS) {
     mAudioHandle = nsnull;
     PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_create_pcm error"));
-    return;
+    return NS_ERROR_FAILURE;
   }
-  
+
   if (sa_stream_open(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) {
     sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
     mAudioHandle = nsnull;
     PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_open error"));
-    return;
+    return NS_ERROR_FAILURE;
   }
+
+  return NS_OK;
 }
 
 void nsAudioStream::Shutdown()
 {
   if (!mAudioHandle) 
     return;
 
   sa_stream_destroy(static_cast<sa_stream_t*>(mAudioHandle));
   mAudioHandle = nsnull;
 }
 
-void nsAudioStream::Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking)
+nsresult nsAudioStream::Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking)
 {
   NS_ABORT_IF_FALSE(aCount % mChannels == 0,
                     "Buffer size must be divisible by channel count");
   NS_ASSERTION(!mPaused, "Don't write audio when paused, you'll block");
 
   PRUint32 offset = mBufferOverflow.Length();
   PRUint32 count = aCount + offset;
 
   if (!mAudioHandle)
-    return;
+    return NS_ERROR_FAILURE;
 
   nsAutoArrayPtr<short> s_data(new short[count]);
 
   if (s_data) {
     for (PRUint32 i=0; i < offset; ++i) {
       s_data[i] = mBufferOverflow.ElementAt(i);
     }
     mBufferOverflow.Clear();
@@ -189,18 +192,21 @@ void nsAudioStream::Write(const void* aB
     }
 
     if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
                         s_data.get(),
                         count * sizeof(short)) != SA_SUCCESS)
     {
       PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_write error"));
       Shutdown();
+      return NS_ERROR_FAILURE;
     }
   }
+
+  return NS_OK;
 }
 
 PRUint32 nsAudioStream::Available()
 {
   // If the audio backend failed to open, lie and say we'll accept some
   // data.
   if (!mAudioHandle)
     return FAKE_BUFFER_SIZE;
@@ -258,24 +264,33 @@ void nsAudioStream::Resume()
   if (!mAudioHandle)
     return;
   mPaused = PR_FALSE;
   sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
 }
 
 PRInt64 nsAudioStream::GetPosition()
 {
+  PRInt64 sampleOffset = GetSampleOffset();
+  if(sampleOffset >= 0) {
+    return ((MILLISECONDS_PER_SECOND * sampleOffset) / mRate / mChannels);
+  }
+
+  return -1;
+}
+
+PRInt64 nsAudioStream::GetSampleOffset()
+{
   if (!mAudioHandle)
     return -1;
 
   sa_position_t positionType = SA_POSITION_WRITE_SOFTWARE;
 #if defined(XP_WIN)
   positionType = SA_POSITION_WRITE_HARDWARE;
 #endif
   PRInt64 position = 0;
   if (sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle),
                              positionType, &position) == SA_SUCCESS) {
-    return ((1000 * position) / mRate / mChannels / sizeof(short));
+    return position / sizeof(short);
   }
 
   return -1;
 }
-
--- a/content/media/nsAudioStream.h
+++ b/content/media/nsAudioStream.h
@@ -63,28 +63,28 @@ class nsAudioStream
   static void ShutdownLibrary();
 
   nsAudioStream();
   ~nsAudioStream();
 
   // Initialize the audio stream. aNumChannels is the number of audio channels 
   // (1 for mono, 2 for stereo, etc) and aRate is the frequency of the sound 
   // samples (22050, 44100, etc).
-  void Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
+  nsresult Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aFormat);
 
   // Closes the stream. All future use of the stream is an error.
   void Shutdown();
 
   // Write sound data to the audio hardware.  aBuf is an array of samples in
   // the format specified by mFormat of length aCount.  aCount should be
   // evenly divisible by the number of channels in this audio stream.
   // When aBlocking is PR_TRUE, we'll block until the write has completed,
   // otherwise we'll buffer any data we can't write immediately, and write
   // it in a later call.
-  void Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking);
+  nsresult Write(const void* aBuf, PRUint32 aCount, PRBool aBlocking);
 
   // Return the number of sound samples that can be written to the audio device
   // without blocking.
   PRUint32 Available();
 
   // Set the current volume of the audio playback. This is a value from
   // 0 (meaning muted) to 1 (meaning full volume).
   void SetVolume(float aVolume);
@@ -97,16 +97,20 @@ class nsAudioStream
 
   // Resume audio playback
   void Resume();
 
   // Return the position in milliseconds of the sample being played by the
   // audio hardware.
   PRInt64 GetPosition();
 
+  // Return the position, measured in samples played since the start, by
+  // the audio hardware.
+  PRInt64 GetSampleOffset();
+
   // Returns PR_TRUE when the audio stream is paused.
   PRBool IsPaused() { return mPaused; }
 
  private:
   double mVolume;
   void* mAudioHandle;
   int mRate;
   int mChannels;
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -288,21 +288,42 @@ nsMediaStream* nsBuiltinDecoder::GetCurr
 }
 
 already_AddRefed<nsIPrincipal> nsBuiltinDecoder::GetCurrentPrincipal()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return mStream ? mStream->GetCurrentPrincipal() : nsnull;
 }
 
-void nsBuiltinDecoder::MetadataLoaded()
+void nsBuiltinDecoder::AudioAvailable(float* aFrameBuffer,
+                                      PRUint32 aFrameBufferLength,
+                                      PRUint64 aTime)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  if (mShuttingDown)
+  if (mShuttingDown) {
+    return;
+  }
+
+  if (!mElement->MayHaveAudioAvailableEventListener()) {
     return;
+  }
+
+  mElement->NotifyAudioAvailable(aFrameBuffer, aFrameBufferLength, aTime);
+}
+
+void nsBuiltinDecoder::MetadataLoaded(PRUint32 aChannels,
+                                      PRUint32 aRate,
+                                      PRUint32 aFrameBufferLength)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  if (mShuttingDown) {
+    return;
+  }
+
+  mFrameBufferLength = aFrameBufferLength;
 
   // Only inform the element of MetadataLoaded if not doing a load() in order
   // to fulfill a seek, otherwise we'll get multiple metadataloaded events.
   PRBool notifyElement = PR_TRUE;
   {
     MonitorAutoEnter mon(mMonitor);
     mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
     // Duration has changed so we should recompute playback rate
@@ -310,17 +331,17 @@ void nsBuiltinDecoder::MetadataLoaded()
 
     notifyElement = mNextState != PLAY_STATE_SEEKING;
   }
 
   if (mElement && notifyElement) {
     // Make sure the element and the frame (if any) are told about
     // our new size.
     Invalidate();
-    mElement->MetadataLoaded();
+    mElement->MetadataLoaded(aChannels, aRate);
   }
 
   if (!mResourceLoaded) {
     StartProgress();
   }
   else if (mElement) {
     // Resource was loaded during metadata loading, when progress
     // events are being ignored. Fire the final progress event.
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -405,16 +405,18 @@ class nsBuiltinDecoder : public nsMediaD
 
   // Tells our nsMediaStream to put all loads in the background.
   virtual void MoveLoadsToBackground();
 
   // Stop the state machine thread and drop references to the thread and
   // state machine.
   void Stop();
 
+  void AudioAvailable(float* aFrameBuffer, PRUint32 aFrameBufferLength, PRUint64 aTime);
+
   // Called by the state machine to notify the decoder that the duration
   // has changed.
   void DurationChanged();
 
   PRBool OnStateMachineThread() {
     return IsCurrentThread(mStateMachineThread);
   }
 
@@ -473,17 +475,19 @@ class nsBuiltinDecoder : public nsMediaD
 
   // Change to a new play state. This updates the mState variable and
   // notifies any thread blocking on this object's monitor of the
   // change. Call on the main thread only.
   void ChangeState(PlayState aState);
 
   // Called when the metadata from the media file has been read.
   // Call on the main thread only.
-  void MetadataLoaded();
+  void MetadataLoaded(PRUint32 aChannels,
+                      PRUint32 aRate,
+                      PRUint32 aFrameBufferLength);
 
   // Called when the first frame has been loaded.
   // Call on the main thread only.
   void FirstFrameLoaded();
 
   // Called when the video has completed playing.
   // Call on the main thread only.
   void PlaybackEnded();
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -97,16 +97,41 @@ const PRUint32 SILENCE_BYTES_CHUNK = 32 
 // we'll only go into BUFFERING state if we've got audio and have queued
 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
 // less than LOW_VIDEO_FRAMES frames.
 static const PRUint32 LOW_VIDEO_FRAMES = 1;
 
 // Arbitrary "frame duration" when playing only audio.
 static const int AUDIO_DURATION_MS = 40;
 
+class nsAudioMetadataEventRunner : public nsRunnable
+{
+private:
+  nsCOMPtr<nsBuiltinDecoder> mDecoder;
+public:
+  nsAudioMetadataEventRunner(nsBuiltinDecoder* aDecoder, PRUint32 aChannels,
+                             PRUint32 aRate, PRUint32 aFrameBufferLength) :
+    mDecoder(aDecoder),
+    mChannels(aChannels),
+    mRate(aRate),
+    mFrameBufferLength(aFrameBufferLength)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    mDecoder->MetadataLoaded(mChannels, mRate, mFrameBufferLength);
+    return NS_OK;
+  }
+
+  const PRUint32 mChannels;
+  const PRUint32 mRate;
+  const PRUint32 mFrameBufferLength;
+};
+
 nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder,
                                                            nsBuiltinDecoderReader* aReader) :
   mDecoder(aDecoder),
   mState(DECODER_STATE_DECODING_METADATA),
   mAudioMonitor("media.audiostream"),
   mCbCrSize(0),
   mPlayDuration(0),
   mBufferingEndOffset(0),
@@ -119,17 +144,18 @@ nsBuiltinDecoderStateMachine::nsBuiltinD
   mAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mVolume(1.0),
   mSeekable(PR_TRUE),
   mPositionChangeQueued(PR_FALSE),
   mAudioCompleted(PR_FALSE),
   mBufferExhausted(PR_FALSE),
   mGotDurationFromHeader(PR_FALSE),
-  mStopDecodeThreads(PR_TRUE)
+  mStopDecodeThreads(PR_TRUE),
+  mEventManager(aDecoder)
 {
   MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
 }
 
 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
 {
   MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
 }
@@ -403,19 +429,20 @@ void nsBuiltinDecoderStateMachine::Audio
     }
 
     if (missingSamples > 0) {
       // The next sound chunk begins some time after the end of the last chunk
       // we pushed to the sound hardware. We must push silence into the audio
       // hardware so that the next sound chunk begins playback at the correct
       // time.
       missingSamples = NS_MIN(static_cast<PRInt64>(PR_UINT32_MAX), missingSamples);
-      audioDuration += PlaySilence(static_cast<PRUint32>(missingSamples), channels);
+      audioDuration += PlaySilence(static_cast<PRUint32>(missingSamples),
+                                   channels, sampleTime);
     } else {
-      audioDuration += PlayFromAudioQueue();
+      audioDuration += PlayFromAudioQueue(sampleTime, channels);
     }
     {
       MonitorAutoEnter mon(mDecoder->GetMonitor());
       PRInt64 playedMs;
       if (!SamplesToMs(audioDuration, rate, playedMs)) {
         NS_WARNING("Int overflow calculating playedMs");
         break;
       }
@@ -445,48 +472,57 @@ void nsBuiltinDecoderStateMachine::Audio
       mState != DECODER_STATE_SHUTDOWN &&
       !mStopDecodeThreads)
   {
     // Last sample pushed to audio hardware, wait for the audio to finish,
     // before the audio thread terminates.
     MonitorAutoEnter audioMon(mAudioMonitor);
     if (mAudioStream) {
       mAudioStream->Drain();
+      // Fire one last event for any extra samples that didn't fill a framebuffer.
+      mEventManager.Drain(mAudioEndTime);
     }
     LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
   }
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     mAudioCompleted = PR_TRUE;
     UpdateReadyState();
     // Kick the decode and state machine threads; they may be sleeping waiting
     // for this to finish.
     mDecoder->GetMonitor().NotifyAll();
   }
   LOG(PR_LOG_DEBUG, ("Audio stream finished playing, audio thread exit"));
 }
 
-PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples, PRUint32 aChannels)
+PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
+                                                   PRUint32 aChannels,
+                                                   PRUint64 aSampleOffset)
+
 {
   MonitorAutoEnter audioMon(mAudioMonitor);
   if (mAudioStream->IsPaused()) {
     // The state machine has paused since we've released the decoder
     // monitor and acquired the audio monitor. Don't write any audio.
     return 0;
   }
   PRUint32 maxSamples = SILENCE_BYTES_CHUNK / aChannels;
   PRUint32 samples = NS_MIN(aSamples, maxSamples);
   PRUint32 numFloats = samples * aChannels;
   nsAutoArrayPtr<float> buf(new float[numFloats]);
   memset(buf.get(), 0, sizeof(float) * numFloats);
   mAudioStream->Write(buf, numFloats, PR_TRUE);
+  // Dispatch events to the DOM for the audio just written.
+  mEventManager.QueueWrittenAudioData(buf.get(), numFloats,
+                                      (aSampleOffset + samples) * aChannels);
   return samples;
 }
 
-PRUint32 nsBuiltinDecoderStateMachine::PlayFromAudioQueue()
+PRUint32 nsBuiltinDecoderStateMachine::PlayFromAudioQueue(PRUint64 aSampleOffset,
+                                                          PRUint32 aChannels)
 {
   nsAutoPtr<SoundData> sound(mReader->mAudioQueue.PopFront());
   {
     MonitorAutoEnter mon(mDecoder->GetMonitor());
     NS_WARN_IF_FALSE(IsPlaying(), "Should be playing");
     // Awaken the decode loop if it's waiting for space to free up in the
     // audio queue.
     mDecoder->GetMonitor().NotifyAll();
@@ -497,25 +533,31 @@ PRUint32 nsBuiltinDecoderStateMachine::P
     MonitorAutoEnter audioMon(mAudioMonitor);
     if (!mAudioStream) {
       return 0;
     }
     // The state machine could have paused since we've released the decoder
     // monitor and acquired the audio monitor. Rather than acquire both
     // monitors, the audio stream also maintains whether its paused or not.
     // This prevents us from doing a blocking write while holding the audio
-    // monitor while paused; we would block, and the state machine won't be 
+    // monitor while paused; we would block, and the state machine won't be
     // able to acquire the audio monitor in order to resume or destroy the
     // audio stream.
     if (!mAudioStream->IsPaused()) {
       mAudioStream->Write(sound->mAudioData,
                           sound->AudioDataLength(),
                           PR_TRUE);
+
       offset = sound->mOffset;
       samples = sound->mSamples;
+
+      // Dispatch events to the DOM for the audio just written.
+      mEventManager.QueueWrittenAudioData(sound->mAudioData.get(),
+                                          sound->AudioDataLength(),
+                                          (aSampleOffset + samples) * aChannels);
     } else {
       mReader->mAudioQueue.PushFront(sound);
       sound.forget();
     }
   }
   if (offset != -1) {
     mDecoder->UpdatePlaybackOffset(offset);
   }
@@ -547,16 +589,17 @@ void nsBuiltinDecoderStateMachine::StopP
     MonitorAutoExit exitMon(mDecoder->GetMonitor());
     MonitorAutoEnter audioMon(mAudioMonitor);
     if (mAudioStream) {
       if (aMode == AUDIO_PAUSE) {
         mAudioStream->Pause();
       } else if (aMode == AUDIO_SHUTDOWN) {
         mAudioStream->Shutdown();
         mAudioStream = nsnull;
+        mEventManager.Clear();
       }
     }
   }
 }
 
 void nsBuiltinDecoderStateMachine::StartPlayback()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
@@ -603,16 +646,19 @@ void nsBuiltinDecoderStateMachine::Updat
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
   if (!mPositionChangeQueued) {
     mPositionChangeQueued = PR_TRUE;
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::PlaybackPositionChanged);
     NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   }
+
+  // Notify DOM of any queued up audioavailable events
+  mEventManager.DispatchPendingEvents(mCurrentFrameTime + mStartTime);
 }
 
 void nsBuiltinDecoderStateMachine::ClearPositionChangeFlag()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   mPositionChangeQueued = PR_FALSE;
@@ -846,20 +892,26 @@ nsresult nsBuiltinDecoderStateMachine::R
                      "Active seekable media should have end time");
         NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
         LOG(PR_LOG_DEBUG, ("%p Media goes from %lldms to %lldms (duration %lldms) seekable=%d",
                            mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
 
         if (mState == DECODER_STATE_SHUTDOWN)
           continue;
 
-        // Inform the element that we've loaded the metadata and the
-        // first frame.
+        // Inform the element that we've loaded the metadata and the first frame,
+        // setting the default framebuffer size for audioavailable events.  Also
+        // let the MozAudioAvailable event manager know about the metadata.
+        const nsVideoInfo& info = mReader->GetInfo();
+        PRUint32 frameBufferLength = info.mAudioChannels * FRAMEBUFFER_LENGTH_PER_CHANNEL;
+        mEventManager.Init(info.mAudioChannels, info.mAudioRate);
+        mDecoder->RequestFrameBufferLength(frameBufferLength);
         nsCOMPtr<nsIRunnable> metadataLoadedEvent =
-          NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::MetadataLoaded);
+          new nsAudioMetadataEventRunner(mDecoder, info.mAudioChannels,
+                                         info.mAudioRate, frameBufferLength);
         NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
 
         if (mState == DECODER_STATE_DECODING_METADATA) {
           LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
           mState = DECODER_STATE_DECODING;
         }
 
         // Start playback.
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -111,16 +111,17 @@ not yet time to display the next frame.
 */
 #if !defined(nsBuiltinDecoderStateMachine_h__)
 #define nsBuiltinDecoderStateMachine_h__
 
 #include "prmem.h"
 #include "nsThreadUtils.h"
 #include "nsBuiltinDecoder.h"
 #include "nsBuiltinDecoderReader.h"
+#include "nsAudioAvailableEventManager.h"
 #include "nsHTMLMediaElement.h"
 #include "mozilla/Monitor.h"
 
 /*
   The playback state machine class. This manages the decoding in the
   nsBuiltinDecoderReader on the decode thread, seeking and in-sync-playback on the
   state machine thread, and controls the audio "push" thread.
 
@@ -288,21 +289,23 @@ protected:
 
   // Pushes up to aSamples samples of silence onto the audio hardware. Returns
   // the number of samples acutally pushed to the hardware. This pushes up to
   // 32KB worth of samples to the hardware before returning, so must be called
   // in a loop to ensure that the desired number of samples are pushed to the
   // hardware. This ensures that the playback position advances smoothly, and
   // guarantees that we don't try to allocate an impossibly large chunk of
   // memory in order to play back silence. Called on the audio thread.
-  PRUint32 PlaySilence(PRUint32 aSamples, PRUint32 aChannels);
+  PRUint32 PlaySilence(PRUint32 aSamples, PRUint32 aChannels,
+                       PRUint64 aSampleOffset);
 
   // Pops an audio chunk from the front of the audio queue, and pushes its
-  // sound data to the audio hardware. Called on the audio thread.
-  PRUint32 PlayFromAudioQueue();
+  // sound data to the audio hardware. MozAudioAvailable sample data is also
+  // queued here. Called on the audio thread.
+  PRUint32 PlayFromAudioQueue(PRUint64 aSampleOffset, PRUint32 aChannels);
 
   // Stops the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   void StopDecodeThreads();
 
   // Starts the decode threads. The decoder monitor must be held with exactly
   // one lock count. Called on the state machine thread.
   nsresult StartDecodeThreads();
@@ -449,12 +452,17 @@ protected:
 
   // PR_TRUE if mDuration has a value obtained from an HTTP header.
   // Accessed on the state machine thread.
   PRPackedBool mGotDurationFromHeader;
     
   // PR_FALSE while decode threads should be running. Accessed on audio, 
   // state machine and decode threads. Syncrhonised by decoder monitor.
   PRPackedBool mStopDecodeThreads;
+
+private:
+  // Manager for queuing and dispatching MozAudioAvailable events.  The
+  // event manager is accessed from the state machine and audio threads,
+  // and takes care of synchronizing access to its internal queue.
+  nsAudioAvailableEventManager mEventManager;
 };
 
-
 #endif
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -71,16 +71,17 @@
 nsMediaDecoder::nsMediaDecoder() :
   mElement(0),
   mRGBWidth(-1),
   mRGBHeight(-1),
   mProgressTime(),
   mDataTime(),
   mVideoUpdateLock(nsnull),
   mPixelAspectRatio(1.0),
+  mFrameBufferLength(0),
   mPinnedForSeek(PR_FALSE),
   mSizeChanged(PR_FALSE),
   mShuttingDown(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsMediaDecoder);
 }
 
 nsMediaDecoder::~nsMediaDecoder()
@@ -106,16 +107,29 @@ void nsMediaDecoder::Shutdown()
   mElement = nsnull;
 }
 
 nsHTMLMediaElement* nsMediaDecoder::GetMediaElement()
 {
   return mElement;
 }
 
+nsresult nsMediaDecoder::RequestFrameBufferLength(PRUint32 aLength)
+{
+  // Must be a power of 2 between 512 and 32768
+  if (aLength < FRAMEBUFFER_LENGTH_MIN || aLength > FRAMEBUFFER_LENGTH_MAX ||
+      (aLength & (aLength - 1)) > 0) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  mFrameBufferLength = aLength;
+  return NS_OK;
+}
+
+
 static PRInt32 ConditionDimension(float aValue, PRInt32 aDefault)
 {
   // This will exclude NaNs and infinities
   if (aValue >= 1.0 && aValue <= 10000.0)
     return PRInt32(NS_round(aValue));
   return aDefault;
 }
 
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -48,16 +48,26 @@
 #include "nsITimer.h"
 #include "ImageLayers.h"
 
 class nsHTMLMediaElement;
 class nsMediaStream;
 class nsIStreamListener;
 class nsHTMLTimeRanges;
 
+// The size to use for audio data frames in audioavailable events.
+// This value is per channel, and is chosen to give ~43 fps of events,
+// for example, 44100 with 2 channels, 2*1024 = 2048.
+#define FRAMEBUFFER_LENGTH_PER_CHANNEL 1024
+
+// The total size of the framebuffer used for audioavailable events
+// has to be a power of 2, and must fit in the following range.
+#define FRAMEBUFFER_LENGTH_MIN 512
+#define FRAMEBUFFER_LENGTH_MAX 32768
+
 // All methods of nsMediaDecoder must be called from the main thread only
 // with the exception of GetImageContainer, SetVideoData and GetStatistics,
 // which can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
 public:
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
@@ -214,16 +224,23 @@ public:
   // we force the decoder to go into buffering state before resuming
   // playback.
   virtual void Resume(PRBool aForceBuffering) = 0;
 
   // Returns a weak reference to the media element we're decoding for,
   // if it's available.
   nsHTMLMediaElement* GetMediaElement();
 
+  // Returns the current size of the framebuffer used in audioavailable events.
+  PRUint32 GetFrameBufferLength() { return mFrameBufferLength; };
+
+  // Sets the length of the framebuffer used in audioavailable events.  The
+  // new size must be a power of 2 between 512 and 32768.
+  nsresult RequestFrameBufferLength(PRUint32 aLength);
+
   // Moves any existing channel loads into the background, so that they don't
   // block the load event. This is called when we stop delaying the load
   // event. Any new loads initiated (for example to seek) will also be in the
   // background. Implementations of this must call MoveLoadsToBackground() on
   // their nsMediaStream.
   virtual void MoveLoadsToBackground()=0;
 
   // Gets the image container for the media element. Will return null if
@@ -293,16 +310,19 @@ protected:
   // to the RGB buffer must obtain this lock first to ensure that
   // the video element does not use video data or sizes that are
   // in the midst of being changed.
   PRLock* mVideoUpdateLock;
 
   // Pixel aspect ratio (ratio of the pixel width to pixel height)
   float mPixelAspectRatio;
 
+  // The framebuffer size to use for audioavailable events.
+  PRUint32 mFrameBufferLength;
+
   // PR_TRUE when our media stream has been pinned. We pin the stream
   // while seeking.
   PRPackedBool mPinnedForSeek;
 
   // Has our size changed since the last repaint?
   PRPackedBool mSizeChanged;
 
   // True if the decoder is being shutdown. At this point all events that
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -134,16 +134,20 @@ include $(topsrcdir)/config/rules.mk
 		test_seek_out_of_range.html \
 		test_source.html \
 		test_source_write.html \
 		test_standalone.html \
 		test_timeupdate_small_files.html \
 		test_volume.html \
 		test_video_to_canvas.html \
 		use_large_cache.js \
+		test_a4_tone.html \
+		test_audiowrite.html \
+		file_audio_event_adopt_iframe.html \
+		test_audio_event_adopt.html \
 		$(NULL)
 
 # These tests are disabled until we figure out random failures.
 # When these tests are fixed, we should also make them backend-independent.
 #		test_resume.html \
 # Bug 492821:
 #   test_videoDocumentTitle.html
 # Bug 493692:
@@ -199,16 +203,17 @@ endif
 		dirac.ogg \
 		seek.ogv \
 		seek.webm \
 		seek.yuv \
 		short-video.ogv \
 		small-shot.ogg \
 		sound.ogg \
 		video-overhang.ogg \
+		file_a4_tone.ogg \
 		$(NULL)
 
 # Wave sample files
 _TEST_FILES += \
 		big.wav \
 		bogus.wav \
 		r11025_msadpcm_c1.wav \
 		r11025_s16_c1.wav \
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..814e8641766c99c5cd3500b2141ffcf111f9040a
GIT binary patch
literal 5989
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
new file mode 100644
--- /dev/null
+++ b/content/media/test/file_audio_event_adopt_iframe.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+  function audioAvailable(e) {
+    document.getElementById("wasAudioAvailableCalled").checked = true;
+  }
+</script>
+</head>
+<body>
+<audio id="a1" src="sound.ogg" controls></audio>
+<script>
+  document.getElementById("a1").addEventListener("MozAudioAvailable", audioAvailable, false);
+</script>
+<input id="wasAudioAvailableCalled" type="checkbox" readonly />
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_a4_tone.html
@@ -0,0 +1,262 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=490705
+-->
+
+<head>
+  <title>Media test: simple audioAvalailable event checks</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=490705">Mozilla Bug 490705</a>
+
+<!-- mute audio, since there is no need to hear the sound for these tests -->
+<audio id='a1' onerror="event.stopPropagation();" controls></audio>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+* FFT is a class for calculating the Discrete Fourier Transform of a signal
+* with the Fast Fourier Transform algorithm.
+*
+* Source: github.com/corbanbrook/dsp.js; License: MIT; Copyright: Corban Brook
+*
+* @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2
+* @param {Number} sampleRate The sampleRate of the buffer (eg. 44100)
+*
+* @constructor
+*/
+FFT = function(bufferSize, sampleRate) {
+  this.bufferSize = bufferSize;
+  this.sampleRate = sampleRate;
+  this.spectrum = new Float32Array(bufferSize/2);
+  this.real = new Float32Array(bufferSize);
+  this.imag = new Float32Array(bufferSize);
+
+  this.reverseTable = new Uint32Array(bufferSize);
+
+  var limit = 1;
+  var bit = bufferSize >> 1;
+
+  while ( limit < bufferSize ) {
+    for ( var i = 0; i < limit; i++ ) {
+      this.reverseTable[i + limit] = this.reverseTable[i] + bit;
+    }
+
+    limit = limit << 1;
+    bit = bit >> 1;
+  }
+
+  this.sinTable = new Float32Array(bufferSize);
+  this.cosTable = new Float32Array(bufferSize);
+
+  for ( var i = 0; i < bufferSize; i++ ) {
+    this.sinTable[i] = Math.sin(-Math.PI/i);
+    this.cosTable[i] = Math.cos(-Math.PI/i);
+  }
+};
+
+/**
+* Performs a forward tranform on the sample buffer.
+* Converts a time domain signal to frequency domain spectra.
+*
+* @param {Array} buffer The sample buffer. Buffer Length must be power of 2
+*
+* @returns The frequency spectrum array
+*/
+FFT.prototype.forward = function(buffer) {
+  // Locally scope variables for speed up
+  var bufferSize = this.bufferSize,
+      cosTable = this.cosTable,
+      sinTable = this.sinTable,
+      reverseTable = this.reverseTable,
+      real = this.real,
+      imag = this.imag,
+      spectrum = this.spectrum;
+
+  var k = Math.floor(Math.log(bufferSize) / Math.LN2);
+  if ( Math.pow(2, k) !== bufferSize ) {
+    throw "Invalid buffer size, must be a power of 2.";
+  }
+  if ( bufferSize !== buffer.length ) {
+    throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length;
+  }
+
+  for ( var i = 0; i < bufferSize; i++ ) {
+    real[i] = buffer[reverseTable[i]];
+    imag[i] = 0;
+  }
+
+  var halfSize = 1,
+      phaseShiftStepReal,
+      phaseShiftStepImag,
+      currentPhaseShiftReal,
+      currentPhaseShiftImag,
+      off,
+      tr,
+      ti,
+      tmpReal,
+      i;
+
+  while ( halfSize < bufferSize ) {
+    phaseShiftStepReal = cosTable[halfSize];
+    phaseShiftStepImag = sinTable[halfSize];
+    currentPhaseShiftReal = 1;
+    currentPhaseShiftImag = 0;
+
+    for ( var fftStep = 0; fftStep < halfSize; fftStep++ ) {
+      i = fftStep;
+
+      while ( i < bufferSize ) {
+        off = i + halfSize;
+        tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]);
+        ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]);
+
+        real[off] = real[i] - tr;
+        imag[off] = imag[i] - ti;
+        real[i] += tr;
+        imag[i] += ti;
+
+        i += halfSize << 1;
+      }
+
+      tmpReal = currentPhaseShiftReal;
+      currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag);
+      currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal);
+    }
+
+    halfSize = halfSize << 1;
+  }
+
+  i = bufferSize/2;
+  while(i--) {
+    spectrum[i] = 2 * Math.sqrt(real[i] * real[i] + imag[i] * imag[i]) / bufferSize;
+  }
+
+  return spectrum;
+};
+/* end of FFT */
+
+
+var testFile = "file_a4_tone.ogg";
+var testFileDuration = 3.0;
+var testFileChannelCount = 1;
+var testFileSampleRate = 44100;
+var testFileFrameBufferLength = 1024;
+var signal = [{start:1.1, end: 1.9, fftBin: 10 } ];
+var noSignal = [{start:0.1, end: 0.9 }, {start:2.1, end: 2.9 } ];
+
+var undef;
+var fft, fftBufferSize;
+var currentSampleOffset = 0;
+var spectrumMaxs = [];
+var isTimePropertyValid = true;
+
+function audioAvailable(event) {
+  var buffer = event.frameBuffer;
+
+  if(fft === undef) {
+    fftBufferSize = buffer.length;
+    fft = new FFT(fftBufferSize, testFileSampleRate);
+  }
+
+  fft.forward(buffer);
+
+  var spectrum = fft.spectrum;
+  // Finding pick frequency
+  var maxIndex = 0, maxValue = spectrum[0];
+  for(var i=0;i<spectrum.length;i++) {
+    if(maxValue < spectrum[i]) {
+      maxValue = spectrum[maxIndex = i];
+    }
+  }
+
+  spectrumMaxs.push({ value: maxValue, index: maxIndex, time: (currentSampleOffset / testFileSampleRate) });
+
+  if( (typeof event.time !== "number") ||
+      (Math.abs(event.time - currentSampleOffset / testFileSampleRate) > 0.01) ) {
+    isTimePropertyValid = false;
+  }
+
+  currentSampleOffset += buffer.length;
+}
+
+var loadedMetadataCalled = false;
+function loadedMetadata() {
+  loadedMetadataCalled = true;
+  var a1 = document.getElementById('a1');
+  is(a1.mozChannels, testFileChannelCount, "mozChannels should be " + testFileChannelCount + ".");
+  is(a1.mozSampleRate, testFileSampleRate, "mozSampleRate should be " + testFileSampleRate + ".");
+  is(a1.mozFrameBufferLength, testFileFrameBufferLength, "mozFrameBufferLength should be " + testFileFrameBufferLength + ".");
+}
+
+function checkResults() {
+  ok(loadedMetadataCalled, "loadedmetadata event not dispatched.");
+  ok(isTimePropertyValid, "The audioAvailable event's time attribute was invalid.");
+
+  var expectedOffset = Math.ceil(testFileDuration * testFileSampleRate);
+  if(expectedOffset % fftBufferSize !== 0) { expectedOffset += (fftBufferSize - (expectedOffset % fftBufferSize)); }
+  is(currentSampleOffset, expectedOffset, "Check amount of signal data processed");
+
+  var i, j;
+  var signalPresent = true;
+  for(i=0;i<signal.length;++i) {
+    var signalAnalysed = false;
+    for(j=0;j<spectrumMaxs.length;++j) {
+      if(signal[i].start <= spectrumMaxs[j].time && spectrumMaxs[j].time < signal[i].end) {
+        signalAnalysed = true;
+        signalPresent = spectrumMaxs[j].index == signal[i].fftBin;
+      }
+      if(!signalPresent) break;
+    }
+    if(!signalAnalysed) signalPresent = false;;
+    if(!signalPresent) break;
+  }
+  is(signalPresent, true, "Check signal present");
+
+  var noSignalPresent = true;
+  for(i=0;i<noSignal.length;++i) {
+    var signalAnalysed = false;
+    for(j=0;j<spectrumMaxs.length;++j) {
+      if(noSignal[i].start <= spectrumMaxs[j].time && spectrumMaxs[j].time < noSignal[i].end) {
+        signalAnalysed = true;
+        noSignalPresent = spectrumMaxs[j].index == 0;
+      }
+      if(!noSignalPresent) break;
+    }
+    if(!signalAnalysed) noSignalPresent = false;;
+    if(!noSignalPresent) break;
+  }
+  is(signalPresent, true, "Check mute fragments present");
+
+  SimpleTest.finish();
+}
+
+function audioEnded() {
+  checkResults();
+}
+
+function initTest() {
+  var a1 = document.getElementById('a1');
+  a1.addEventListener("ended", audioEnded, false);
+  a1.addEventListener("loadedmetadata", loadedMetadata, false);
+  a1.addEventListener("MozAudioAvailable", audioAvailable, false);
+  a1.src = testFile;
+  a1.muted = true;
+  a1.play();
+}
+
+window.addEventListener("load", function(e) {
+  initTest();
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_audio_event_adopt.html
@@ -0,0 +1,41 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=490705
+-->
+<head>
+  <title>Media test: addEventListener optimization and adoptNode</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script>
+  function adopt() {
+    var a1Node = document.getElementById("f1").contentDocument.getElementById("a1");
+    var adopted = document.adoptNode(a1Node);
+    document.body.appendChild(adopted);
+    return adopted;
+  }
+  function wasAudioAvailableCalled() {
+    var resultNode = document.getElementById("f1").contentDocument.getElementById("wasAudioAvailableCalled");
+    return document.adoptNode(resultNode).checked;
+  }
+  function endTest() {
+    is(wasAudioAvailableCalled(), true, "audioAvailable was not called");
+
+    SimpleTest.finish();
+  }
+  function startTest() {
+    var audio = adopt();
+    audio.addEventListener("ended", endTest, false);
+    audio.play();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=490705">Mozilla Bug 490705</a>
+  <iframe id="f1" src="file_audio_event_adopt_iframe.html" onload="startTest()"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_audiowrite.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=490705
+-->
+
+<head>
+  <title>Media test: simple audio write checks</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=490705">Mozilla Bug 490705</a>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var channels = 2;
+var rate = 44100;
+
+function runTests() {
+  var a1 = new Audio();
+  a1.mozSetup(channels, rate);
+
+  is(a1.mozChannels, channels, "mozChannels should be " + channels + ".");
+  is(a1.mozSampleRate, rate, "mozSampleRate should be " + rate + ".");
+  is(a1.volume, 1.0, "volume should be 1.0 by default.");
+
+  // Make sure changing volume on audio changes write audio stream.
+  a1.volume = 0.5;
+  is(a1.volume, 0.5, "volume should have been changed to 0.5.");
+  a1.muted = true;
+  ok(a1.muted, "volume should be muted.");
+
+  is(a1.mozCurrentSampleOffset(), 0, "mozCurrentSampleOffset() not working.");
+
+  // Test writing with js array
+  var samples1 = [.5, .5];
+  var written = sampleOffset = a1.mozWriteAudio(samples1);
+  is(written, samples1.length, "Not all samples in JS Array written.");
+
+  // Test writing with Float32Array
+  var samples2 = Float32Array([.2, .3, .2, .3]);
+  written = a1.mozWriteAudio(samples2);
+  is(written, samples2.length, "Not all samples in Float32Array written.");
+
+  // Test passing the wrong arguments to mozWriteAudio.
+  var writeArgsOK = false;
+  try {
+    // incorrect, should throw
+    written = a1.mozWriteAudio(samples2.length, samples2);
+  } catch(e) {
+    writeArgsOK = true;
+  }
+  ok(writeArgsOK, "mozWriteAudio args test failed.");
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", function(e) {
+  runTests();
+}, false);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/wave/nsWaveDecoder.cpp
+++ b/content/media/wave/nsWaveDecoder.cpp
@@ -142,16 +142,25 @@ public:
   void Seek(float aTime);
   void Shutdown();
 
   // Returns the playback length of the audio data in seconds, calculated
   // from the length extracted from the metadata.  Returns NaN if called
   // before metadata validation has completed.  Threadsafe.
   float GetDuration();
 
+  // Returns the number of channels extracted from the metadata.  Returns 0
+  // if called before metadata validation has completed.  Threadsafe.
+  PRUint32 GetChannels();
+
+  // Returns the audio sample rate (number of samples per second) extracted
+  // from the metadata.  Returns 0 if called before metadata validation has
+  // completed.  Threadsafe.
+  PRUint32 GetSampleRate();
+
   // Returns true if the state machine is seeking.  Threadsafe.
   PRBool IsSeeking();
 
   // Returns true if the state machine has reached the end of playback.  Threadsafe.
   PRBool IsEnded();
 
   // Main state machine loop. Runs forever, until shutdown state is reached.
   NS_IMETHOD Run();
@@ -466,16 +475,36 @@ nsWaveStateMachine::GetDuration()
 {
   nsAutoMonitor monitor(mMonitor);
   if (mMetadataValid) {
     return BytesToTime(GetDataLength());
   }
   return std::numeric_limits<float>::quiet_NaN();
 }
 
+PRUint32
+nsWaveStateMachine::GetChannels()
+{
+  nsAutoMonitor monitor(mMonitor);
+  if (mMetadataValid) {
+    return mChannels;
+  }
+  return 0;
+}
+
+PRUint32
+nsWaveStateMachine::GetSampleRate()
+{
+  nsAutoMonitor monitor(mMonitor);
+  if (mMetadataValid) {
+    return mSampleRate;
+  }
+  return 0;
+}
+
 PRBool
 nsWaveStateMachine::IsSeeking()
 {
   nsAutoMonitor monitor(mMonitor);
   return mState == STATE_SEEKING || mNextState == STATE_SEEKING;
 }
 
 PRBool
@@ -1367,17 +1396,18 @@ nsWaveDecoder::Load(nsMediaStream* aStre
 void
 nsWaveDecoder::MetadataLoaded()
 {
   if (mShuttingDown) {
     return;
   }
 
   if (mElement) {
-    mElement->MetadataLoaded();
+    mElement->MetadataLoaded(mPlaybackStateMachine->GetChannels(),
+                             mPlaybackStateMachine->GetSampleRate());
     mElement->FirstFrameLoaded(mResourceLoaded);
   }
 
   mMetadataLoadedReported = PR_TRUE;
 
   if (mResourceLoaded) {
     ResourceLoaded();
   } else {
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -231,16 +231,17 @@
 #include "nsIDOMBeforeUnloadEvent.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsIDOMSmartCardEvent.h"
 #include "nsIDOMXULCommandEvent.h"
 #include "nsIDOMPageTransitionEvent.h"
 #include "nsIDOMMessageEvent.h"
 #include "nsPaintRequest.h"
 #include "nsIDOMNotifyPaintEvent.h"
+#include "nsIDOMNotifyAudioAvailableEvent.h"
 #include "nsIDOMScrollAreaEvent.h"
 #include "nsIDOMTransitionEvent.h"
 #include "nsIDOMNSDocumentStyle.h"
 #include "nsIDOMDocumentRange.h"
 #include "nsIDOMDocumentTraversal.h"
 #include "nsIDOMDocumentXBL.h"
 #include "nsIDOMDocumentView.h"
 #include "nsIDOMElementCSSInlineStyle.h"
@@ -1375,16 +1376,19 @@ static nsDOMClassInfoData sClassInfoData
 
   // data transfer for drag and drop
   NS_DEFINE_CLASSINFO_DATA(DataTransfer, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(NotifyPaintEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(NotifyAudioAvailableEvent, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(SimpleGestureEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(MozTouchEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #ifdef MOZ_MATHML
   NS_DEFINE_CLASSINFO_DATA_WITH_NAME(MathMLElement, Element, nsElementSH,
                                      ELEMENT_SCRIPTABLE_FLAGS)
@@ -3913,16 +3917,21 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSDataTransfer)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(NotifyPaintEvent, nsIDOMNotifyPaintEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNotifyPaintEvent)
     DOM_CLASSINFO_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(NotifyAudioAvailableEvent, nsIDOMNotifyAudioAvailableEvent)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNotifyAudioAvailableEvent)
+    DOM_CLASSINFO_EVENT_MAP_ENTRIES
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(SimpleGestureEvent, nsIDOMSimpleGestureEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSimpleGestureEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMouseEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSMouseEvent)
     DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozTouchEvent, nsIDOMMozTouchEvent)
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -442,16 +442,18 @@ DOMCI_CLASS(XMLHttpRequestUpload)
 
 // DOM Traversal NodeIterator class
 DOMCI_CLASS(NodeIterator)
 
 DOMCI_CLASS(DataTransfer)
 
 DOMCI_CLASS(NotifyPaintEvent)
 
+DOMCI_CLASS(NotifyAudioAvailableEvent)
+
 DOMCI_CLASS(SimpleGestureEvent)
 
 DOMCI_CLASS(MozTouchEvent)
 
 #ifdef MOZ_MATHML
 DOMCI_CLASS(MathMLElement)
 #endif
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -641,18 +641,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTimeout, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release)
 
 nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *aOuterWindow)
 : mFrameElement(nsnull), mDocShell(nsnull), mModalStateDepth(0),
   mRunningTimeout(nsnull), mMutationBits(0), mIsDocumentLoaded(PR_FALSE),
   mIsHandlingResizeEvent(PR_FALSE), mIsInnerWindow(aOuterWindow != nsnull),
   mMayHavePaintEventListener(PR_FALSE), mMayHaveTouchEventListener(PR_FALSE),
-  mIsModalContentWindow(PR_FALSE), mIsActive(PR_FALSE),
-  mInnerWindow(nsnull), mOuterWindow(aOuterWindow) {}
+  mMayHaveAudioAvailableEventListener(PR_FALSE), mIsModalContentWindow(PR_FALSE),
+  mIsActive(PR_FALSE), mInnerWindow(nsnull), mOuterWindow(aOuterWindow) {}
 
 nsPIDOMWindow::~nsPIDOMWindow() {}
 
 //*****************************************************************************
 //***    nsGlobalWindow: Object Management
 //*****************************************************************************
 
 nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -426,16 +426,34 @@ public:
    */
   void SetHasTouchEventListeners()
   {
     mMayHaveTouchEventListener = PR_TRUE;
     MaybeUpdateTouchState();
   }
 
   /**
+   * Call this to check whether some node (this window, its document,
+   * or content in that document) has a MozAudioAvailable event listener.
+   */
+  PRBool HasAudioAvailableEventListeners()
+  {
+    return mMayHaveAudioAvailableEventListener;
+  }
+
+  /**
+   * Call this to indicate that some node (this window, its document,
+   * or content in that document) has a MozAudioAvailable event listener.
+   */
+  void SetHasAudioAvailableEventListeners()
+  {
+    mMayHaveAudioAvailableEventListener = PR_TRUE;
+  }
+
+  /**
    * Initialize window.java and window.Packages.
    */
   virtual void InitJavaProperties() = 0;
 
   virtual void* GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey) = 0;
   virtual void CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
                                         nsScriptObjectHolder& aHandler) = 0;
 
@@ -564,16 +582,17 @@ protected:
 
   PRUint32               mMutationBits;
 
   PRPackedBool           mIsDocumentLoaded;
   PRPackedBool           mIsHandlingResizeEvent;
   PRPackedBool           mIsInnerWindow;
   PRPackedBool           mMayHavePaintEventListener;
   PRPackedBool           mMayHaveTouchEventListener;
+  PRPackedBool           mMayHaveAudioAvailableEventListener;
 
   // This variable is used on both inner and outer windows (and they
   // should match).
   PRPackedBool           mIsModalContentWindow;
 
   // Tracks activation state that's used for :-moz-window-inactive.
   PRPackedBool           mIsActive;
 
--- a/dom/interfaces/events/Makefile.in
+++ b/dom/interfaces/events/Makefile.in
@@ -71,16 +71,17 @@ XPIDLSRCS =					\
 	nsIDOMPopupBlockedEvent.idl		\
 	nsIDOMBeforeUnloadEvent.idl		\
 	nsIDOMNSEventTarget.idl			\
 	nsIDOMSmartCardEvent.idl                \
 	nsIDOMPageTransitionEvent.idl		\
 	nsIDOMCommandEvent.idl			\
 	nsIDOMMessageEvent.idl			\
 	nsIDOMNotifyPaintEvent.idl              \
+	nsIDOMNotifyAudioAvailableEvent.idl     \
 	nsIDOMPaintRequest.idl			\
 	nsIDOMPaintRequestList.idl		\
 	nsIDOMSimpleGestureEvent.idl		\
 	nsIDOMNSMouseEvent.idl			\
 	nsIDOMMozTouchEvent.idl			\
 	nsIDOMOrientationEvent.idl              \
 	nsIDOMScrollAreaEvent.idl		\
 	nsIDOMTransitionEvent.idl		\
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMNotifyAudioAvailableEvent.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  David Humphrey <david.humphrey@senecac.on.ca>
+ *  Yury Delendik
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEvent.idl"
+#include "nsIVariant.idl"
+
+%{ C++
+#include "jspubtd.h"
+%}
+
+[scriptable, uuid(6250652d-7a6a-49a4-a2ee-9114e1e83427)]
+interface nsIDOMNotifyAudioAvailableEvent : nsIDOMEvent
+{
+  [implicit_jscontext]
+  readonly attribute jsval  frameBuffer;
+
+  readonly attribute float  time;
+
+  void initAudioAvailableEvent(in DOMString typeArg,
+                               in boolean canBubbleArg,
+                               in boolean cancelableArg,
+                               [array, size_is(frameBufferLength)] in float frameBufferPtr,
+                               in unsigned long frameBufferLength,
+                               in float time,
+                               in boolean allowAudioData);
+};
--- a/dom/interfaces/html/nsIDOMHTMLAudioElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLAudioElement.idl
@@ -33,23 +33,36 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIDOMHTMLMediaElement.idl"
 
+%{C++
+#include "jsapi.h"
+%}
+
 /**
  * The nsIDOMHTMLAudioElement interface is the interface to a HTML
  * <audio> element.
  *
  * For more information on this interface, please see
  * http://www.whatwg.org/specs/web-apps/current-work/#audio
  *
  * @status UNDER_DEVELOPMENT
  */
 
-[scriptable, uuid(5ecd8913-a738-41be-8597-7f3a4ffba017)]
+[scriptable, uuid(cd1a6a6b-bc4c-4e5a-b7da-53dccc878ab8)]
 interface nsIDOMHTMLAudioElement : nsIDOMHTMLMediaElement
 {
+  // Setup the audio stream for writing
+  void mozSetup(in PRUint32 channels, in PRUint32 rate);
+
+  // Write audio to the audio stream
+  [implicit_jscontext]
+  unsigned long mozWriteAudio(in jsval data);
+
+  // Get the current offset (measured in samples since the start) of the audio
+  // stream created using mozWriteAudio().
+  unsigned long long mozCurrentSampleOffset();
 };
-
--- a/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
@@ -52,17 +52,17 @@
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 %{C++
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 %}
 
-[scriptable, uuid(b6c9f51d-237c-44d1-842d-996f4d62c843)]
+[scriptable, uuid(f748b7db-4ab9-4370-835d-59f30c8de57c)]
 interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
 {
   // error state
   readonly attribute nsIDOMHTMLMediaError error;
 
   // network state
            attribute DOMString src;
   readonly attribute DOMString currentSrc;
@@ -96,16 +96,26 @@ interface nsIDOMHTMLMediaElement : nsIDO
   void play();
   void pause();
 
   // controls
            attribute boolean controls;
            attribute float volume;
            attribute boolean muted;
 
+  // Mozilla extension: extra stream metadata information, used as part
+  // of audioavailable events and the mozWriteAudio() method.  The
+  // mozFrameBufferLength method allows for the size of the framebuffer
+  // used within audioavailable events to be changed.  The new size must
+  // be a power of 2 between 512 and 32768.  The default size, for a
+  // media element with audio, is (mozChannels * 1024).
+  readonly attribute unsigned long mozChannels;
+  readonly attribute unsigned long mozSampleRate;
+           attribute unsigned long mozFrameBufferLength;
+
   // Mozilla extension: load data from another media element. This is like
   // load() but we don't run the resource selection algorithm; instead
   // we just set our source to other's currentSrc. This is optimized
   // so that this element will get access to all of other's cached/
   // buffered data. In fact any future data downloaded by this element or
   // other will be sharable by both elements.
   void mozLoadFrom(in nsIDOMHTMLMediaElement other);
 };
--- a/js/src/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/src/xpconnect/src/dom_quickstubs.qsconf
@@ -446,16 +446,21 @@ members = [
     '-nsICanvasRenderingContextWebGL.getRenderbufferParameter',
     '-nsICanvasRenderingContextWebGL.getProgramParameter',
     '-nsICanvasRenderingContextWebGL.texParameterf',
     '-nsICanvasRenderingContextWebGL.texParameteri',
     '-nsICanvasRenderingContextWebGL.getUniform',
     '-nsICanvasRenderingContextWebGL.getVertexAttrib',
     '-nsICanvasRenderingContextWebGL.getShaderParameter',
 
+    # Audio
+    'nsIDOMNotifyAudioAvailableEvent.frameBuffer',
+    'nsIDOMNotifyAudioAvailableEvent.time',
+    'nsIDOMHTMLAudioElement.mozWriteAudio',
+
     # dom/indexedDB
     'nsIIDBCursor.*',
     'nsIIDBDatabase.*',
     'nsIIDBDatabaseException.*',
     'nsIIDBEvent.*',
     'nsIIDBErrorEvent.*',
     'nsIIDBIndex.*',
     'nsIIDBKeyRange.*',
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -412,16 +412,17 @@ class nsHashKey;
 #define NS_CANPLAYTHROUGH      (NS_MEDIA_EVENT_START+12)
 #define NS_SEEKING             (NS_MEDIA_EVENT_START+13)
 #define NS_SEEKED              (NS_MEDIA_EVENT_START+14)
 #define NS_TIMEUPDATE          (NS_MEDIA_EVENT_START+15)
 #define NS_ENDED               (NS_MEDIA_EVENT_START+16)
 #define NS_RATECHANGE          (NS_MEDIA_EVENT_START+17)
 #define NS_DURATIONCHANGE      (NS_MEDIA_EVENT_START+18)
 #define NS_VOLUMECHANGE        (NS_MEDIA_EVENT_START+19)
+#define NS_MOZAUDIOAVAILABLE   (NS_MEDIA_EVENT_START+20)
 #endif // MOZ_MEDIA
 
 // paint notification events
 #define NS_NOTIFYPAINT_START    3400
 #define NS_AFTERPAINT           (NS_NOTIFYPAINT_START)
 #define NS_BEFOREPAINT          (NS_NOTIFYPAINT_START+1)
 
 // Simple gesture events
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -592,16 +592,28 @@ NS_CYCLE_COLLECTION_PARTICIPANT_INSTANCE
 class NS_CYCLE_COLLECTION_INNERCLASS                                           \
  : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class)                           \
 {                                                                              \
 public:                                                                        \
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY_NO_UNLINK(_class, _base_class) \
 };                                                                             \
 NS_CYCLE_COLLECTION_PARTICIPANT_INSTANCE
 
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(_class,         \
+                                                               _base_class)    \
+class NS_CYCLE_COLLECTION_INNERCLASS                                           \
+ : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class)                           \
+{                                                                              \
+public:                                                                        \
+  NS_IMETHOD RootAndUnlinkJSObjects(void *p);                                  \
+  NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure);           \
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class)           \
+};                                                                             \
+NS_CYCLE_COLLECTION_PARTICIPANT_INSTANCE
+
 /**
  * This implements a stub UnmarkPurple function for classes that want to be
  * traversed but whose AddRef/Release functions don't add/remove them to/from
  * the purple buffer. If you're just using NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  * then you don't need this.
  */
 #define NS_DECL_CYCLE_COLLECTION_UNMARK_PURPLE_STUB(_class)                    \
   NS_IMETHODIMP_(void) UnmarkPurple()                                          \