From acfadf1d4780de3f2dbf4583d538619e53b6fa12 Mon Sep 17 00:00:00 2001 From: Roy Tam Date: Wed, 3 Jul 2019 22:14:04 +0800 Subject: [PATCH] import change from rmottola/Arctic-Fox: - Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan (79ff7e5ea) (and GraphDriver.cpp vc2013 fix from bug1163469) - Bug 1147699 - Part 2: Set the content policy type on FetchEvent.request based on the content policy type of the channel; r=nsm (f4bfbb8e2) - Bug 1147699 - Part 3: Add a test for FetchEvent.request.context when intercepting an image load; (36e429469) - Bug 1147699 - Part 4: Add a test for FetchEvent.request.context when intercepting a responsive image load; (c7f4a27f3) - Bug 1147699 - Part 5: Add a test for FetchEvent.request.context when intercepting an audio element load; (40dfbeb87) - Bug 1147699 - Part 6: Add a test for FetchEvent.request.context when intercepting a video element load; (dcc6a359a) - Bug 1147699 - Part 7: Add a test for FetchEvent.request.context when intercepting a beacon load; (8cb913bff) - Bug 1147699 - Part 8: Add a test for FetchEvent.request.context when intercepting a CSP report (67d7ab74a) - Bug 1147699 - Part 9: Add tests for FetchEvent.request.context when intercepting embeds and objects; (b7f8d3c11) - Bug 1147699 - Part 10: Add a test for FetchEvent.request.context when intercepting font loads; (4108db24e) - Bug 1147699 - Part 11: Add tests for FetchEvent.request.context when intercepting iframes and frames; (981c93483) - Bug 1147699 - Part 12: Add a test for FetchEvent.request.context when intercepting new window loads; (cd1ce857f) - Bug 1147699 - Part 13: Add a test for FetchEvent.request.context when intercepting ping loads; r=nsm (377e09600) - Bug 1146610 - Add static_asserts that check the validity of the enum values that we write into the cache database; r=bkelly (ac8c96abc) - Bug 1147699 - Part 14: Add a test for FetchEvent.request.context when intercepting loads coming from plugins; r=nsm (0e1769bb4) - Bug 1148064 - Enable interception of pings through service workers; r=nsm,smaug (8dc3328cb) - Bug 1150608 Do not reuse CacheId values within an origin. (4eb46f1d5) --- docshell/base/nsDocShell.cpp | 28 +- dom/base/nsGlobalWindow.cpp | 6 +- dom/cache/DBSchema.cpp | 196 ++++++- dom/cache/Manager.cpp | 4 +- dom/cache/StreamList.cpp | 4 +- dom/cache/Types.h | 3 +- dom/media/GraphDriver.cpp | 120 ++-- dom/media/GraphDriver.h | 31 +- dom/media/MediaStreamGraph.cpp | 543 +++++++++++++++--- dom/media/MediaStreamGraph.h | 35 ++ dom/media/MediaStreamGraphImpl.h | 55 +- dom/media/TrackUnionStream.cpp | 18 +- dom/media/webaudio/AudioContext.cpp | 348 ++++++++++- dom/media/webaudio/AudioContext.h | 87 ++- dom/media/webaudio/AudioDestinationNode.cpp | 11 +- .../webaudio/AudioNodeExternalInputStream.cpp | 4 +- .../webaudio/AudioNodeExternalInputStream.h | 2 +- dom/media/webaudio/AudioNodeStream.cpp | 4 +- dom/media/webaudio/AudioNodeStream.h | 10 +- .../webaudio/MediaStreamAudioSourceNode.h | 1 + dom/media/webaudio/moz.build | 1 + dom/media/webaudio/test/mochitest.ini | 1 + .../test_audioContextSuspendResumeClose.html | 393 +++++++++++++ dom/media/webaudio/test/webaudio.js | 12 + dom/webidl/AudioContext.webidl | 34 +- dom/workers/ServiceWorkerManager.cpp | 10 + .../serviceworkers/fetch/context/beacon.sjs | 43 ++ .../fetch/context/context_test.js | 112 +++- .../fetch/context/csp-violate.sjs | 6 + .../serviceworkers/fetch/context/index.html | 352 +++++++++++- .../serviceworkers/fetch/context/ping.html | 7 + .../fetch/context/realaudio.ogg | 0 .../serviceworkers/fetch/context/realimg.jpg | 0 .../test/serviceworkers/fetch/context/xml.xml | 3 + dom/workers/test/serviceworkers/mochitest.ini | 6 + .../serviceworkers/test_request_context.html | 38 ++ image/imgLoader.cpp | 4 +- 37 files changed, 2294 insertions(+), 238 deletions(-) create mode 100644 dom/media/webaudio/test/test_audioContextSuspendResumeClose.html create mode 100644 dom/workers/test/serviceworkers/fetch/context/beacon.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/context/ping.html create mode 100644 dom/workers/test/serviceworkers/fetch/context/realaudio.ogg create mode 100644 dom/workers/test/serviceworkers/fetch/context/realimg.jpg create mode 100644 dom/workers/test/serviceworkers/fetch/context/xml.xml diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index ebd34f0092..575416cf1f 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -446,6 +446,11 @@ public: nsresult StartTimeout(); + void SetInterceptController(nsINetworkInterceptController* aInterceptController) + { + mInterceptController = aInterceptController; + } + private: ~nsPingListener(); @@ -453,6 +458,7 @@ private: nsCOMPtr mContent; nsCOMPtr mLoadGroup; nsCOMPtr mTimer; + nsCOMPtr mInterceptController; }; NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver, @@ -517,8 +523,15 @@ NS_IMETHODIMP nsPingListener::GetInterface(const nsIID& aIID, void** aResult) { if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { - NS_ADDREF_THIS(); - *aResult = (nsIChannelEventSink*)this; + nsCOMPtr copy(this); + *aResult = copy.forget().take(); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) && + mInterceptController) { + nsCOMPtr copy(mInterceptController); + *aResult = copy.forget().take(); return NS_OK; } @@ -557,13 +570,14 @@ nsPingListener::AsyncOnChannelRedirect(nsIChannel* aOldChan, return NS_OK; } -struct SendPingInfo +struct MOZ_STACK_CLASS SendPingInfo { int32_t numPings; int32_t maxPings; bool requireSameHost; nsIURI* target; nsIURI* referrer; + nsIDocShell* docShell; uint32_t referrerPolicy; }; @@ -697,6 +711,8 @@ SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI, nsPingListener* pingListener = new nsPingListener(info->requireSameHost, aContent, loadGroup); + nsCOMPtr interceptController = do_QueryInterface(info->docShell); + pingListener->SetInterceptController(interceptController); nsCOMPtr listener(pingListener); // Observe redirects as well: @@ -721,7 +737,8 @@ SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI, // Spec: http://whatwg.org/specs/web-apps/current-work/#ping static void -DispatchPings(nsIContent* aContent, +DispatchPings(nsIDocShell* aDocShell, + nsIContent* aContent, nsIURI* aTarget, nsIURI* aReferrer, uint32_t aReferrerPolicy) @@ -739,6 +756,7 @@ DispatchPings(nsIContent* aContent, info.target = aTarget; info.referrer = aReferrer; info.referrerPolicy = aReferrerPolicy; + info.docShell = aDocShell; ForEachPing(aContent, SendPing, &info); } @@ -13747,7 +13765,7 @@ nsDocShell::OnLinkClickSync(nsIContent* aContent, aDocShell, // DocShell out-param aRequest); // Request out-param if (NS_SUCCEEDED(rv)) { - DispatchPings(aContent, aURI, referer, refererPolicy); + DispatchPings(this, aContent, aURI, referer, refererPolicy); } return rv; } diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 6a7932a445..1a9863ce08 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -13180,7 +13180,8 @@ nsGlobalWindow::SuspendTimeouts(uint32_t aIncrease, // Suspend all of the AudioContexts for this window for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { - mAudioContexts[i]->Suspend(); + ErrorResult dummy; + unused << mAudioContexts[i]->Suspend(dummy); } } @@ -13240,7 +13241,8 @@ nsGlobalWindow::ResumeTimeouts(bool aThawChildren) // Resume all of the AudioContexts for this window for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { - mAudioContexts[i]->Resume(); + ErrorResult dummy; + unused << mAudioContexts[i]->Resume(dummy); } // Thaw all of the workers for this window. diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index d37b1be35f..0c74497e1e 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -16,16 +16,130 @@ #include "nsTArray.h" #include "nsCRT.h" #include "nsHttp.h" +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ResponseBinding.h" +#include "Types.h" +#include "nsIContentPolicy.h" namespace mozilla { namespace dom { namespace cache { - -const int32_t DBSchema::kMaxWipeSchemaVersion = 4; -const int32_t DBSchema::kLatestSchemaVersion = 4; +const int32_t DBSchema::kMaxWipeSchemaVersion = 6; +const int32_t DBSchema::kLatestSchemaVersion = 6; const int32_t DBSchema::kMaxEntriesPerStatement = 255; +// If any of the static_asserts below fail, it means that you have changed +// the corresponding WebIDL enum in a way that may be incompatible with the +// existing data stored in the DOM Cache. You would need to update the Cache +// database schema accordingly and adjust the failing static_assert. +static_assert(int(HeadersGuardEnum::None) == 0 && + int(HeadersGuardEnum::Request) == 1 && + int(HeadersGuardEnum::Request_no_cors) == 2 && + int(HeadersGuardEnum::Response) == 3 && + int(HeadersGuardEnum::Immutable) == 4 && + int(HeadersGuardEnum::EndGuard_) == 5, + "HeadersGuardEnum values are as expected"); +static_assert(int(RequestMode::Same_origin) == 0 && + int(RequestMode::No_cors) == 1 && + int(RequestMode::Cors) == 2 && + int(RequestMode::Cors_with_forced_preflight) == 3 && + int(RequestMode::EndGuard_) == 4, + "RequestMode values are as expected"); +static_assert(int(RequestCredentials::Omit) == 0 && + int(RequestCredentials::Same_origin) == 1 && + int(RequestCredentials::Include) == 2 && + int(RequestCredentials::EndGuard_) == 3, + "RequestCredentials values are as expected"); +static_assert(int(RequestContext::Audio) == 0 && + int(RequestContext::Beacon) == 1 && + int(RequestContext::Cspreport) == 2 && + int(RequestContext::Download) == 3 && + int(RequestContext::Embed) == 4 && + int(RequestContext::Eventsource) == 5 && + int(RequestContext::Favicon) == 6 && + int(RequestContext::Fetch) == 7 && + int(RequestContext::Font) == 8 && + int(RequestContext::Form) == 9 && + int(RequestContext::Frame) == 10 && + int(RequestContext::Hyperlink) == 11 && + int(RequestContext::Iframe) == 12 && + int(RequestContext::Image) == 13 && + int(RequestContext::Imageset) == 14 && + int(RequestContext::Import) == 15 && + int(RequestContext::Internal) == 16 && + int(RequestContext::Location) == 17 && + int(RequestContext::Manifest) == 18 && + int(RequestContext::Object) == 19 && + int(RequestContext::Ping) == 20 && + int(RequestContext::Plugin) == 21 && + int(RequestContext::Prefetch) == 22 && + int(RequestContext::Script) == 23 && + int(RequestContext::Serviceworker) == 24 && + int(RequestContext::Sharedworker) == 25 && + int(RequestContext::Subresource) == 26 && + int(RequestContext::Style) == 27 && + int(RequestContext::Track) == 28 && + int(RequestContext::Video) == 29 && + int(RequestContext::Worker) == 30 && + int(RequestContext::Xmlhttprequest) == 31 && + int(RequestContext::Xslt) == 32, + "RequestContext values are as expected"); +static_assert(int(RequestCache::Default) == 0 && + int(RequestCache::No_store) == 1 && + int(RequestCache::Reload) == 2 && + int(RequestCache::No_cache) == 3 && + int(RequestCache::Force_cache) == 4 && + int(RequestCache::Only_if_cached) == 5 && + int(RequestCache::EndGuard_) == 6, + "RequestCache values are as expected"); +static_assert(int(ResponseType::Basic) == 0 && + int(ResponseType::Cors) == 1 && + int(ResponseType::Default) == 2 && + int(ResponseType::Error) == 3 && + int(ResponseType::Opaque) == 4 && + int(ResponseType::EndGuard_) == 5, + "ResponseType values are as expected"); + +// If the static_asserts below fails, it means that you have changed the +// Namespace enum in a way that may be incompatible with the existing data +// stored in the DOM Cache. You would need to update the Cache database schema +// accordingly and adjust the failing static_assert. +static_assert(DEFAULT_NAMESPACE == 0 && + CHROME_ONLY_NAMESPACE == 1 && + NUMBER_OF_NAMESPACES == 2, + "Namespace values are as expected"); + +// If the static_asserts below fails, it means that you have changed the +// nsContentPolicy enum in a way that may be incompatible with the existing data +// stored in the DOM Cache. You would need to update the Cache database schema +// accordingly and adjust the failing static_assert. +static_assert(nsIContentPolicy::TYPE_INVALID == 0 && + nsIContentPolicy::TYPE_OTHER == 1 && + nsIContentPolicy::TYPE_SCRIPT == 2 && + nsIContentPolicy::TYPE_IMAGE == 3 && + nsIContentPolicy::TYPE_STYLESHEET == 4 && + nsIContentPolicy::TYPE_OBJECT == 5 && + nsIContentPolicy::TYPE_DOCUMENT == 6 && + nsIContentPolicy::TYPE_SUBDOCUMENT == 7 && + nsIContentPolicy::TYPE_REFRESH == 8 && + nsIContentPolicy::TYPE_XBL == 9 && + nsIContentPolicy::TYPE_PING == 10 && + nsIContentPolicy::TYPE_XMLHTTPREQUEST == 11 && + nsIContentPolicy::TYPE_DATAREQUEST == 11 && + nsIContentPolicy::TYPE_OBJECT_SUBREQUEST == 12 && + nsIContentPolicy::TYPE_DTD == 13 && + nsIContentPolicy::TYPE_FONT == 14 && + nsIContentPolicy::TYPE_MEDIA == 15 && + nsIContentPolicy::TYPE_WEBSOCKET == 16 && + nsIContentPolicy::TYPE_CSP_REPORT == 17 && + nsIContentPolicy::TYPE_XSLT == 18 && + nsIContentPolicy::TYPE_BEACON == 19 && + nsIContentPolicy::TYPE_FETCH == 20 && + nsIContentPolicy::TYPE_IMAGESET == 21, + "nsContentPolicytType values are as expected"); + using mozilla::void_t; // static @@ -72,9 +186,11 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn) // For now, the caches table mainly exists for data integrity with // foreign keys, but could be expanded to contain additional cache object // information. + // + // AUTOINCREMENT is necessary to prevent CacheId values from being reused. rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "CREATE TABLE caches (" - "id INTEGER NOT NULL PRIMARY KEY " + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " ");" )); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -89,6 +205,8 @@ DBSchema::CreateSchema(mozIStorageConnection* aConn) "request_headers_guard INTEGER NOT NULL, " "request_mode INTEGER NOT NULL, " "request_credentials INTEGER NOT NULL, " + "request_contentpolicytype INTEGER NOT NULL, " + "request_context INTEGER NOT NULL, " "request_cache INTEGER NOT NULL, " "request_body_id TEXT NULL, " "response_type INTEGER NOT NULL, " @@ -221,7 +339,7 @@ DBSchema::CreateCache(mozIStorageConnection* aConn, CacheId* aCacheIdOut) if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (NS_WARN_IF(!hasMoreData)) { return NS_ERROR_UNEXPECTED; } - rv = state->GetInt32(0, aCacheIdOut); + rv = state->GetInt64(0, aCacheIdOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; @@ -252,7 +370,7 @@ DBSchema::DeleteCache(mozIStorageConnection* aConn, CacheId aCacheId, ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(0, aCacheId); + rv = state->BindInt64Parameter(0, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); @@ -279,7 +397,7 @@ DBSchema::IsCacheOrphaned(mozIStorageConnection* aConn, ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(0, aCacheId); + rv = state->BindInt64Parameter(0, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMoreData = false; @@ -472,7 +590,7 @@ DBSchema::StorageMatch(mozIStorageConnection* aConn, if (!aParams.cacheName().EqualsLiteral("")) { bool foundCache = false; // no invalid CacheId, init to least likely real value - CacheId cacheId = INT32_MAX; + CacheId cacheId = INVALID_CACHE_ID; rv = StorageGetCacheId(aConn, aNamespace, aParams.cacheName(), &foundCache, &cacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -500,8 +618,8 @@ DBSchema::StorageMatch(mozIStorageConnection* aConn, bool hasMoreData = false; while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { - CacheId cacheId = INT32_MAX; - rv = state->GetInt32(0, &cacheId); + CacheId cacheId = INVALID_CACHE_ID; + rv = state->GetInt64(0, &cacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } cacheIdList.AppendElement(cacheId); } @@ -554,7 +672,7 @@ DBSchema::StorageGetCacheId(mozIStorageConnection* aConn, Namespace aNamespace, return rv; } - rv = state->GetInt32(0, aCacheIdOut); + rv = state->GetInt64(0, aCacheIdOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aFoundCacheOut = true; @@ -581,7 +699,7 @@ DBSchema::StoragePutCache(mozIStorageConnection* aConn, Namespace aNamespace, rv = state->BindStringParameter(1, aKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(2, aCacheId); + rv = state->BindInt64Parameter(2, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); @@ -658,7 +776,7 @@ DBSchema::QueryAll(mozIStorageConnection* aConn, CacheId aCacheId, ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(0, aCacheId); + rv = state->BindInt64Parameter(0, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMoreData = false; @@ -720,7 +838,7 @@ DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId, nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(0, aCacheId); + rv = state->BindInt64Parameter(0, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aParams.prefixMatch()) { @@ -985,6 +1103,8 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, "request_headers_guard, " "request_mode, " "request_credentials, " + "request_contentpolicytype, " + "request_context, " "request_cache, " "request_body_id, " "response_type, " @@ -995,7 +1115,7 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, "response_body_id, " "response_security_info, " "cache_id " - ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)" + ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19)" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1023,37 +1143,45 @@ DBSchema::InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindInt32Parameter(7, + static_cast(aRequest.contentPolicyType())); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindInt32Parameter(8, + static_cast(aRequest.context())); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindInt32Parameter(9, static_cast(aRequest.requestCache())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = BindId(state, 8, aRequestBodyId); + rv = BindId(state, 10, aRequestBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(9, static_cast(aResponse.type())); + rv = state->BindInt32Parameter(11, static_cast(aResponse.type())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindStringParameter(10, aResponse.url()); + rv = state->BindStringParameter(12, aResponse.url()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(11, aResponse.status()); + rv = state->BindInt32Parameter(13, aResponse.status()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindUTF8StringParameter(12, aResponse.statusText()); + rv = state->BindUTF8StringParameter(14, aResponse.statusText()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(13, + rv = state->BindInt32Parameter(15, static_cast(aResponse.headersGuard())); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = BindId(state, 14, aResponseBodyId); + rv = BindId(state, 16, aResponseBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindBlobParameter(15, reinterpret_cast + rv = state->BindBlobParameter(17, reinterpret_cast (aResponse.securityInfo().get()), aResponse.securityInfo().Length()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt32Parameter(16, aCacheId); + rv = state->BindInt64Parameter(18, aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); @@ -1239,6 +1367,8 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, "request_headers_guard, " "request_mode, " "request_credentials, " + "request_contentpolicytype, " + "request_context, " "request_cache, " "request_body_id " "FROM entries " @@ -1282,19 +1412,31 @@ DBSchema::ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId, aSavedRequestOut->mValue.credentials() = static_cast(credentials); + int32_t requestContentPolicyType; + rv = state->GetInt32(7, &requestContentPolicyType); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedRequestOut->mValue.contentPolicyType() = + static_cast(requestContentPolicyType); + + int32_t requestContext; + rv = state->GetInt32(8, &requestContext); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + aSavedRequestOut->mValue.context() = + static_cast(requestContext); + int32_t requestCache; - rv = state->GetInt32(7, &requestCache); + rv = state->GetInt32(9, &requestCache); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mValue.requestCache() = static_cast(requestCache); bool nullBody = false; - rv = state->GetIsNull(8, &nullBody); + rv = state->GetIsNull(10, &nullBody); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aSavedRequestOut->mHasBodyId = !nullBody; if (aSavedRequestOut->mHasBodyId) { - rv = ExtractId(state, 8, &aSavedRequestOut->mBodyId); + rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } diff --git a/dom/cache/Manager.cpp b/dom/cache/Manager.cpp index 0ffb3ee16b..4414b01c63 100644 --- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -1219,7 +1219,7 @@ public: : BaseAction(aManager, aListenerId, aRequestId) , mNamespace(aNamespace) , mKey(aKey) - , mCacheId(0) + , mCacheId(INVALID_CACHE_ID) { } virtual nsresult @@ -1275,7 +1275,7 @@ public: , mNamespace(aNamespace) , mKey(aKey) , mCacheDeleted(false) - , mCacheId(0) + , mCacheId(INVALID_CACHE_ID) { } virtual nsresult diff --git a/dom/cache/StreamList.cpp b/dom/cache/StreamList.cpp index 495fbcead4..3e3ac2a674 100644 --- a/dom/cache/StreamList.cpp +++ b/dom/cache/StreamList.cpp @@ -18,7 +18,7 @@ namespace cache { StreamList::StreamList(Manager* aManager, Context* aContext) : mManager(aManager) , mContext(aContext) - , mCacheId(0) + , mCacheId(INVALID_CACHE_ID) , mStreamControl(nullptr) , mActivated(false) { @@ -58,7 +58,7 @@ StreamList::Activate(CacheId aCacheId) { NS_ASSERT_OWNINGTHREAD(StreamList); MOZ_ASSERT(!mActivated); - MOZ_ASSERT(!mCacheId); + MOZ_ASSERT(mCacheId == INVALID_CACHE_ID); mActivated = true; mCacheId = aCacheId; mManager->AddRefCacheId(mCacheId); diff --git a/dom/cache/Types.h b/dom/cache/Types.h index bd2d96499e..f10473654c 100644 --- a/dom/cache/Types.h +++ b/dom/cache/Types.h @@ -26,7 +26,8 @@ enum Namespace typedef uintptr_t RequestId; static const RequestId INVALID_REQUEST_ID = 0; -typedef int32_t CacheId; +typedef int64_t CacheId; +static const CacheId INVALID_CACHE_ID = -1; struct QuotaInfo { diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index 4ea5e91889..8eaceaff9b 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -23,7 +23,7 @@ extern PRLogModuleInfo* gMediaStreamGraphLog; #ifdef ENABLE_LIFECYCLE_LOG #ifdef ANDROID #include "android/log.h" -#define LIFECYCLE_LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Goanna - MSG" , ## __VA_ARGS__); printf(__VA_ARGS__);printf("\n"); +#define LIFECYCLE_LOG(...) __android_log_print(ANDROID_LOG_INFO, "Goanna - MSG" , __VA_ARGS__); printf(__VA_ARGS__);printf("\n"); #else #define LIFECYCLE_LOG(...) printf(__VA_ARGS__);printf("\n"); #endif @@ -95,9 +95,6 @@ void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) LIFECYCLE_LOG("Switching to new driver: %p (%s)", aNextDriver, aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver"); - // Sometimes we switch twice to a new driver per iteration, this is probably a - // bug. - MOZ_ASSERT(!mNextDriver || mNextDriver->AsAudioCallbackDriver()); mNextDriver = aNextDriver; } @@ -145,7 +142,7 @@ public: LIFECYCLE_LOG("Releasing audio driver off main thread."); nsRefPtr releaseEvent = new AsyncCubebTask(mDriver->AsAudioCallbackDriver(), - AsyncCubebTask::SHUTDOWN); + AsyncCubebOperation::SHUTDOWN); mDriver = nullptr; releaseEvent->Dispatch(); } else { @@ -163,7 +160,7 @@ void GraphDriver::Shutdown() if (AsAudioCallbackDriver()) { LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n"); nsRefPtr releaseEvent = - new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); + new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); releaseEvent->Dispatch(); } else { Stop(); @@ -204,7 +201,7 @@ public: // because the osx audio stack is currently switching output device. if (!mDriver->mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) { nsRefPtr releaseEvent = - new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); + new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); mDriver->mPreviousDriver = nullptr; releaseEvent->Dispatch(); } @@ -505,36 +502,21 @@ AsyncCubebTask::Run() MOZ_ASSERT(mDriver); switch(mOperation) { - case AsyncCubebOperation::INIT: + case AsyncCubebOperation::INIT: { LIFECYCLE_LOG("AsyncCubebOperation::INIT\n"); mDriver->Init(); + mDriver->CompleteAudioContextOperations(mOperation); break; - case AsyncCubebOperation::SHUTDOWN: + } + case AsyncCubebOperation::SHUTDOWN: { LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n"); mDriver->Stop(); + + mDriver->CompleteAudioContextOperations(mOperation); + mDriver = nullptr; mShutdownGrip = nullptr; break; - case AsyncCubebOperation::SLEEP: { - { - LIFECYCLE_LOG("AsyncCubebOperation::SLEEP\n"); - MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); - // We might just have been awoken - if (mDriver->mGraphImpl->mNeedAnotherIteration) { - mDriver->mPauseRequested = false; - mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_RUNNING; - mDriver->mGraphImpl->mGraphDriverAsleep = false ; // atomic - break; - } - mDriver->Stop(); - mDriver->mGraphImpl->mGraphDriverAsleep = true; // atomic - mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_WAITING_INDEFINITELY; - mDriver->mPauseRequested = false; - mDriver->mGraphImpl->GetMonitor().Wait(PR_INTERVAL_NO_TIMEOUT); - } - STREAM_LOG(PR_LOG_DEBUG, ("Restarting audio stream from sleep.")); - mDriver->StartStream(); - break; } default: MOZ_CRASH("Operation not implemented."); @@ -546,6 +528,16 @@ AsyncCubebTask::Run() return NS_OK; } +StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation) + : mStream(aStream) + , mPromise(aPromise) + , mOperation(aOperation) +{ + MOZ_ASSERT(aPromise); +} + AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl, dom::AudioChannel aChannel) : GraphDriver(aGraphImpl) , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS) @@ -561,7 +553,9 @@ AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl, dom:: } AudioCallbackDriver::~AudioCallbackDriver() -{} +{ + MOZ_ASSERT(mPromisesForOperation.IsEmpty()); +} void AudioCallbackDriver::Init() @@ -651,12 +645,18 @@ AudioCallbackDriver::Start() if (NS_IsMainThread()) { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); nsRefPtr initEvent = - new AsyncCubebTask(this, AsyncCubebTask::INIT); + new AsyncCubebTask(this, AsyncCubebOperation::INIT); initEvent->Dispatch(); } else { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from the previous driver's thread", mGraphImpl)); Init(); + // Check if we need to resolve promises because the driver just got switched + // because of a resuming AudioContext + if (!mPromisesForOperation.IsEmpty()) { + CompleteAudioContextOperations(AsyncCubebOperation::INIT); + } + if (mPreviousDriver) { nsCOMPtr event = new MediaStreamGraphShutdownThreadRunnable(mPreviousDriver); @@ -704,7 +704,7 @@ AudioCallbackDriver::Revive() } else { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); nsRefPtr initEvent = - new AsyncCubebTask(this, AsyncCubebTask::INIT); + new AsyncCubebTask(this, AsyncCubebOperation::INIT); initEvent->Dispatch(); } } @@ -729,20 +729,6 @@ AudioCallbackDriver::GetCurrentTime() void AudioCallbackDriver::WaitForNextIteration() { -#if 0 - mGraphImpl->GetMonitor().AssertCurrentThreadOwns(); - - // We can't block on the monitor in the audio callback, so we kick off a new - // thread that will pause the audio stream, and restart it when unblocked. - // We don't want to sleep when we haven't started the driver yet. - if (!mGraphImpl->mNeedAnotherIteration && mAudioStream && mGraphImpl->Running()) { - STREAM_LOG(PR_LOG_DEBUG+1, ("AudioCallbackDriver going to sleep")); - mPauseRequested = true; - nsRefPtr sleepEvent = - new AsyncCubebTask(this, AsyncCubebTask::SLEEP); - sleepEvent->Dispatch(); - } -#endif } void @@ -1074,5 +1060,47 @@ AudioCallbackDriver::IsStarted() { return mStarted; } +void +AudioCallbackDriver::EnqueueStreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation) +{ + MonitorAutoLock mon(mGraphImpl->GetMonitor()); + mPromisesForOperation.AppendElement(StreamAndPromiseForOperation(aStream, + aPromise, + aOperation)); +} + +void AudioCallbackDriver::CompleteAudioContextOperations(AsyncCubebOperation aOperation) +{ + nsAutoTArray array; + + // We can't lock for the whole function because AudioContextOperationCompleted + // will grab the monitor + { + MonitorAutoLock mon(GraphImpl()->GetMonitor()); + array.SwapElements(mPromisesForOperation); + } + + for (int32_t i = array.Length() - 1; i >= 0; i--) { + StreamAndPromiseForOperation& s = array[i]; + if ((aOperation == AsyncCubebOperation::INIT && + s.mOperation == dom::AudioContextOperation::Resume) || + (aOperation == AsyncCubebOperation::SHUTDOWN && + s.mOperation != dom::AudioContextOperation::Resume)) { + + GraphImpl()->AudioContextOperationCompleted(s.mStream, + s.mPromise, + s.mOperation); + array.RemoveElementAt(i); + } + } + + if (!array.IsEmpty()) { + MonitorAutoLock mon(GraphImpl()->GetMonitor()); + mPromisesForOperation.AppendElements(array); + } +} + } // namepace mozilla diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h index de387ec176..5bc64866c2 100644 --- a/dom/media/GraphDriver.h +++ b/dom/media/GraphDriver.h @@ -13,6 +13,7 @@ #include "AudioSegment.h" #include "SelfRef.h" #include "mozilla/Atomics.h" +#include "AudioContext.h" struct cubeb_stream; @@ -321,6 +322,21 @@ private: GraphTime mSlice; }; +struct StreamAndPromiseForOperation +{ + StreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + nsRefPtr mStream; + void* mPromise; + dom::AudioContextOperation mOperation; +}; + +enum AsyncCubebOperation { + INIT, + SHUTDOWN +}; + /** * This is a graph driver that is based on callback functions called by the * audio api. This ensures minimal audio latency, because it means there is no @@ -392,6 +408,12 @@ public: return this; } + /* Enqueue a promise that is going to be resolved when a specific operation + * occurs on the cubeb stream. */ + void EnqueueStreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + bool IsSwitchingDevice() { #ifdef XP_MACOSX return mSelfReference; @@ -414,6 +436,8 @@ public: /* Tell the driver whether this process is using a microphone or not. This is * thread safe. */ void SetMicrophoneActive(bool aActive); + + void CompleteAudioContextOperations(AsyncCubebOperation aOperation); private: /** * On certain MacBookPro, the microphone is located near the left speaker. @@ -471,6 +495,7 @@ private: /* Thread for off-main-thread initialization and * shutdown of the audio stream. */ nsCOMPtr mInitShutdownThread; + nsAutoTArray mPromisesForOperation; dom::AudioChannel mAudioChannel; Atomic mInCallback; /* A thread has been created to be able to pause and restart the audio thread, @@ -498,12 +523,6 @@ private: class AsyncCubebTask : public nsRunnable { public: - enum AsyncCubebOperation { - INIT, - SHUTDOWN, - SLEEP - }; - AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation); diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 6e8f2d34e2..9038174142 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -24,6 +24,7 @@ #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "AudioNodeExternalInputStream.h" +#include "mozilla/dom/AudioContextBinding.h" #include #include "DOMMediaStream.h" #include "GoannaProfiler.h" @@ -122,12 +123,31 @@ MediaStreamGraphImpl::FinishStream(MediaStream* aStream) SetStreamOrderDirty(); } +static const GraphTime START_TIME_DELAYED = -1; + void MediaStreamGraphImpl::AddStream(MediaStream* aStream) { - aStream->mBufferStartTime = IterationEnd(); - mStreams.AppendElement(aStream); - STREAM_LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream)); + // Check if we're adding a stream to a suspended context, in which case, we + // add it to mSuspendedStreams, and delay setting mBufferStartTime + bool contextSuspended = false; + if (aStream->AsAudioNodeStream()) { + for (uint32_t i = 0; i < mSuspendedStreams.Length(); i++) { + if (aStream->AudioContextId() == mSuspendedStreams[i]->AudioContextId()) { + contextSuspended = true; + } + } + } + + if (contextSuspended) { + aStream->mBufferStartTime = START_TIME_DELAYED; + mSuspendedStreams.AppendElement(aStream); + STREAM_LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph, in the suspended stream array", aStream)); + } else { + aStream->mBufferStartTime = IterationEnd(); + mStreams.AppendElement(aStream); + STREAM_LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream)); + } SetStreamOrderDirty(); } @@ -151,6 +171,8 @@ MediaStreamGraphImpl::RemoveStream(MediaStream* aStream) SetStreamOrderDirty(); mStreams.RemoveElement(aStream); + mSuspendedStreams.RemoveElement(aStream); + NS_RELEASE(aStream); // probably destroying it STREAM_LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream)); @@ -397,49 +419,64 @@ MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime, Gr { nsTArray streamsReadyToFinish; nsAutoTArray streamHasOutput; + + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + streamHasOutput.SetLength(mStreams.Length()); - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - MediaStream* stream = mStreams[i]; - // Calculate blocked time and fire Blocked/Unblocked events - GraphTime blockedTime = 0; - GraphTime t = aPrevCurrentTime; - // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called - // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime == stream end time| - while (t <= aNextCurrentTime) { - GraphTime end; - bool blocked = stream->mBlocked.GetAt(t, &end); - if (blocked) { - blockedTime += std::min(end, aNextCurrentTime) - t; - } - if (blocked != stream->mNotifiedBlocked) { - for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { - MediaStreamListener* l = stream->mListeners[j]; - l->NotifyBlockingChanged(this, - blocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED); + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + + // Calculate blocked time and fire Blocked/Unblocked events + GraphTime blockedTime = 0; + GraphTime t = aPrevCurrentTime; + // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called + // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime == + // stream end time| + while (t <= aNextCurrentTime) { + GraphTime end; + bool blocked = stream->mBlocked.GetAt(t, &end); + if (blocked) { + blockedTime += std::min(end, aNextCurrentTime) - t; } - stream->mNotifiedBlocked = blocked; + if (blocked != stream->mNotifiedBlocked) { + for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { + MediaStreamListener* l = stream->mListeners[j]; + l->NotifyBlockingChanged(this, blocked + ? MediaStreamListener::BLOCKED + : MediaStreamListener::UNBLOCKED); + } + stream->mNotifiedBlocked = blocked; + } + t = end; } - t = end; + + stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime, + blockedTime); + // Advance mBlocked last so that implementations of + // AdvanceTimeVaryingValuesToCurrentTime can rely on the value of + // mBlocked. + stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime); + + if (runningAndSuspendedPair[array] == &mStreams) { + streamHasOutput[i] = blockedTime < aNextCurrentTime - aPrevCurrentTime; + // Make this an assertion when bug 957832 is fixed. + NS_WARN_IF_FALSE( + !streamHasOutput[i] || !stream->mNotifiedFinished, + "Shouldn't have already notified of finish *and* have output!"); + + if (stream->mFinished && !stream->mNotifiedFinished) { + streamsReadyToFinish.AppendElement(stream); + } + } + STREAM_LOG(PR_LOG_DEBUG + 1, + ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream, + MediaTimeToSeconds(stream->mBufferStartTime), + MediaTimeToSeconds(blockedTime))); } - - - stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime, blockedTime); - // Advance mBlocked last so that implementations of - // AdvanceTimeVaryingValuesToCurrentTime can rely on the value of mBlocked. - stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime); - - streamHasOutput[i] = blockedTime < aNextCurrentTime - aPrevCurrentTime; - // Make this an assertion when bug 957832 is fixed. - NS_WARN_IF_FALSE(!streamHasOutput[i] || !stream->mNotifiedFinished, - "Shouldn't have already notified of finish *and* have output!"); - - if (stream->mFinished && !stream->mNotifiedFinished) { - streamsReadyToFinish.AppendElement(stream); - } - STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p bufferStartTime=%f blockedTime=%f", - stream, MediaTimeToSeconds(stream->mBufferStartTime), - MediaTimeToSeconds(blockedTime))); } @@ -537,6 +574,21 @@ MediaStreamGraphImpl::MarkConsumed(MediaStream* aStream) } } +bool +MediaStreamGraphImpl::StreamSuspended(MediaStream* aStream) +{ + // Only AudioNodeStreams can be suspended, so we can shortcut here. + return aStream->AsAudioNodeStream() && + mSuspendedStreams.IndexOf(aStream) != mSuspendedStreams.NoIndex; +} + +namespace { + // Value of mCycleMarker for unvisited streams in cycle detection. + const uint32_t NOT_VISITED = UINT32_MAX; + // Value of mCycleMarker for ordered streams in muted cycles. + const uint32_t IN_MUTED_CYCLE = 1; +} + void MediaStreamGraphImpl::UpdateStreamOrder() { @@ -544,11 +596,6 @@ MediaStreamGraphImpl::UpdateStreamOrder() bool shouldAEC = false; #endif bool audioTrackPresent = false; - // Value of mCycleMarker for unvisited streams in cycle detection. - const uint32_t NOT_VISITED = UINT32_MAX; - // Value of mCycleMarker for ordered streams in muted cycles. - const uint32_t IN_MUTED_CYCLE = 1; - for (uint32_t i = 0; i < mStreams.Length(); ++i) { MediaStream* stream = mStreams[i]; stream->mIsConsumed = false; @@ -662,10 +709,17 @@ MediaStreamGraphImpl::UpdateStreamOrder() // Not-visited input streams should be processed first. // SourceMediaStreams have already been ordered. for (uint32_t i = inputs.Length(); i--; ) { + if (StreamSuspended(inputs[i]->mSource)) { + continue; + } auto input = inputs[i]->mSource->AsProcessedStream(); if (input && input->mCycleMarker == NOT_VISITED) { - input->remove(); - dfsStack.insertFront(input); + // It can be that this stream has an input which is from a suspended + // AudioContext. + if (input->isInList()) { + input->remove(); + dfsStack.insertFront(input); + } } } continue; @@ -681,6 +735,9 @@ MediaStreamGraphImpl::UpdateStreamOrder() // unless it is part of the cycle. uint32_t cycleStackMarker = 0; for (uint32_t i = inputs.Length(); i--; ) { + if (StreamSuspended(inputs[i]->mSource)) { + continue; + } auto input = inputs[i]->mSource->AsProcessedStream(); if (input) { cycleStackMarker = std::max(cycleStackMarker, input->mCycleMarker); @@ -776,29 +833,36 @@ MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions) STREAM_LOG(PR_LOG_DEBUG+1, ("Media graph %p computing blocking for time %f", this, MediaTimeToSeconds(CurrentDriver()->StateComputedTime()))); - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - MediaStream* stream = mStreams[i]; - if (!stream->mInBlockingSet) { - // Compute a partition of the streams containing 'stream' such that we can - // compute the blocking status of each subset independently. - nsAutoTArray streamSet; - AddBlockingRelatedStreamsToSet(&streamSet, stream); + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; - GraphTime end; - for (GraphTime t = CurrentDriver()->StateComputedTime(); - t < aEndBlockingDecisions; t = end) { - end = GRAPH_TIME_MAX; - RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end); - if (end < GRAPH_TIME_MAX) { - blockingDecisionsWillChange = true; + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < (*runningAndSuspendedPair[array]).Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + if (!stream->mInBlockingSet) { + // Compute a partition of the streams containing 'stream' such that we + // can + // compute the blocking status of each subset independently. + nsAutoTArray streamSet; + AddBlockingRelatedStreamsToSet(&streamSet, stream); + + GraphTime end; + for (GraphTime t = CurrentDriver()->StateComputedTime(); + t < aEndBlockingDecisions; t = end) { + end = GRAPH_TIME_MAX; + RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end); + if (end < GRAPH_TIME_MAX) { + blockingDecisionsWillChange = true; + } } } - } - GraphTime end; - stream->mBlocked.GetAt(IterationEnd(), &end); - if (end < GRAPH_TIME_MAX) { - blockingDecisionsWillChange = true; + GraphTime end; + stream->mBlocked.GetAt(IterationEnd(), &end); + if (end < GRAPH_TIME_MAX) { + blockingDecisionsWillChange = true; + } } } STREAM_LOG(PR_LOG_DEBUG+1, ("Media graph %p computed blocking for interval %f to %f", @@ -1013,14 +1077,6 @@ MediaStreamGraphImpl::PlayAudio(MediaStream* aStream, // sample. One sample may be played twice, but this should not happen // again during an unblocked sequence of track samples. StreamTime offset = GraphTimeToStreamTime(aStream, aFrom); - if (audioOutput.mLastTickWritten && - audioOutput.mLastTickWritten != offset) { - // If there is a global underrun of the MSG, this property won't hold, and - // we reset the sample count tracking. - if (offset - audioOutput.mLastTickWritten == 1) { - offset = audioOutput.mLastTickWritten; - } - } // We don't update aStream->mBufferStartTime here to account for time spent // blocked. Instead, we'll update it in UpdateCurrentTimeForStreams after @@ -1052,11 +1108,13 @@ MediaStreamGraphImpl::PlayAudio(MediaStream* aStream, } else { StreamTime endTicksNeeded = offset + toWrite; StreamTime endTicksAvailable = audio->GetDuration(); - STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing %ld samples for %f to %f (samples %ld to %ld)\n", - aStream, toWrite, MediaTimeToSeconds(t), MediaTimeToSeconds(end), - offset, endTicksNeeded)); if (endTicksNeeded <= endTicksAvailable) { + STREAM_LOG(PR_LOG_DEBUG + 1, + ("MediaStream %p writing %ld samples for %f to %f " + "(samples %ld to %ld)\n", + aStream, toWrite, MediaTimeToSeconds(t), + MediaTimeToSeconds(end), offset, endTicksNeeded)); output.AppendSlice(*audio, offset, endTicksNeeded); ticksWritten += toWrite; offset = endTicksNeeded; @@ -1067,12 +1125,22 @@ MediaStreamGraphImpl::PlayAudio(MediaStream* aStream, if (endTicksNeeded > endTicksAvailable && offset < endTicksAvailable) { output.AppendSlice(*audio, offset, endTicksAvailable); + STREAM_LOG(PR_LOG_DEBUG + 1, + ("MediaStream %p writing %ld samples for %f to %f " + "(samples %ld to %ld)\n", + aStream, toWrite, MediaTimeToSeconds(t), + MediaTimeToSeconds(end), offset, endTicksNeeded)); uint32_t available = endTicksAvailable - offset; ticksWritten += available; toWrite -= available; offset = endTicksAvailable; } output.AppendNullData(toWrite); + STREAM_LOG(PR_LOG_DEBUG + 1, + ("MediaStream %p writing %ld padding slsamples for %f to " + "%f (samples %ld to %ld)\n", + aStream, toWrite, MediaTimeToSeconds(t), + MediaTimeToSeconds(end), offset, endTicksNeeded)); ticksWritten += toWrite; } output.ApplyVolume(volume); @@ -1822,7 +1890,7 @@ MediaStreamGraphImpl::EnsureStableStateEventPosted() void MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage) { - NS_ASSERTION(NS_IsMainThread(), "main thread only"); + MOZ_ASSERT(NS_IsMainThread(), "main thread only"); NS_ASSERTION(!aMessage->GetStream() || !aMessage->GetStream()->IsDestroyed(), "Stream already destroyed"); @@ -2180,6 +2248,46 @@ MediaStream::ChangeExplicitBlockerCount(int32_t aDelta) GraphImpl()->AppendMessage(new Message(this, aDelta)); } +void +MediaStream::BlockStreamIfNeeded() +{ + class Message : public ControlMessage { + public: + explicit Message(MediaStream* aStream) : ControlMessage(aStream) + { } + virtual void Run() + { + mStream->BlockStreamIfNeededImpl( + mStream->GraphImpl()->CurrentDriver()->StateComputedTime()); + } + }; + + if (mMainThreadDestroyed) { + return; + } + GraphImpl()->AppendMessage(new Message(this)); +} + +void +MediaStream::UnblockStreamIfNeeded() +{ + class Message : public ControlMessage { + public: + explicit Message(MediaStream* aStream) : ControlMessage(aStream) + { } + virtual void Run() + { + mStream->UnblockStreamIfNeededImpl( + mStream->GraphImpl()->CurrentDriver()->StateComputedTime()); + } + }; + + if (mMainThreadDestroyed) { + return; + } + GraphImpl()->AppendMessage(new Message(this)); +} + void MediaStream::AddListenerImpl(already_AddRefed aListener) { @@ -3062,7 +3170,8 @@ MediaStreamGraph::CreateAudioNodeExternalInputStream(AudioNodeEngine* aEngine, T if (!aSampleRate) { aSampleRate = aEngine->NodeMainThread()->Context()->SampleRate(); } - AudioNodeExternalInputStream* stream = new AudioNodeExternalInputStream(aEngine, aSampleRate); + AudioNodeExternalInputStream* stream = new AudioNodeExternalInputStream( + aEngine, aSampleRate, aEngine->NodeMainThread()->Context()->Id()); NS_ADDREF(stream); MediaStreamGraphImpl* graph = static_cast(this); stream->SetGraphImpl(graph); @@ -3079,7 +3188,12 @@ MediaStreamGraph::CreateAudioNodeStream(AudioNodeEngine* aEngine, if (!aSampleRate) { aSampleRate = aEngine->NodeMainThread()->Context()->SampleRate(); } - AudioNodeStream* stream = new AudioNodeStream(aEngine, aKind, aSampleRate); + // MediaRecorders use an AudioNodeStream, but no AudioNode + AudioNode* node = aEngine->NodeMainThread(); + dom::AudioContext::AudioContextId contextIdForStream = node ? node->Context()->Id() : + NO_AUDIO_CONTEXT; + AudioNodeStream* stream = new AudioNodeStream(aEngine, aKind, aSampleRate, + contextIdForStream); NS_ADDREF(stream); MediaStreamGraphImpl* graph = static_cast(this); stream->SetGraphImpl(graph); @@ -3092,6 +3206,273 @@ MediaStreamGraph::CreateAudioNodeStream(AudioNodeEngine* aEngine, return stream; } +class GraphStartedRunnable final : public nsRunnable +{ +public: + GraphStartedRunnable(AudioNodeStream* aStream, MediaStreamGraph* aGraph) + : mStream(aStream) + , mGraph(aGraph) + { } + + NS_IMETHOD Run() { + mGraph->NotifyWhenGraphStarted(mStream); + return NS_OK; + } + +private: + nsRefPtr mStream; + MediaStreamGraph* mGraph; +}; + +void +MediaStreamGraph::NotifyWhenGraphStarted(AudioNodeStream* aStream) +{ + class GraphStartedNotificationControlMessage : public ControlMessage + { + public: + explicit GraphStartedNotificationControlMessage(AudioNodeStream* aStream) + : ControlMessage(aStream) + { + } + virtual void Run() + { + // This runs on the graph thread, so when this runs, and the current + // driver is an AudioCallbackDriver, we know the audio hardware is + // started. If not, we are going to switch soon, keep reposting this + // ControlMessage. + MediaStreamGraphImpl* graphImpl = mStream->GraphImpl(); + if (graphImpl->CurrentDriver()->AsAudioCallbackDriver()) { + nsCOMPtr event = new dom::StateChangeTask( + mStream->AsAudioNodeStream(), nullptr, AudioContextState::Running); + NS_DispatchToMainThread(event); + } else { + nsCOMPtr event = new GraphStartedRunnable( + mStream->AsAudioNodeStream(), mStream->Graph()); + NS_DispatchToMainThread(event); + } + } + virtual void RunDuringShutdown() + { + MOZ_ASSERT(false, "We should be reviving the graph?"); + } + }; + + MediaStreamGraphImpl* graphImpl = static_cast(this); + graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream)); +} + +void +MediaStreamGraphImpl::ResetVisitedStreamState() +{ + // Reset the visited/consumed/blocked state of the streams. + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + ProcessedMediaStream* ps = + (*runningAndSuspendedPair[array])[i]->AsProcessedStream(); + if (ps) { + ps->mCycleMarker = NOT_VISITED; + ps->mIsConsumed = false; + ps->mInBlockingSet = false; + } + } + } +} + +void +MediaStreamGraphImpl::StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId, + mozilla::LinkedList& aStreamSet) +{ + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + if (aAudioContextId == stream->AudioContextId()) { + aStreamSet.insertFront(stream); + } + } + } +} + +void +MediaStreamGraphImpl::MoveStreams(AudioContextOperation aAudioContextOperation, + mozilla::LinkedList& aStreamSet) +{ + // For our purpose, Suspend and Close are equivalent: we want to remove the + // streams from the set of streams that are going to be processed. + nsTArray& from = + aAudioContextOperation == AudioContextOperation::Resume ? mSuspendedStreams + : mStreams; + nsTArray& to = + aAudioContextOperation == AudioContextOperation::Resume ? mStreams + : mSuspendedStreams; + + MediaStream* stream; + while ((stream = aStreamSet.getFirst())) { + // It is posible to not find the stream here, if there has been two + // suspend/resume/close calls in a row. + auto i = from.IndexOf(stream); + if (i != from.NoIndex) { + from.RemoveElementAt(i); + to.AppendElement(stream); + } + + // If streams got added during a period where an AudioContext was suspended, + // set their buffer start time to the appropriate value now: + if (aAudioContextOperation == AudioContextOperation::Resume && + stream->mBufferStartTime == START_TIME_DELAYED) { + stream->mBufferStartTime = IterationEnd(); + } + + stream->remove(); + } + STREAM_LOG(PR_LOG_DEBUG, ("Moving streams between suspended and running" + "state: mStreams: %d, mSuspendedStreams: %d\n", mStreams.Length(), + mSuspendedStreams.Length())); +#ifdef DEBUG + // The intersection of the two arrays should be null. + for (uint32_t i = 0; i < mStreams.Length(); i++) { + for (uint32_t j = 0; j < mSuspendedStreams.Length(); j++) { + MOZ_ASSERT( + mStreams[i] != mSuspendedStreams[j], + "The suspended stream set and running stream set are not disjoint."); + } + } +#endif +} + +void +MediaStreamGraphImpl::AudioContextOperationCompleted(MediaStream* aStream, + void* aPromise, + AudioContextOperation aOperation) +{ + // This can be called from the thread created to do cubeb operation, or the + // MSG thread. The pointers passed back here are refcounted, so are still + // alive. + MonitorAutoLock lock(mMonitor); + + AudioContextState state; + switch (aOperation) { + case Suspend: state = AudioContextState::Suspended; break; + case Resume: state = AudioContextState::Running; break; + case Close: state = AudioContextState::Closed; break; + default: MOZ_CRASH("Not handled."); + } + + nsCOMPtr event = new dom::StateChangeTask( + aStream->AsAudioNodeStream(), aPromise, state); + NS_DispatchToMainThread(event); +} + +void +MediaStreamGraphImpl::ApplyAudioContextOperationImpl(AudioNodeStream* aStream, + AudioContextOperation aOperation, + void* aPromise) +{ + MOZ_ASSERT(CurrentDriver()->OnThread()); + mozilla::LinkedList streamSet; + + SetStreamOrderDirty(); + + ResetVisitedStreamState(); + + StreamSetForAudioContext(aStream->AudioContextId(), streamSet); + + MoveStreams(aOperation, streamSet); + MOZ_ASSERT(!streamSet.getFirst(), + "Streams should be removed from the list after having been moved."); + + // If we have suspended the last AudioContext, and we don't have other + // streams that have audio, this graph will automatically switch to a + // SystemCallbackDriver, because it can't find a MediaStream that has an audio + // track. When resuming, force switching to an AudioCallbackDriver. It would + // have happened at the next iteration anyways, but doing this now save + // some time. + if (aOperation == AudioContextOperation::Resume) { + if (!CurrentDriver()->AsAudioCallbackDriver()) { + AudioCallbackDriver* driver = new AudioCallbackDriver(this); + driver->EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation); + mMixer.AddCallback(driver); + CurrentDriver()->SwitchAtNextIteration(driver); + } else { + // We are resuming a context, but we are already using an + // AudioCallbackDriver, we can resolve the promise now. + AudioContextOperationCompleted(aStream, aPromise, aOperation); + } + } + // Close, suspend: check if we are going to switch to a + // SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver + // if that's the case, so it can notify the content. + // This is the same logic as in UpdateStreamOrder, but it's simpler to have it + // here as well so we don't have to store the Promise(s) on the Graph. + if (aOperation != AudioContextOperation::Resume) { + bool audioTrackPresent = false; + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + MediaStream* stream = mStreams[i]; + if (stream->AsAudioNodeStream()) { + audioTrackPresent = true; + } + for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO); + !tracks.IsEnded(); tracks.Next()) { + audioTrackPresent = true; + } + } + if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) { + CurrentDriver()->AsAudioCallbackDriver()-> + EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation); + + SystemClockDriver* driver = new SystemClockDriver(this); + CurrentDriver()->SwitchAtNextIteration(driver); + } else { + // We are closing or suspending an AudioContext, but something else is + // using the audio stream, we can resolve the promise now. + AudioContextOperationCompleted(aStream, aPromise, aOperation); + } + } +} + +void +MediaStreamGraph::ApplyAudioContextOperation(AudioNodeStream* aNodeStream, + AudioContextOperation aOperation, + void* aPromise) +{ + class AudioContextOperationControlMessage : public ControlMessage + { + public: + AudioContextOperationControlMessage(AudioNodeStream* aStream, + AudioContextOperation aOperation, + void* aPromise) + : ControlMessage(aStream) + , mAudioContextOperation(aOperation) + , mPromise(aPromise) + { + } + virtual void Run() + { + mStream->GraphImpl()->ApplyAudioContextOperationImpl( + mStream->AsAudioNodeStream(), mAudioContextOperation, mPromise); + } + virtual void RunDuringShutdown() + { + MOZ_ASSERT(false, "We should be reviving the graph?"); + } + + private: + AudioContextOperation mAudioContextOperation; + void* mPromise; + }; + + MediaStreamGraphImpl* graphImpl = static_cast(this); + graphImpl->AppendMessage( + new AudioContextOperationControlMessage(aNodeStream, aOperation, aPromise)); +} + bool MediaStreamGraph::IsNonRealtime() const { diff --git a/dom/media/MediaStreamGraph.h b/dom/media/MediaStreamGraph.h index 8b4c1eadba..b10749170c 100644 --- a/dom/media/MediaStreamGraph.h +++ b/dom/media/MediaStreamGraph.h @@ -22,6 +22,7 @@ #include #include "mozilla/dom/AudioChannelBinding.h" #include "DOMMediaStream.h" +#include "AudioContext.h" class nsIRunnable; @@ -318,6 +319,7 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream) explicit MediaStream(DOMMediaStream* aWrapper); + virtual dom::AudioContext::AudioContextId AudioContextId() const { return 0; } protected: // Protected destructor, to discourage deletion outside of Release(): @@ -364,6 +366,8 @@ public: // Explicitly block. Useful for example if a media element is pausing // and we need to stop its stream emitting its buffered data. virtual void ChangeExplicitBlockerCount(int32_t aDelta); + void BlockStreamIfNeeded(); + void UnblockStreamIfNeeded(); // Events will be dispatched by calling methods of aListener. virtual void AddListener(MediaStreamListener* aListener); virtual void RemoveListener(MediaStreamListener* aListener); @@ -465,6 +469,22 @@ public: { mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta); } + void BlockStreamIfNeededImpl(GraphTime aTime) + { + bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0; + if (blocked) { + return; + } + ChangeExplicitBlockerCountImpl(aTime, 1); + } + void UnblockStreamIfNeededImpl(GraphTime aTime) + { + bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0; + if (!blocked) { + return; + } + ChangeExplicitBlockerCountImpl(aTime, -1); + } void AddListenerImpl(already_AddRefed aListener); void RemoveListenerImpl(MediaStreamListener* aListener); void RemoveAllListenersImpl(); @@ -1227,6 +1247,21 @@ public: CreateAudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate = 0); + /* From the main thread, ask the MSG to send back an event when the graph + * thread is running, and audio is being processed. */ + void NotifyWhenGraphStarted(AudioNodeStream* aNodeStream); + /* From the main thread, suspend, resume or close an AudioContext. + * aNodeStream is the stream of the DestinationNode of the AudioContext. + * + * This can possibly pause the graph thread, releasing system resources, if + * all streams have been suspended/closed. + * + * When the operation is complete, aPromise is resolved. + */ + void ApplyAudioContextOperation(AudioNodeStream* aNodeStream, + dom::AudioContextOperation aState, + void * aPromise); + bool IsNonRealtime() const; /** * Start processing non-realtime for a specific number of ticks. diff --git a/dom/media/MediaStreamGraphImpl.h b/dom/media/MediaStreamGraphImpl.h index a4b57b7d16..5e25740ca8 100644 --- a/dom/media/MediaStreamGraphImpl.h +++ b/dom/media/MediaStreamGraphImpl.h @@ -248,6 +248,49 @@ public: * Mark aStream and all its inputs (recursively) as consumed. */ static void MarkConsumed(MediaStream* aStream); + + /** + * Given the Id of an AudioContext, return the set of all MediaStreams that + * are part of this context. + */ + void StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId, + mozilla::LinkedList& aStreamSet); + + /** + * Called when a suspend/resume/close operation has been completed, on the + * graph thread. + */ + void AudioContextOperationCompleted(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + + /** + * Apply and AudioContext operation (suspend/resume/closed), on the graph + * thread. + */ + void ApplyAudioContextOperationImpl(AudioNodeStream* aStream, + dom::AudioContextOperation aOperation, + void* aPromise); + + /* + * Move streams from the mStreams to mSuspendedStream if suspending/closing an + * AudioContext, or the inverse when resuming an AudioContext. + */ + void MoveStreams(dom::AudioContextOperation aAudioContextOperation, + mozilla::LinkedList& aStreamSet); + + /* + * Reset some state about the streams before suspending them, or resuming + * them. + */ + void ResetVisitedStreamState(); + + /* + * True if a stream is suspended, that is, is not in mStreams, but in + * mSuspendedStream. + */ + bool StreamSuspended(MediaStream* aStream); + /** * Sort mStreams so that every stream not in a cycle is after any streams * it depends on, and every stream in a cycle is marked as being in a cycle. @@ -368,7 +411,10 @@ public: /** * Returns true when there are no active streams. */ - bool IsEmpty() { return mStreams.IsEmpty() && mPortCount == 0; } + bool IsEmpty() + { + return mStreams.IsEmpty() && mSuspendedStreams.IsEmpty() && mPortCount == 0; + } // For use by control messages, on graph thread only. /** @@ -487,6 +533,13 @@ public: * unnecessary thread-safe refcount changes. */ nsTArray mStreams; + /** + * This stores MediaStreams that are part of suspended AudioContexts. + * mStreams and mSuspendStream are disjoint sets: a stream is either suspended + * or not suspended. Suspended streams are not ordered in UpdateStreamOrder, + * and are therefore not doing any processing. + */ + nsTArray mSuspendedStreams; /** * Streams from mFirstCycleBreaker to the end of mStreams produce output * before they receive input. They correspond to DelayNodes that are in diff --git a/dom/media/TrackUnionStream.cpp b/dom/media/TrackUnionStream.cpp index 52f6811879..b7a0c658a8 100644 --- a/dom/media/TrackUnionStream.cpp +++ b/dom/media/TrackUnionStream.cpp @@ -24,10 +24,10 @@ #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "AudioNodeExternalInputStream.h" +#include "webaudio/MediaStreamAudioDestinationNode.h" #include #include "DOMMediaStream.h" #include "GoannaProfiler.h" -#include "mozilla/unused.h" #ifdef MOZ_WEBRTC #include "AudioOutputObserver.h" #endif @@ -282,12 +282,16 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) : STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d", this, (long long)ticks, outputTrack->GetID())); } else { - MOZ_ASSERT(outputTrack->GetEnd() == GraphTimeToStreamTime(interval.mStart), - "Samples missing"); - StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart); - segment->AppendSlice(*aInputTrack->GetSegment(), - std::min(inputTrackEndPoint, inputStart), - std::min(inputTrackEndPoint, inputEnd)); + if (GraphImpl()->StreamSuspended(source)) { + segment->AppendNullData(aTo - aFrom); + } else { + MOZ_ASSERT(outputTrack->GetEnd() == GraphTimeToStreamTime(interval.mStart), + "Samples missing"); + StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart); + segment->AppendSlice(*aInputTrack->GetSegment(), + std::min(inputTrackEndPoint, inputStart), + std::min(inputTrackEndPoint, inputEnd)); + } } ApplyTrackDisabling(outputTrack->GetID(), segment); for (uint32_t j = 0; j < mListeners.Length(); ++j) { diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp index 8dc7a2f35a..854eef3461 100644 --- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -9,8 +9,8 @@ #include "nsPIDOMWindow.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/AnalyserNode.h" -#include "mozilla/dom/AudioContextBinding.h" #include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/dom/AudioContextBinding.h" #include "mozilla/dom/OfflineAudioContextBinding.h" #include "mozilla/dom/OwningNonNull.h" #include "MediaStreamGraph.h" @@ -42,6 +42,10 @@ namespace mozilla { namespace dom { +// 0 is a special value that MediaStreams use to denote they are not part of a +// AudioContext. +static dom::AudioContext::AudioContextId gAudioContextId = 1; + NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioContext) @@ -85,12 +89,15 @@ AudioContext::AudioContext(nsPIDOMWindow* aWindow, uint32_t aLength, float aSampleRate) : DOMEventTargetHelper(aWindow) + , mId(gAudioContextId++) , mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate)) + , mAudioContextState(AudioContextState::Suspended) , mNumberOfChannels(aNumberOfChannels) , mNodeCount(0) , mIsOffline(aIsOffline) , mIsStarted(!aIsOffline) , mIsShutDown(false) + , mCloseCalled(false) { aWindow->AddAudioContext(this); @@ -197,9 +204,22 @@ AudioContext::Constructor(const GlobalObject& aGlobal, return object.forget(); } -already_AddRefed -AudioContext::CreateBufferSource() +bool AudioContext::CheckClosed(ErrorResult& aRv) { + if (mAudioContextState == AudioContextState::Closed) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return true; + } + return false; +} + +already_AddRefed +AudioContext::CreateBufferSource(ErrorResult& aRv) +{ + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr bufferNode = new AudioBufferSourceNode(this); return bufferNode.forget(); @@ -247,6 +267,10 @@ AudioContext::CreateMediaStreamDestination(ErrorResult& aRv) return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr node = new MediaStreamAudioDestinationNode(this); return node.forget(); @@ -266,6 +290,10 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize, return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr scriptProcessor = new ScriptProcessorNode(this, aBufferSize, aNumberOfInputChannels, aNumberOfOutputChannels); @@ -273,15 +301,23 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize, } already_AddRefed -AudioContext::CreateAnalyser() +AudioContext::CreateAnalyser(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr analyserNode = new AnalyserNode(this); return analyserNode.forget(); } already_AddRefed -AudioContext::CreateStereoPanner() +AudioContext::CreateStereoPanner(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr stereoPannerNode = new StereoPannerNode(this); return stereoPannerNode.forget(); } @@ -294,6 +330,11 @@ AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement, aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } + + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr stream = aMediaElement.MozCaptureStream(aRv); if (aRv.Failed()) { return nullptr; @@ -310,19 +351,31 @@ AudioContext::CreateMediaStreamSource(DOMMediaStream& aMediaStream, return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + return MediaStreamAudioSourceNode::Create(this, &aMediaStream, aRv); } already_AddRefed -AudioContext::CreateGain() +AudioContext::CreateGain(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr gainNode = new GainNode(this); return gainNode.forget(); } already_AddRefed -AudioContext::CreateWaveShaper() +AudioContext::CreateWaveShaper(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr waveShaperNode = new WaveShaperNode(this); return waveShaperNode.forget(); } @@ -330,25 +383,38 @@ AudioContext::CreateWaveShaper() already_AddRefed AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + if (aMaxDelayTime > 0. && aMaxDelayTime < 180.) { nsRefPtr delayNode = new DelayNode(this, aMaxDelayTime); return delayNode.forget(); } + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } already_AddRefed -AudioContext::CreatePanner() +AudioContext::CreatePanner(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr pannerNode = new PannerNode(this); mPannerNodes.PutEntry(pannerNode); return pannerNode.forget(); } already_AddRefed -AudioContext::CreateConvolver() +AudioContext::CreateConvolver(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr convolverNode = new ConvolverNode(this); return convolverNode.forget(); } @@ -362,6 +428,10 @@ AudioContext::CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv) return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr splitterNode = new ChannelSplitterNode(this, aNumberOfOutputs); return splitterNode.forget(); @@ -376,30 +446,46 @@ AudioContext::CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv) return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr mergerNode = new ChannelMergerNode(this, aNumberOfInputs); return mergerNode.forget(); } already_AddRefed -AudioContext::CreateDynamicsCompressor() +AudioContext::CreateDynamicsCompressor(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr compressorNode = new DynamicsCompressorNode(this); return compressorNode.forget(); } already_AddRefed -AudioContext::CreateBiquadFilter() +AudioContext::CreateBiquadFilter(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr filterNode = new BiquadFilterNode(this); return filterNode.forget(); } already_AddRefed -AudioContext::CreateOscillator() +AudioContext::CreateOscillator(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr oscillatorNode = new OscillatorNode(this); return oscillatorNode.forget(); @@ -587,22 +673,239 @@ AudioContext::Shutdown() } } -void -AudioContext::Suspend() +AudioContextState AudioContext::State() const { - MediaStream* ds = DestinationStream(); - if (ds) { - ds->ChangeExplicitBlockerCount(1); - } + return mAudioContextState; } -void -AudioContext::Resume() +StateChangeTask::StateChangeTask(AudioContext* aAudioContext, + void* aPromise, + AudioContextState aNewState) + : mAudioContext(aAudioContext) + , mPromise(aPromise) + , mAudioNodeStream(nullptr) + , mNewState(aNewState) { + MOZ_ASSERT(NS_IsMainThread(), + "This constructor should be used from the main thread."); +} + +StateChangeTask::StateChangeTask(AudioNodeStream* aStream, + void* aPromise, + AudioContextState aNewState) + : mAudioContext(nullptr) + , mPromise(aPromise) + , mAudioNodeStream(aStream) + , mNewState(aNewState) +{ + MOZ_ASSERT(!NS_IsMainThread(), + "This constructor should be used from the graph thread."); +} + +NS_IMETHODIMP +StateChangeTask::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAudioContext && !mAudioNodeStream) { + return NS_OK; + } + if (mAudioNodeStream) { + AudioNode* node = mAudioNodeStream->Engine()->NodeMainThread(); + if (!node) { + return NS_OK; + } + mAudioContext = node->Context(); + if (!mAudioContext) { + return NS_OK; + } + } + + mAudioContext->OnStateChanged(mPromise, mNewState); + // We have can't call Release() on the AudioContext on the MSG thread, so we + // unref it here, on the main thread. + mAudioContext = nullptr; + + return NS_OK; +} + +/* This runnable allows to fire the "statechange" event */ +class OnStateChangeTask final : public nsRunnable +{ +public: + explicit OnStateChangeTask(AudioContext* aAudioContext) + : mAudioContext(aAudioContext) + {} + + NS_IMETHODIMP + Run() override + { + nsCOMPtr parent = do_QueryInterface(mAudioContext->GetParentObject()); + if (!parent) { + return NS_ERROR_FAILURE; + } + + nsIDocument* doc = parent->GetExtantDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + return nsContentUtils::DispatchTrustedEvent(doc, + static_cast(mAudioContext), + NS_LITERAL_STRING("statechange"), + false, false); + } + +private: + nsRefPtr mAudioContext; +}; + + + +void +AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState) +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT((mAudioContextState == AudioContextState::Suspended && + aNewState == AudioContextState::Running) || + (mAudioContextState == AudioContextState::Running && + aNewState == AudioContextState::Suspended) || + (mAudioContextState == AudioContextState::Running && + aNewState == AudioContextState::Closed) || + (mAudioContextState == AudioContextState::Suspended && + aNewState == AudioContextState::Closed) || + (mAudioContextState == aNewState), + "Invalid AudioContextState transition"); + + MOZ_ASSERT( + mIsOffline || aPromise || aNewState == AudioContextState::Running, + "We should have a promise here if this is a real-time AudioContext." + "Or this is the first time we switch to \"running\"."); + + if (aPromise) { + Promise* promise = reinterpret_cast(aPromise); + promise->MaybeResolve(JS::UndefinedHandleValue); + DebugOnly rv = mPromiseGripArray.RemoveElement(promise); + MOZ_ASSERT(rv, "Promise wasn't in the grip array?"); + } + + if (mAudioContextState != aNewState) { + nsRefPtr onStateChangeTask = + new OnStateChangeTask(this); + NS_DispatchToMainThread(onStateChangeTask); + } + + mAudioContextState = aNewState; +} + +already_AddRefed +AudioContext::Suspend(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed || + mCloseCalled) { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Suspended) { + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); + } + MediaStream* ds = DestinationStream(); if (ds) { - ds->ChangeExplicitBlockerCount(-1); + ds->BlockStreamIfNeeded(); } + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Suspend, promise); + + return promise.forget(); +} + +already_AddRefed +AudioContext::Resume(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed || + mCloseCalled) { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Running) { + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); + } + + MediaStream* ds = DestinationStream(); + if (ds) { + ds->UnblockStreamIfNeeded(); + } + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Resume, promise); + + return promise.forget(); +} + +already_AddRefed +AudioContext::Close(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed) { + promise->MaybeResolve(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + mCloseCalled = true; + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Close, promise); + + MediaStream* ds = DestinationStream(); + if (ds) { + ds->BlockStreamIfNeeded(); + } + + return promise.forget(); } void @@ -643,6 +946,9 @@ AudioContext::StartRendering(ErrorResult& aRv) mIsStarted = true; nsRefPtr promise = Promise::Create(parentObject, aRv); mDestination->StartRendering(promise); + + OnStateChanged(nullptr, AudioContextState::Running); + return promise.forget(); } diff --git a/dom/media/webaudio/AudioContext.h b/dom/media/webaudio/AudioContext.h index ed9a7fe55b..20e7f5b648 100644 --- a/dom/media/webaudio/AudioContext.h +++ b/dom/media/webaudio/AudioContext.h @@ -35,9 +35,12 @@ class DOMMediaStream; class ErrorResult; class MediaStream; class MediaStreamGraph; +class AudioNodeEngine; +class AudioNodeStream; namespace dom { +enum class AudioContextState : uint32_t; class AnalyserNode; class AudioBuffer; class AudioBufferSourceNode; @@ -64,6 +67,30 @@ class WaveShaperNode; class PeriodicWave; class Promise; +/* This runnable allows the MSG to notify the main thread when audio is actually + * flowing */ +class StateChangeTask final : public nsRunnable +{ +public: + /* This constructor should be used when this event is sent from the main + * thread. */ + StateChangeTask(AudioContext* aAudioContext, void* aPromise, AudioContextState aNewState); + + /* This constructor should be used when this event is sent from the audio + * thread. */ + StateChangeTask(AudioNodeStream* aStream, void* aPromise, AudioContextState aNewState); + + NS_IMETHOD Run() override; + +private: + nsRefPtr mAudioContext; + void* mPromise; + nsRefPtr mAudioNodeStream; + AudioContextState mNewState; +}; + +enum AudioContextOperation { Suspend, Resume, Close }; + class AudioContext final : public DOMEventTargetHelper, public nsIMemoryReporter { @@ -76,6 +103,8 @@ class AudioContext final : public DOMEventTargetHelper, ~AudioContext(); public: + typedef uint64_t AudioContextId; + NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioContext, DOMEventTargetHelper) @@ -87,8 +116,6 @@ public: } void Shutdown(); // idempotent - void Suspend(); - void Resume(); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -124,11 +151,31 @@ public: return mSampleRate; } + AudioContextId Id() const + { + return mId; + } + double CurrentTime() const; AudioListener* Listener(); - already_AddRefed CreateBufferSource(); + AudioContextState State() const; + // Those three methods return a promise to content, that is resolved when an + // (possibly long) operation is completed on the MSG (and possibly other) + // thread(s). To avoid having to match the calls and asychronous result when + // the operation is completed, we keep a reference to the promises on the main + // thread, and then send the promises pointers down the MSG thread, as a void* + // (to make it very clear that the pointer is to merely be treated as an ID). + // When back on the main thread, we can resolve or reject the promise, by + // casting it back to a `Promise*` while asserting we're back on the main + // thread and removing the reference we added. + already_AddRefed Suspend(ErrorResult& aRv); + already_AddRefed Resume(ErrorResult& aRv); + already_AddRefed Close(ErrorResult& aRv); + IMPL_EVENT_HANDLER(statechange) + + already_AddRefed CreateBufferSource(ErrorResult& aRv); already_AddRefed CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, @@ -145,16 +192,16 @@ public: ErrorResult& aRv); already_AddRefed - CreateStereoPanner(); + CreateStereoPanner(ErrorResult& aRv); already_AddRefed - CreateAnalyser(); + CreateAnalyser(ErrorResult& aRv); already_AddRefed - CreateGain(); + CreateGain(ErrorResult& aRv); already_AddRefed - CreateWaveShaper(); + CreateWaveShaper(ErrorResult& aRv); already_AddRefed CreateMediaElementSource(HTMLMediaElement& aMediaElement, ErrorResult& aRv); @@ -165,10 +212,10 @@ public: CreateDelay(double aMaxDelayTime, ErrorResult& aRv); already_AddRefed - CreatePanner(); + CreatePanner(ErrorResult& aRv); already_AddRefed - CreateConvolver(); + CreateConvolver(ErrorResult& aRv); already_AddRefed CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv); @@ -177,13 +224,13 @@ public: CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv); already_AddRefed - CreateDynamicsCompressor(); + CreateDynamicsCompressor(ErrorResult& aRv); already_AddRefed - CreateBiquadFilter(); + CreateBiquadFilter(ErrorResult& aRv); already_AddRefed - CreateOscillator(); + CreateOscillator(ErrorResult& aRv); already_AddRefed CreatePeriodicWave(const Float32Array& aRealData, const Float32Array& aImagData, @@ -244,6 +291,8 @@ public: return aTime + ExtraCurrentTime(); } + void OnStateChanged(void* aPromise, AudioContextState aNewState); + IMPL_EVENT_HANDLER(mozinterruptbegin) IMPL_EVENT_HANDLER(mozinterruptend) @@ -266,13 +315,23 @@ private: friend struct ::mozilla::WebAudioDecodeJob; + bool CheckClosed(ErrorResult& aRv); + private: + // Each AudioContext has an id, that is passed down the MediaStreams that + // back the AudioNodes, so we can easily compute the set of all the + // MediaStreams for a given context, on the MediasStreamGraph side. + const AudioContextId mId; // Note that it's important for mSampleRate to be initialized before // mDestination, as mDestination's constructor needs to access it! const float mSampleRate; + AudioContextState mAudioContextState; nsRefPtr mDestination; nsRefPtr mListener; nsTArray > mDecodeJobs; + // This array is used to keep the suspend/resume/close promises alive until + // they are resolved, so we can safely pass them accross threads. + nsTArray> mPromiseGripArray; // See RegisterActiveNode. These will keep the AudioContext alive while it // is rendering and the window remains alive. nsTHashtable > mActiveNodes; @@ -286,8 +345,12 @@ private: bool mIsOffline; bool mIsStarted; bool mIsShutDown; + // Close has been called, reject suspend and resume call. + bool mCloseCalled; }; +static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0; + } } diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp index 08030c0f72..172bbd7c85 100644 --- a/dom/media/webaudio/AudioDestinationNode.cpp +++ b/dom/media/webaudio/AudioDestinationNode.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioDestinationNode.h" +#include "AudioContext.h" #include "mozilla/dom/AudioDestinationNodeBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" @@ -176,9 +177,11 @@ public: aNode->ResolvePromise(renderedBuffer); - nsRefPtr task = + nsRefPtr onCompleteTask = new OnCompleteTask(context, renderedBuffer); - NS_DispatchToMainThread(task); + NS_DispatchToMainThread(onCompleteTask); + + context->OnStateChanged(nullptr, AudioContextState::Closed); } virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override @@ -367,6 +370,10 @@ AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, mStream->AddMainThreadListener(this); mStream->AddAudioOutput(&gWebAudioOutputKey); + if (!aIsOffline) { + graph->NotifyWhenGraphStarted(mStream->AsAudioNodeStream()); + } + if (aChannel != AudioChannel::Normal) { ErrorResult rv; SetMozAudioChannelType(aChannel, rv); diff --git a/dom/media/webaudio/AudioNodeExternalInputStream.cpp b/dom/media/webaudio/AudioNodeExternalInputStream.cpp index 15442c27f4..b165af71d0 100644 --- a/dom/media/webaudio/AudioNodeExternalInputStream.cpp +++ b/dom/media/webaudio/AudioNodeExternalInputStream.cpp @@ -12,8 +12,8 @@ using namespace mozilla::dom; namespace mozilla { -AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate) - : AudioNodeStream(aEngine, MediaStreamGraph::INTERNAL_STREAM, aSampleRate) +AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId) + : AudioNodeStream(aEngine, MediaStreamGraph::INTERNAL_STREAM, aSampleRate, aContextId) { MOZ_COUNT_CTOR(AudioNodeExternalInputStream); } diff --git a/dom/media/webaudio/AudioNodeExternalInputStream.h b/dom/media/webaudio/AudioNodeExternalInputStream.h index 92b9b782d3..d2248a8232 100644 --- a/dom/media/webaudio/AudioNodeExternalInputStream.h +++ b/dom/media/webaudio/AudioNodeExternalInputStream.h @@ -20,7 +20,7 @@ namespace mozilla { */ class AudioNodeExternalInputStream : public AudioNodeStream { public: - AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate); + AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId); protected: ~AudioNodeExternalInputStream(); diff --git a/dom/media/webaudio/AudioNodeStream.cpp b/dom/media/webaudio/AudioNodeStream.cpp index 2fcf4ab8ed..8e23ec1608 100644 --- a/dom/media/webaudio/AudioNodeStream.cpp +++ b/dom/media/webaudio/AudioNodeStream.cpp @@ -27,10 +27,12 @@ namespace mozilla { AudioNodeStream::AudioNodeStream(AudioNodeEngine* aEngine, MediaStreamGraph::AudioNodeStreamKind aKind, - TrackRate aSampleRate) + TrackRate aSampleRate, + AudioContext::AudioContextId aContextId) : ProcessedMediaStream(nullptr), mEngine(aEngine), mSampleRate(aSampleRate), + mAudioContextId(aContextId), mKind(aKind), mNumberOfInputChannels(2), mMarkAsFinishedAfterThisBlock(false), diff --git a/dom/media/webaudio/AudioNodeStream.h b/dom/media/webaudio/AudioNodeStream.h index 0956478873..52711abefb 100644 --- a/dom/media/webaudio/AudioNodeStream.h +++ b/dom/media/webaudio/AudioNodeStream.h @@ -47,7 +47,8 @@ public: */ AudioNodeStream(AudioNodeEngine* aEngine, MediaStreamGraph::AudioNodeStreamKind aKind, - TrackRate aSampleRate); + TrackRate aSampleRate, + AudioContext::AudioContextId aContextId); protected: ~AudioNodeStream(); @@ -121,6 +122,7 @@ public: // Any thread AudioNodeEngine* Engine() { return mEngine; } TrackRate SampleRate() const { return mSampleRate; } + AudioContext::AudioContextId AudioContextId() const override { return mAudioContextId; } /** * Convert a time in seconds on the destination stream to ticks @@ -147,6 +149,7 @@ public: void SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf, AudioNodeSizes& aUsage) const; + protected: void AdvanceOutputSegment(); void FinishOutput(); @@ -166,8 +169,11 @@ protected: OutputChunks mLastChunks; // The stream's sampling rate const TrackRate mSampleRate; + // This is necessary to be able to find all the nodes for a given + // AudioContext. It is set on the main thread, in the constructor. + const AudioContext::AudioContextId mAudioContextId; // Whether this is an internal or external stream - MediaStreamGraph::AudioNodeStreamKind mKind; + const MediaStreamGraph::AudioNodeStreamKind mKind; // The number of input channels that this stream requires. 0 means don't care. uint32_t mNumberOfInputChannels; // The mixing modes diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.h b/dom/media/webaudio/MediaStreamAudioSourceNode.h index d031290da7..b888289304 100644 --- a/dom/media/webaudio/MediaStreamAudioSourceNode.h +++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h @@ -35,6 +35,7 @@ public: NS_ERROR("MediaStreamAudioSourceNodeEngine bad parameter index"); } } + private: bool mEnabled; }; diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build index b1bc05dd25..966c7294c0 100644 --- a/dom/media/webaudio/moz.build +++ b/dom/media/webaudio/moz.build @@ -31,6 +31,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'FFTBlock.h', + 'MediaStreamAudioDestinationNode.h', ] EXPORTS.mozilla.dom += [ diff --git a/dom/media/webaudio/test/mochitest.ini b/dom/media/webaudio/test/mochitest.ini index e17eff686d..088a30dfae 100644 --- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -43,6 +43,7 @@ skip-if = (toolkit == 'android' && (processor == 'x86' || debug)) || os == 'win' skip-if = (toolkit == 'gonk') || (toolkit == 'android') || debug #bug 906752 [test_audioBufferSourceNodePassThrough.html] [test_AudioContext.html] +[test_audioContextSuspendResumeClose.html] [test_audioDestinationNode.html] [test_AudioListener.html] [test_audioParamExponentialRamp.html] diff --git a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html new file mode 100644 index 0000000000..0bcb9546a1 --- /dev/null +++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html @@ -0,0 +1,393 @@ + + + + Test suspend, resume and close method of the AudioContext + + + + + +
+
+
+ + diff --git a/dom/media/webaudio/test/webaudio.js b/dom/media/webaudio/test/webaudio.js index 0effda1a1e..34a69fa92f 100644 --- a/dom/media/webaudio/test/webaudio.js +++ b/dom/media/webaudio/test/webaudio.js @@ -33,6 +33,18 @@ function expectTypeError(func) { ok(threw, "The exception was thrown"); } +function expectRejectedPromise(that, func, exceptionName) { + var promise = that[func](); + + ok(promise instanceof Promise, "Expect a Promise"); + + promise.then(function(res) { + ok(false, "Promise resolved when it should have been rejected."); + }).catch(function(err) { + is(err.name, exceptionName, "Promise correctly reject with " + exceptionName); + }); +} + function fuzzyCompare(a, b) { return Math.abs(a - b) < 9e-3; } diff --git a/dom/webidl/AudioContext.webidl b/dom/webidl/AudioContext.webidl index 94e1ac158d..791c3568af 100644 --- a/dom/webidl/AudioContext.webidl +++ b/dom/webidl/AudioContext.webidl @@ -13,6 +13,12 @@ callback DecodeSuccessCallback = void (AudioBuffer decodedData); callback DecodeErrorCallback = void (); +enum AudioContextState { + "suspended", + "running", + "closed" +}; + [Constructor, Constructor(AudioChannel audioChannelType)] interface AudioContext : EventTarget { @@ -21,6 +27,14 @@ interface AudioContext : EventTarget { readonly attribute float sampleRate; readonly attribute double currentTime; readonly attribute AudioListener listener; + readonly attribute AudioContextState state; + [Throws] + Promise suspend(); + [Throws] + Promise resume(); + [Throws] + Promise close(); + attribute EventHandler onstatechange; [NewObject, Throws] AudioBuffer createBuffer(unsigned long numberOfChannels, unsigned long length, float sampleRate); @@ -31,7 +45,7 @@ interface AudioContext : EventTarget { optional DecodeErrorCallback errorCallback); // AudioNode creation - [NewObject] + [NewObject, Throws] AudioBufferSourceNode createBufferSource(); [NewObject, Throws] @@ -42,25 +56,25 @@ interface AudioContext : EventTarget { optional unsigned long numberOfInputChannels = 2, optional unsigned long numberOfOutputChannels = 2); - [NewObject] + [NewObject, Throws] StereoPannerNode createStereoPanner(); - [NewObject] + [NewObject, Throws] AnalyserNode createAnalyser(); [NewObject, Throws, UnsafeInPrerendering] MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement); [NewObject, Throws, UnsafeInPrerendering] MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream); - [NewObject] + [NewObject, Throws] GainNode createGain(); [NewObject, Throws] DelayNode createDelay(optional double maxDelayTime = 1); - [NewObject] + [NewObject, Throws] BiquadFilterNode createBiquadFilter(); - [NewObject] + [NewObject, Throws] WaveShaperNode createWaveShaper(); - [NewObject] + [NewObject, Throws] PannerNode createPanner(); - [NewObject] + [NewObject, Throws] ConvolverNode createConvolver(); [NewObject, Throws] @@ -68,10 +82,10 @@ interface AudioContext : EventTarget { [NewObject, Throws] ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6); - [NewObject] + [NewObject, Throws] DynamicsCompressorNode createDynamicsCompressor(); - [NewObject] + [NewObject, Throws] OscillatorNode createOscillator(); [NewObject, Throws] PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag); diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 6d06d5ecde..be53fe3543 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -2174,6 +2174,7 @@ class FetchEventRunnable : public WorkerRunnable bool mIsReload; RequestMode mRequestMode; RequestCredentials mRequestCredentials; + nsContentPolicyType mContentPolicyType; public: FetchEventRunnable(WorkerPrivate* aWorkerPrivate, nsMainThreadPtrHandle& aChannel, @@ -2189,6 +2190,7 @@ public: // By default we set it to same-origin since normal HTTP fetches always // send credentials to same-origin websites unless explicitly forbidden. , mRequestCredentials(RequestCredentials::Same_origin) + , mContentPolicyType(nsIContentPolicy::TYPE_INVALID) { MOZ_ASSERT(aWorkerPrivate); } @@ -2261,6 +2263,12 @@ public: rv = httpChannel->VisitRequestHeaders(this); NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr loadInfo; + rv = channel->GetLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + mContentPolicyType = loadInfo->InternalContentPolicyType(); + return NS_OK; } @@ -2331,6 +2339,8 @@ private: return false; } + request->SetContentPolicyType(mContentPolicyType); + RootedDictionary init(aCx); init.mRequest.Construct(); init.mRequest.Value() = request; diff --git a/dom/workers/test/serviceworkers/fetch/context/beacon.sjs b/dom/workers/test/serviceworkers/fetch/context/beacon.sjs new file mode 100644 index 0000000000..8401bc29bb --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/context/beacon.sjs @@ -0,0 +1,43 @@ +/* + * This is based on dom/tests/mochitest/beacon/beacon-originheader-handler.sjs. + */ + +function handleRequest(request, response) +{ + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/plain", false); + + // case XHR-REQUEST: the xhr-request tries to query the + // stored context from the beacon request. + if (request.queryString == "queryContext") { + var context = getState("interceptContext"); + // if the beacon already stored the context - return. + if (context) { + response.write(context); + setState("interceptContext", ""); + return; + } + // otherwise wait for the beacon request + response.processAsync(); + setObjectState("sw-xhr-response", response); + return; + } + + // case BEACON-REQUEST: get the beacon context and + // store the context on the server. + var context = request.queryString; + setState("interceptContext", context); + + // if there is an xhr-request waiting, return the context now. + try{ + getObjectState("sw-xhr-response", function(xhrResponse) { + if (!xhrResponse) { + return; + } + setState("interceptContext", ""); + xhrResponse.write(context); + xhrResponse.finish(); + }); + } catch(e) { + } +} diff --git a/dom/workers/test/serviceworkers/fetch/context/context_test.js b/dom/workers/test/serviceworkers/fetch/context/context_test.js index cb3507ddbb..24b0002a96 100644 --- a/dom/workers/test/serviceworkers/fetch/context/context_test.js +++ b/dom/workers/test/serviceworkers/fetch/context/context_test.js @@ -1,15 +1,119 @@ self.addEventListener("fetch", function(event) { if (event.request.url.indexOf("index.html") >= 0 || event.request.url.indexOf("register.html") >= 0 || - event.request.url.indexOf("unregister.html") >= 0) { + event.request.url.indexOf("unregister.html") >= 0 || + event.request.url.indexOf("ping.html") >= 0 || + event.request.url.indexOf("xml.xml") >= 0 || + event.request.url.indexOf("csp-violate.sjs") >= 0) { // Handle pass-through requests event.respondWith(fetch(event.request)); } else if (event.request.url.indexOf("fetch.txt") >= 0) { var body = event.request.context == "fetch" ? "so fetch" : "so unfetch"; event.respondWith(new Response(body)); - } else { - // Fail any request that we don't know about. - event.respondWith(Promise.reject()); + } else if (event.request.url.indexOf("img.jpg") >= 0) { + if (event.request.context == "image") { + event.respondWith(fetch("realimg.jpg")); + } + } else if (event.request.url.indexOf("responsive.jpg") >= 0) { + if (event.request.context == "imageset") { + event.respondWith(fetch("realimg.jpg")); + } + } else if (event.request.url.indexOf("audio.ogg") >= 0) { + if (event.request.context == "audio") { + event.respondWith(fetch("realaudio.ogg")); + } + } else if (event.request.url.indexOf("video.ogg") >= 0) { + // FIXME: Bug 1147668: This should be "video". + if (event.request.context == "audio") { + event.respondWith(fetch("realaudio.ogg")); + } + } else if (event.request.url.indexOf("beacon.sjs") >= 0) { + if (event.request.url.indexOf("queryContext") == -1) { + event.respondWith(fetch("beacon.sjs?" + event.request.context)); + } else { + event.respondWith(fetch(event.request)); + } + } else if (event.request.url.indexOf("csp-report.sjs") >= 0) { + respondToServiceWorker(event, "csp-report"); + } else if (event.request.url.indexOf("embed") >= 0) { + respondToServiceWorker(event, "embed"); + } else if (event.request.url.indexOf("object") >= 0) { + respondToServiceWorker(event, "object"); + } else if (event.request.url.indexOf("font") >= 0) { + respondToServiceWorker(event, "font"); + } else if (event.request.url.indexOf("iframe") >= 0) { + if (event.request.context == "iframe") { + event.respondWith(fetch("context_test.js")); + } + } else if (event.request.url.indexOf("frame") >= 0) { + // FIXME: Bug 1148044: This should be "frame". + if (event.request.context == "iframe") { + event.respondWith(fetch("context_test.js")); + } + } else if (event.request.url.indexOf("newwindow") >= 0) { + respondToServiceWorker(event, "newwindow"); + } else if (event.request.url.indexOf("ping") >= 0) { + respondToServiceWorker(event, "ping"); + } else if (event.request.url.indexOf("plugin") >= 0) { + respondToServiceWorker(event, "plugin"); + } else if (event.request.url.indexOf("script.js") >= 0) { + if (event.request.context == "script") { + event.respondWith(new Response("")); + } + } else if (event.request.url.indexOf("style.css") >= 0) { + respondToServiceWorker(event, "style"); + } else if (event.request.url.indexOf("track") >= 0) { + respondToServiceWorker(event, "track"); + } else if (event.request.url.indexOf("xhr") >= 0) { + if (event.request.context == "xmlhttprequest") { + event.respondWith(new Response("")); + } + } else if (event.request.url.indexOf("xslt") >= 0) { + respondToServiceWorker(event, "xslt"); + } else if (event.request.url.indexOf("cache") >= 0) { + var cache; + var origContext = event.request.context; + event.respondWith(caches.open("cache") + .then(function(c) { + cache = c; + // Store the Request in the cache. + return cache.put(event.request, new Response("fake")); + }).then(function() { + // Read it back. + return cache.keys(event.request); + }).then(function(res) { + var req = res[0]; + // Check to see if the context remained the same. + var success = req.context === origContext; + return clients.matchAll() + .then(function(clients) { + // Report it back to the main page. + clients.forEach(function(c) { + c.postMessage({data: "cache", success: success}); + }); + })}).then(function() { + // Cleanup. + return caches.delete("cache"); + }).then(function() { + return new Response("ack"); + })); + } + // Fail any request that we don't know about. + try { + event.respondWith(Promise.reject(event.request.url)); + } catch(e) { + // Eat up the possible InvalidStateError exception that we may get if some + // code above has called respondWith too. } }); + +function respondToServiceWorker(event, data) { + event.respondWith(clients.matchAll() + .then(function(clients) { + clients.forEach(function(c) { + c.postMessage({data: data, context: event.request.context}); + }); + return new Response("ack"); + })); +} diff --git a/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs b/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs new file mode 100644 index 0000000000..4c3e76d152 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/context/csp-violate.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.setHeader("Content-Security-Policy", "default-src 'none'; report-uri /tests/dom/workers/test/serviceworkers/fetch/context/csp-report.sjs", false); + response.setHeader("Content-Type", "text/html", false); + response.write(""); +} diff --git a/dom/workers/test/serviceworkers/fetch/context/index.html b/dom/workers/test/serviceworkers/fetch/context/index.html index 7c24b30dd2..6de5772ece 100644 --- a/dom/workers/test/serviceworkers/fetch/context/index.html +++ b/dom/workers/test/serviceworkers/fetch/context/index.html @@ -1,5 +1,8 @@ +link diff --git a/dom/workers/test/serviceworkers/fetch/context/realaudio.ogg b/dom/workers/test/serviceworkers/fetch/context/realaudio.ogg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dom/workers/test/serviceworkers/fetch/context/realimg.jpg b/dom/workers/test/serviceworkers/fetch/context/realimg.jpg new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dom/workers/test/serviceworkers/fetch/context/xml.xml b/dom/workers/test/serviceworkers/fetch/context/xml.xml new file mode 100644 index 0000000000..69c64adf1d --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/context/xml.xml @@ -0,0 +1,3 @@ + + + diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index ce55cd5ec3..655d28acec 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -30,6 +30,12 @@ support-files = fetch/context/register.html fetch/context/unregister.html fetch/context/context_test.js + fetch/context/realimg.jpg + fetch/context/realaudio.ogg + fetch/context/beacon.sjs + fetch/context/csp-violate.sjs + fetch/context/ping.html + fetch/context/xml.xml fetch/https/index.html fetch/https/register.html fetch/https/unregister.html diff --git a/dom/workers/test/serviceworkers/test_request_context.html b/dom/workers/test/serviceworkers/test_request_context.html index 2386f659c3..db7726e94c 100644 --- a/dom/workers/test/serviceworkers/test_request_context.html +++ b/dom/workers/test/serviceworkers/test_request_context.html @@ -17,6 +17,38 @@