mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge inbound to m-c. a=merge
This commit is contained in:
commit
ce49d59e92
@ -3,10 +3,10 @@ support-files =
|
||||
head.js
|
||||
|
||||
[browser_popupNotification.js]
|
||||
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
skip-if = (os == "linux" && (debug || asan)) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
[browser_popupNotification_2.js]
|
||||
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
skip-if = (os == "linux" && (debug || asan)) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
[browser_popupNotification_3.js]
|
||||
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
skip-if = (os == "linux" && (debug || asan)) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
[browser_popupNotification_4.js]
|
||||
skip-if = (os == "linux" && debug) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
skip-if = (os == "linux" && (debug || asan)) || e10s # e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
|
||||
|
@ -240,8 +240,9 @@ let CustomizableUIInternal = {
|
||||
return Services.appinfo.OS == "WINNT" &&
|
||||
Services.sysinfo.getProperty("version") != "5.1";
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}, true);
|
||||
#endif
|
||||
|
@ -171,7 +171,7 @@ BezierCanvas.prototype = {
|
||||
this.ctx.closePath();
|
||||
|
||||
var circle = function(ctx, cx, cy, r) {
|
||||
return ctx.beginPath();
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, 2*Math.PI, !1);
|
||||
ctx.closePath();
|
||||
};
|
||||
|
@ -7,14 +7,6 @@ USE_RCS_MK := 1
|
||||
include $(topsrcdir)/config/makefiles/makeutils.mk
|
||||
|
||||
ifdef MOZ_APP_BASENAME
|
||||
DIST_FILES = $(srcdir)/application.ini
|
||||
|
||||
ifneq (android,$(MOZ_WIDGET_TOOLKIT))
|
||||
ifdef MOZ_UPDATER
|
||||
DIST_FILES += update-settings.ini
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef LIBXUL_SDK
|
||||
APP_INI_DEPS = $(LIBXUL_DIST)/bin/platform.ini
|
||||
else
|
||||
|
@ -70,3 +70,8 @@ FINAL_TARGET_FILES += [TOPSRCDIR + '/.gdbinit']
|
||||
# Install the clang-cl runtime library for ASAN next to the binaries we produce.
|
||||
if CONFIG['MOZ_ASAN'] and CONFIG['CLANG_CL']:
|
||||
FINAL_TARGET_FILES += [CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']]
|
||||
|
||||
if CONFIG['MOZ_APP_BASENAME']:
|
||||
DIST_FILES += ['application.ini']
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' and CONFIG['MOZ_UPDATER']:
|
||||
DIST_FILES += ['update-settings.ini']
|
||||
|
@ -14,6 +14,10 @@ if not CONFIG['CROSS_COMPILE']:
|
||||
'test-ctors.c',
|
||||
]
|
||||
|
||||
if '-flto' in CONFIG['OS_CFLAGS']:
|
||||
SOURCES['test-array.c'].flags += ['-fno-lto']
|
||||
SOURCES['test-ctors.c'].flags += ['-fno-lto']
|
||||
|
||||
HOST_SOURCES += [
|
||||
'elf.cpp',
|
||||
'elfhack.cpp',
|
||||
|
@ -21,7 +21,8 @@
|
||||
GLIBCXX_3.4.17 is from gcc 4.7.0 (174383)
|
||||
GLIBCXX_3.4.18 is from gcc 4.8.0 (190787)
|
||||
GLIBCXX_3.4.19 is from gcc 4.8.1 (199309)
|
||||
GLIBCXX_3.4.20 is from gcc 4.9.0 (199307) */
|
||||
GLIBCXX_3.4.20 is from gcc 4.9.0 (199307)
|
||||
GLIBCXX_3.4.21 is from gcc 5.0 (210290) */
|
||||
|
||||
#define GLIBCXX_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c))
|
||||
|
||||
@ -59,9 +60,14 @@ namespace std {
|
||||
template wstring& wstring::assign(wstring&&);
|
||||
#endif /* __GXX_EXPERIMENTAL_CXX0X__ */
|
||||
#endif /* (__GNUC__ == 4) && (__GNUC_MINOR__ >= 5) */
|
||||
#if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 16)
|
||||
/* Instantiate these templates to avoid GLIBCXX_3.4.16 symbol versions
|
||||
* depending on compiler optimizations */
|
||||
template int string::_S_compare(size_type, size_type);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace std __attribute__((visibility("default"))) {
|
||||
namespace std MOZ_EXPORT {
|
||||
#if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 14)
|
||||
/* Hack to avoid GLIBCXX_3.4.14 symbol versions */
|
||||
struct _List_node_base
|
||||
@ -176,3 +182,16 @@ __cxa_throw_bad_array_new_length()
|
||||
MOZ_CRASH();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 21)
|
||||
/* While we generally don't build with exceptions, we have some host tools
|
||||
* that do use them. libstdc++ from GCC 5.0 added exception constructors with
|
||||
* char const* argument. Older versions only have a constructor with
|
||||
* std::string. */
|
||||
namespace std {
|
||||
runtime_error::runtime_error(char const* s)
|
||||
: runtime_error(std::string(s))
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -711,6 +711,9 @@ else
|
||||
$(EXPAND_LIBS_EXEC) -- $(HOST_CC) -o $@ $(HOST_CFLAGS) $(HOST_LDFLAGS) $(HOST_PROGOBJS) $(HOST_LIBS) $(HOST_EXTRA_LIBS)
|
||||
endif # HOST_CPP_PROG_LINK
|
||||
endif
|
||||
ifndef CROSS_COMPILE
|
||||
$(call CHECK_STDCXX,$@)
|
||||
endif
|
||||
|
||||
#
|
||||
# This is an attempt to support generation of multiple binaries
|
||||
@ -753,6 +756,9 @@ else
|
||||
$(EXPAND_LIBS_EXEC) -- $(HOST_CC) $(HOST_OUTOPTION)$@ $(HOST_CFLAGS) $(INCLUDES) $< $(HOST_LIBS) $(HOST_EXTRA_LIBS)
|
||||
endif
|
||||
endif
|
||||
ifndef CROSS_COMPILE
|
||||
$(call CHECK_STDCXX,$@)
|
||||
endif
|
||||
|
||||
ifdef DTRACE_PROBE_OBJ
|
||||
EXTRA_DEPS += $(DTRACE_PROBE_OBJ)
|
||||
|
@ -5036,7 +5036,7 @@ static bool sRemovingScriptBlockers = false;
|
||||
void
|
||||
nsContentUtils::RemoveScriptBlocker()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!sRemovingScriptBlockers);
|
||||
NS_ASSERTION(sScriptBlockerCount != 0, "Negative script blockers");
|
||||
--sScriptBlockerCount;
|
||||
|
@ -40,3 +40,4 @@ DEPRECATED_OPERATION(SyncXMLHttpRequest)
|
||||
DEPRECATED_OPERATION(DataContainerEvent)
|
||||
DEPRECATED_OPERATION(Window_Controllers)
|
||||
DEPRECATED_OPERATION(ImportXULIntoContent)
|
||||
DEPRECATED_OPERATION(PannerNodeDoppler)
|
||||
|
@ -13057,7 +13057,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;
|
||||
nsRefPtr<Promise> d = mAudioContexts[i]->Suspend(dummy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13117,7 +13118,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;
|
||||
nsRefPtr<Promise> d = mAudioContexts[i]->Resume(dummy);
|
||||
}
|
||||
|
||||
// Thaw all of the workers for this window.
|
||||
|
@ -47,10 +47,6 @@ public:
|
||||
mResult = NS_OK;
|
||||
|
||||
#ifdef DEBUG
|
||||
// ErrorResult is extremely performance-sensitive code, where literally
|
||||
// every machine instruction matters. Initialize mMessage only to suppress
|
||||
// a debug-only warning from gcc 4.6.
|
||||
mMessage = nullptr;
|
||||
mMightHaveUnreportedJSException = false;
|
||||
mHasMessage = false;
|
||||
#endif
|
||||
|
@ -14,7 +14,7 @@ load 746813-1.html
|
||||
load 743499-negative-size.html
|
||||
skip-if(Android) load 767337-1.html
|
||||
skip-if(Android||B2G) load 780392-1.html # bug 833371 for B2G
|
||||
skip-if(Android||B2G) load 789933-1.html # bug 833371
|
||||
skip-if(Android||B2G) skip-if(gtk2Widget&&isDebugBuild) load 789933-1.html # bug 833371 for B2G, bug 1155252 for linux
|
||||
load 794463-1.html
|
||||
load 802926-1.html
|
||||
load 896047-1.html
|
||||
|
@ -170,6 +170,12 @@ this.DataStoreCursor.prototype = {
|
||||
let self = this;
|
||||
let request = aRevisionStore.openCursor(null, 'prev');
|
||||
request.onsuccess = function(aEvent) {
|
||||
if (aEvent.target.result === undefined) {
|
||||
aReject(self._window.DOMError("InvalidRevision",
|
||||
"The DataStore is corrupted"));
|
||||
return;
|
||||
}
|
||||
|
||||
self._revision = aEvent.target.result.value;
|
||||
self._objectId = 0;
|
||||
self._state = STATE_SEND_ALL;
|
||||
|
@ -542,9 +542,10 @@ private:
|
||||
// This DataStoreDBCallback is called when DataStoreDB opens the DataStore DB.
|
||||
// Then the first revision will be created if it's needed.
|
||||
class FirstRevisionIdCallback final : public DataStoreDBCallback
|
||||
, public nsIDOMEventListener
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(FirstRevisionIdCallback)
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
FirstRevisionIdCallback(uint32_t aAppId, const nsAString& aName,
|
||||
const nsAString& aManifestURL)
|
||||
@ -568,13 +569,30 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (aStatus == Success) {
|
||||
nsRefPtr<DataStoreService> service = DataStoreService::Get();
|
||||
MOZ_ASSERT(service);
|
||||
ErrorResult error;
|
||||
|
||||
nsresult rv = service->EnableDataStore(mAppId, mName, mManifestURL);
|
||||
if (aStatus == Success) {
|
||||
mTxn = aDb->Transaction();
|
||||
|
||||
nsRefPtr<IDBObjectStore> store =
|
||||
mTxn->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION), error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
mRequest = store->OpenCursor(cx, JS::UndefinedHandleValue,
|
||||
IDBCursorDirection::Prev, error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
rv = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("success"),
|
||||
this, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to enable a DataStore.");
|
||||
NS_WARNING("Failed to add an EventListener.");
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
@ -582,13 +600,22 @@ public:
|
||||
|
||||
// The DB has just been created.
|
||||
|
||||
error = CreateFirstRevision(aDb->Transaction());
|
||||
if (error.Failed()) {
|
||||
NS_WARNING("Failed to add a revision to a DataStore.");
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
CreateFirstRevision(IDBTransaction* aTxn)
|
||||
{
|
||||
MOZ_ASSERT(aTxn);
|
||||
|
||||
ErrorResult error;
|
||||
nsRefPtr<IDBObjectStore> store =
|
||||
aDb->Transaction()->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION),
|
||||
error);
|
||||
if (error.Failed()) {
|
||||
NS_WARNING("Failed to get an ObjectStore object.");
|
||||
return;
|
||||
aTxn->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION), error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.ErrorCode();
|
||||
}
|
||||
MOZ_ASSERT(store);
|
||||
|
||||
@ -604,19 +631,83 @@ public:
|
||||
nsresult rv = revision->AddRevision(cx, store, 0,
|
||||
DataStoreRevision::RevisionVoid,
|
||||
callback);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to add a revision to a DataStore.");
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIDOMEventListener
|
||||
NS_IMETHOD
|
||||
HandleEvent(nsIDOMEvent* aEvent)
|
||||
{
|
||||
AssertIsInMainProcess();
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsRefPtr<IDBRequest> request;
|
||||
request.swap(mRequest);
|
||||
|
||||
nsRefPtr<IDBTransaction> txn;
|
||||
txn.swap(mTxn);
|
||||
|
||||
request->RemoveEventListener(NS_LITERAL_STRING("success"), this, false);
|
||||
|
||||
nsString type;
|
||||
nsresult rv = aEvent->GetType(type);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
MOZ_ASSERT(type.EqualsASCII("success"));
|
||||
#endif
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
|
||||
ErrorResult error;
|
||||
JS::Rooted<JS::Value> result(cx);
|
||||
request->GetResult(cx, &result, error);
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.ErrorCode();
|
||||
}
|
||||
|
||||
// This means that the content is a IDBCursor, so the first revision already
|
||||
// exists.
|
||||
if (result.isObject()) {
|
||||
#ifdef DEBUG
|
||||
IDBCursor* cursor = nullptr;
|
||||
error = UNWRAP_OBJECT(IDBCursor, &result.toObject(), cursor);
|
||||
MOZ_ASSERT(!error.Failed());
|
||||
#endif
|
||||
|
||||
nsRefPtr<DataStoreService> service = DataStoreService::Get();
|
||||
MOZ_ASSERT(service);
|
||||
|
||||
return service->EnableDataStore(mAppId, mName, mManifestURL);
|
||||
}
|
||||
|
||||
rv = CreateFirstRevision(txn);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~FirstRevisionIdCallback() {}
|
||||
|
||||
nsRefPtr<IDBRequest> mRequest;
|
||||
nsRefPtr<IDBTransaction> mTxn;
|
||||
|
||||
uint32_t mAppId;
|
||||
nsString mName;
|
||||
nsString mManifestURL;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(FirstRevisionIdCallback, nsIDOMEventListener)
|
||||
|
||||
// This class calls the 'retrieveRevisionId' method of the DataStore object for
|
||||
// any DataStore in the 'mResults' array. When all of them are called, the
|
||||
// promise is resolved with 'mResults'.
|
||||
|
@ -2,11 +2,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_FILES = \
|
||||
bootstrap.js \
|
||||
install.rdf \
|
||||
$(NULL)
|
||||
|
||||
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
|
||||
|
||||
GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
|
||||
|
@ -5,3 +5,8 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPI_NAME = 'indexedDB'
|
||||
|
||||
DIST_FILES += [
|
||||
'bootstrap.js',
|
||||
'install.rdf',
|
||||
]
|
||||
|
@ -163,3 +163,5 @@ IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet co
|
||||
WillChangeBudgetWarning=Will-change memory consumption is too high. Surface area covers %1$S pixels, budget is the document surface area multiplied by %2$S (%3$S pixels). All occurences of will-change in the document are ignored when over budget.
|
||||
# LOCALIZATION NOTE: Do not translate "ServiceWorker".
|
||||
HittingMaxWorkersPerDomain=A ServiceWorker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The ServiceWorker is now queued and will be started after some of the other workers have completed.
|
||||
# LOCALIZATION NOTE: Do no translate "setVelocity", "PannerNode", "AudioListener", "speedOfSound" and "dopplerFactor"
|
||||
PannerNodeDopplerWarning=Use of setVelocity on the PannerNode and AudioListener, and speedOfSound and dopplerFactor on the AudioListener are deprecated and those members will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/AudioListener#Deprecated_features
|
||||
|
@ -11,40 +11,47 @@
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/unused.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
StaticRefPtr<AbstractThread> sMainThread;
|
||||
|
||||
template<>
|
||||
nsresult
|
||||
AbstractThreadImpl<nsIThread>::Dispatch(already_AddRefed<nsIRunnable> aRunnable)
|
||||
class XPCOMThreadWrapper : public AbstractThread
|
||||
{
|
||||
MediaTaskQueue::AssertInTailDispatchIfNeeded();
|
||||
nsCOMPtr<nsIRunnable> r = aRunnable;
|
||||
return mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
public:
|
||||
explicit XPCOMThreadWrapper(nsIThread* aTarget) : mTarget(aTarget) {}
|
||||
|
||||
template<>
|
||||
bool
|
||||
AbstractThreadImpl<nsIThread>::IsCurrentThreadIn()
|
||||
{
|
||||
bool in = NS_GetCurrentThread() == mTarget;
|
||||
MOZ_ASSERT_IF(in, MediaTaskQueue::GetCurrentQueue() == nullptr);
|
||||
return in;
|
||||
}
|
||||
virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) override
|
||||
{
|
||||
MediaTaskQueue::AssertInTailDispatchIfNeeded();
|
||||
nsCOMPtr<nsIRunnable> r = aRunnable;
|
||||
nsresult rv = mTarget->Dispatch(r, NS_DISPATCH_NORMAL);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
unused << rv;
|
||||
}
|
||||
|
||||
virtual bool IsCurrentThreadIn() override
|
||||
{
|
||||
bool in = NS_GetCurrentThread() == mTarget;
|
||||
MOZ_ASSERT_IF(in, MediaTaskQueue::GetCurrentQueue() == nullptr);
|
||||
return in;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<nsIThread> mTarget;
|
||||
};
|
||||
|
||||
void
|
||||
AbstractThread::MaybeTailDispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
bool aAssertDispatchSuccess)
|
||||
DispatchFailureHandling aFailureHandling)
|
||||
{
|
||||
MediaTaskQueue* currentQueue = MediaTaskQueue::GetCurrentQueue();
|
||||
if (currentQueue && currentQueue->RequiresTailDispatch()) {
|
||||
currentQueue->TailDispatcher().AddTask(this, Move(aRunnable), aAssertDispatchSuccess);
|
||||
currentQueue->TailDispatcher().AddTask(this, Move(aRunnable), aFailureHandling);
|
||||
} else {
|
||||
nsresult rv = Dispatch(Move(aRunnable));
|
||||
MOZ_DIAGNOSTIC_ASSERT(!aAssertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
unused << rv;
|
||||
Dispatch(Move(aRunnable), aFailureHandling);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +71,7 @@ AbstractThread::InitStatics()
|
||||
nsCOMPtr<nsIThread> mainThread;
|
||||
NS_GetMainThread(getter_AddRefs(mainThread));
|
||||
MOZ_DIAGNOSTIC_ASSERT(mainThread);
|
||||
sMainThread = new AbstractThreadImpl<nsIThread>(mainThread.get());
|
||||
sMainThread = new XPCOMThreadWrapper(mainThread.get());
|
||||
ClearOnShutdown(&sMainThread);
|
||||
}
|
||||
|
||||
|
@ -32,13 +32,20 @@ class AbstractThread
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractThread);
|
||||
virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable) = 0;
|
||||
|
||||
enum DispatchFailureHandling { AssertDispatchSuccess, DontAssertDispatchSuccess };
|
||||
virtual void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aHandling = AssertDispatchSuccess) = 0;
|
||||
virtual bool IsCurrentThreadIn() = 0;
|
||||
|
||||
// Convenience method for dispatching a runnable when we may be running on
|
||||
// a thread that requires runnables to be dispatched with tail dispatch.
|
||||
void MaybeTailDispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
bool aAssertDispatchSuccess = true);
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess);
|
||||
|
||||
// Returns true if dispatch is generally reliable. This is used to guard
|
||||
// against FlushableMediaTaskQueues, which should go away.
|
||||
virtual bool IsDispatchReliable() { return true; }
|
||||
|
||||
// Convenience method for getting an AbstractThread for the main thread.
|
||||
static AbstractThread* MainThread();
|
||||
@ -50,17 +57,6 @@ protected:
|
||||
virtual ~AbstractThread() {}
|
||||
};
|
||||
|
||||
template<typename TargetType>
|
||||
class AbstractThreadImpl : public AbstractThread
|
||||
{
|
||||
public:
|
||||
explicit AbstractThreadImpl(TargetType* aTarget) : mTarget(aTarget) {}
|
||||
virtual nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable);
|
||||
virtual bool IsCurrentThreadIn();
|
||||
private:
|
||||
nsRefPtr<TargetType> mTarget;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
@ -256,7 +256,11 @@ AudioSink::Cleanup()
|
||||
AssertCurrentThreadInMonitor();
|
||||
nsRefPtr<AudioStream> audioStream;
|
||||
audioStream.swap(mAudioStream);
|
||||
mStateMachine->DispatchOnAudioSinkComplete();
|
||||
// Suppress the callback when the stop is requested by MediaDecoderStateMachine.
|
||||
// See Bug 115334.
|
||||
if (!mStopAudioThread) {
|
||||
mStateMachine->DispatchOnAudioSinkComplete();
|
||||
}
|
||||
|
||||
ReentrantMonitorAutoExit exit(GetReentrantMonitor());
|
||||
audioStream->Shutdown();
|
||||
|
@ -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, "Gecko - MSG" , ## __VA_ARGS__); printf(__VA_ARGS__);printf("\n");
|
||||
#define LIFECYCLE_LOG(...) __android_log_print(ANDROID_LOG_INFO, "Gecko - 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<AsyncCubebTask> 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<AsyncCubebTask> 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<AsyncCubebTask> 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<AsyncCubebTask> 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<nsIRunnable> 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<AsyncCubebTask> 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<AsyncCubebTask> 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<StreamAndPromiseForOperation, 1> 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 == AudioContextOperation::Resume) ||
|
||||
(aOperation == AsyncCubebOperation::SHUTDOWN &&
|
||||
s.mOperation != 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
|
||||
|
@ -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<MediaStream> 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<nsIThread> mInitShutdownThread;
|
||||
nsAutoTArray<StreamAndPromiseForOperation, 1> mPromisesForOperation;
|
||||
dom::AudioChannel mAudioChannel;
|
||||
Atomic<bool> 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);
|
||||
|
||||
|
@ -239,7 +239,7 @@ protected:
|
||||
// then shut down the thread or task queue that the promise result would
|
||||
// be dispatched on. So we unfortunately can't assert that promise
|
||||
// dispatch succeeds. :-(
|
||||
aDispatcher.AddTask(mResponseTarget, runnable.forget(), /* aAssertDispatchSuccess = */ false);
|
||||
aDispatcher.AddTask(mResponseTarget, runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -321,6 +321,7 @@ public:
|
||||
aDispatcher.AssertIsTailDispatcherIfRequired();
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(aResponseThread->IsDispatchReliable());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveConsumer);
|
||||
mHaveConsumer = true;
|
||||
nsRefPtr<ThenValueBase> thenValue = new ThenValue<ThisType, ResolveMethodType, RejectMethodType>(
|
||||
@ -670,6 +671,7 @@ ProxyInternal(AbstractThread* aTarget, MethodCallBase<PromiseType>* aMethodCall,
|
||||
{
|
||||
nsRefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
|
||||
nsRefPtr<ProxyRunnable<PromiseType>> r = new ProxyRunnable<PromiseType>(p, aMethodCall);
|
||||
MOZ_ASSERT(aTarget->IsDispatchReliable());
|
||||
aDispatcher.AddTask(aTarget, r.forget());
|
||||
return Move(p);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "AudioNodeEngine.h"
|
||||
#include "AudioNodeStream.h"
|
||||
#include "AudioNodeExternalInputStream.h"
|
||||
#include "mozilla/dom/AudioContextBinding.h"
|
||||
#include <algorithm>
|
||||
#include "DOMMediaStream.h"
|
||||
#include "GeckoProfiler.h"
|
||||
@ -102,12 +103,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();
|
||||
}
|
||||
@ -131,6 +151,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));
|
||||
@ -282,7 +304,7 @@ MediaStreamGraphImpl::UpdateBufferSufficiencyState(SourceMediaStream* aStream)
|
||||
// win8 64 debug when invoked from noop_resampler::fill on the cubeb audio
|
||||
// thread.
|
||||
nsCOMPtr<nsIRunnable> r = runnables[i].mRunnable;
|
||||
runnables[i].mTarget->MaybeTailDispatch(r.forget(), /* aAssertDispatchSuccess = */ false);
|
||||
runnables[i].mTarget->MaybeTailDispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,49 +402,64 @@ MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime, Gr
|
||||
{
|
||||
nsTArray<MediaStream*> streamsReadyToFinish;
|
||||
nsAutoTArray<bool,800> streamHasOutput;
|
||||
|
||||
nsTArray<MediaStream*>* 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)));
|
||||
}
|
||||
|
||||
|
||||
@ -520,6 +557,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()
|
||||
{
|
||||
@ -527,11 +579,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;
|
||||
@ -647,10 +694,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;
|
||||
@ -666,6 +720,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);
|
||||
@ -761,29 +818,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<MediaStream*,10> streamSet;
|
||||
AddBlockingRelatedStreamsToSet(&streamSet, stream);
|
||||
nsTArray<MediaStream*>* 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<MediaStream*, 10> 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",
|
||||
@ -998,14 +1062,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
|
||||
@ -1037,11 +1093,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;
|
||||
@ -1052,12 +1110,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);
|
||||
@ -1789,7 +1857,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");
|
||||
@ -2148,6 +2216,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<MediaStreamListener> aListener)
|
||||
{
|
||||
@ -3031,7 +3139,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<MediaStreamGraphImpl*>(this);
|
||||
stream->SetGraphImpl(graph);
|
||||
@ -3048,7 +3157,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<MediaStreamGraphImpl*>(this);
|
||||
stream->SetGraphImpl(graph);
|
||||
@ -3061,6 +3175,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<AudioNodeStream> 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<nsIRunnable> event = new dom::StateChangeTask(
|
||||
mStream->AsAudioNodeStream(), nullptr, AudioContextState::Running);
|
||||
NS_DispatchToMainThread(event);
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> 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<MediaStreamGraphImpl*>(this);
|
||||
graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream));
|
||||
}
|
||||
|
||||
void
|
||||
MediaStreamGraphImpl::ResetVisitedStreamState()
|
||||
{
|
||||
// Reset the visited/consumed/blocked state of the streams.
|
||||
nsTArray<MediaStream*>* 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<MediaStream>& aStreamSet)
|
||||
{
|
||||
nsTArray<MediaStream*>* 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<MediaStream>& 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<MediaStream*>& from =
|
||||
aAudioContextOperation == AudioContextOperation::Resume ? mSuspendedStreams
|
||||
: mStreams;
|
||||
nsTArray<MediaStream*>& 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<nsIRunnable> 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<MediaStream> 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<MediaStreamGraphImpl*>(this);
|
||||
graphImpl->AppendMessage(
|
||||
new AudioContextOperationControlMessage(aNodeStream, aOperation, aPromise));
|
||||
}
|
||||
|
||||
bool
|
||||
MediaStreamGraph::IsNonRealtime() const
|
||||
{
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <speex/speex_resampler.h>
|
||||
#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<MediaStreamListener> 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.
|
||||
|
@ -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<MediaStream>& 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<MediaStream>& 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<MediaStream*> 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<MediaStream*> mSuspendedStreams;
|
||||
/**
|
||||
* Streams from mFirstCycleBreaker to the end of mStreams produce output
|
||||
* before they receive input. They correspond to DelayNodes that are in
|
||||
|
@ -40,14 +40,6 @@ MediaTaskQueue::~MediaTaskQueue()
|
||||
MOZ_COUNT_DTOR(MediaTaskQueue);
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaTaskQueue::Dispatch(TemporaryRef<nsIRunnable> aRunnable)
|
||||
{
|
||||
AssertInTailDispatchIfNeeded(); // Do this before acquiring the monitor.
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
return DispatchLocked(aRunnable, AbortIfFlushing);
|
||||
}
|
||||
|
||||
TaskDispatcher&
|
||||
MediaTaskQueue::TailDispatcher()
|
||||
{
|
||||
@ -57,25 +49,17 @@ MediaTaskQueue::TailDispatcher()
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaTaskQueue::ForceDispatch(TemporaryRef<nsIRunnable> aRunnable)
|
||||
{
|
||||
AssertInTailDispatchIfNeeded(); // Do this before acquiring the monitor.
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
return DispatchLocked(aRunnable, Forced);
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaTaskQueue::DispatchLocked(TemporaryRef<nsIRunnable> aRunnable,
|
||||
DispatchMode aMode)
|
||||
MediaTaskQueue::DispatchLocked(already_AddRefed<nsIRunnable> aRunnable, DispatchMode aMode)
|
||||
{
|
||||
mQueueMonitor.AssertCurrentThreadOwns();
|
||||
nsCOMPtr<nsIRunnable> r = aRunnable;
|
||||
if (mIsFlushing && aMode == AbortIfFlushing) {
|
||||
return NS_ERROR_ABORT;
|
||||
}
|
||||
if (mIsShutdown) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mTasks.push(TaskQueueEntry(aRunnable, aMode == Forced));
|
||||
mTasks.push(r.forget());
|
||||
if (mIsRunning) {
|
||||
return NS_OK;
|
||||
}
|
||||
@ -109,12 +93,11 @@ public:
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult WaitUntilDone() {
|
||||
void WaitUntilDone() {
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
while (!mDone) {
|
||||
mon.Wait();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
RefPtr<nsIRunnable> mRunnable;
|
||||
@ -122,13 +105,12 @@ private:
|
||||
bool mDone;
|
||||
};
|
||||
|
||||
nsresult
|
||||
void
|
||||
MediaTaskQueue::SyncDispatch(TemporaryRef<nsIRunnable> aRunnable) {
|
||||
NS_WARNING("MediaTaskQueue::SyncDispatch is dangerous and deprecated. Stop using this!");
|
||||
RefPtr<MediaTaskQueueSyncRunnable> task(new MediaTaskQueueSyncRunnable(aRunnable));
|
||||
nsresult rv = Dispatch(task);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return task->WaitUntilDone();
|
||||
Dispatch(task);
|
||||
task->WaitUntilDone();
|
||||
}
|
||||
|
||||
void
|
||||
@ -187,7 +169,8 @@ FlushableMediaTaskQueue::FlushAndDispatch(TemporaryRef<nsIRunnable> aRunnable)
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
AutoSetFlushing autoFlush(this);
|
||||
FlushLocked();
|
||||
nsresult rv = DispatchLocked(aRunnable, IgnoreFlushing);
|
||||
nsCOMPtr<nsIRunnable> r = dont_AddRef(aRunnable.take());
|
||||
nsresult rv = DispatchLocked(r.forget(), IgnoreFlushing);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
AwaitIdleLocked();
|
||||
return NS_OK;
|
||||
@ -199,13 +182,9 @@ FlushableMediaTaskQueue::FlushLocked()
|
||||
mQueueMonitor.AssertCurrentThreadOwns();
|
||||
MOZ_ASSERT(mIsFlushing);
|
||||
|
||||
// Clear the tasks, but preserve those with mForceDispatch by re-appending
|
||||
// them to the queue.
|
||||
size_t numTasks = mTasks.size();
|
||||
for (size_t i = 0; i < numTasks; ++i) {
|
||||
if (mTasks.front().mForceDispatch) {
|
||||
mTasks.push(mTasks.front());
|
||||
}
|
||||
// Clear the tasks. If this strikes you as awful, stop using a
|
||||
// FlushableMediaTaskQueue.
|
||||
while (!mTasks.empty()) {
|
||||
mTasks.pop();
|
||||
}
|
||||
}
|
||||
@ -238,7 +217,7 @@ MediaTaskQueue::Runner::Run()
|
||||
mon.NotifyAll();
|
||||
return NS_OK;
|
||||
}
|
||||
event = mQueue->mTasks.front().mRunnable;
|
||||
event = mQueue->mTasks.front();
|
||||
mQueue->mTasks.pop();
|
||||
}
|
||||
MOZ_ASSERT(event);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "SharedThreadPool.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "MediaPromise.h"
|
||||
@ -40,7 +41,12 @@ public:
|
||||
|
||||
explicit MediaTaskQueue(TemporaryRef<SharedThreadPool> aPool, bool aRequireTailDispatch = false);
|
||||
|
||||
nsresult Dispatch(TemporaryRef<nsIRunnable> aRunnable);
|
||||
void Dispatch(TemporaryRef<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> r = dont_AddRef(aRunnable.take());
|
||||
return Dispatch(r.forget(), aFailureHandling);
|
||||
}
|
||||
|
||||
// Returns a TaskDispatcher that will dispatch its tasks when the currently-
|
||||
// running tasks pops off the stack.
|
||||
@ -74,19 +80,18 @@ public:
|
||||
#endif
|
||||
|
||||
// For AbstractThread.
|
||||
nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable) override
|
||||
void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
|
||||
DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) override
|
||||
{
|
||||
RefPtr<nsIRunnable> r(aRunnable);
|
||||
return ForceDispatch(r);
|
||||
MonitorAutoLock mon(mQueueMonitor);
|
||||
nsresult rv = DispatchLocked(Move(aRunnable), AbortIfFlushing);
|
||||
MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
unused << rv;
|
||||
}
|
||||
|
||||
// This should only be used for things that absolutely can't afford to be
|
||||
// flushed. Normal operations should use Dispatch.
|
||||
nsresult ForceDispatch(TemporaryRef<nsIRunnable> aRunnable);
|
||||
|
||||
// DEPRECATED! Do not us, if a flush happens at the same time, this function
|
||||
// can hang and block forever!
|
||||
nsresult SyncDispatch(TemporaryRef<nsIRunnable> aRunnable);
|
||||
void SyncDispatch(TemporaryRef<nsIRunnable> aRunnable);
|
||||
|
||||
// Puts the queue in a shutdown state and returns immediately. The queue will
|
||||
// remain alive at least until all the events are drained, because the Runners
|
||||
@ -118,27 +123,17 @@ protected:
|
||||
// mQueueMonitor must be held.
|
||||
void AwaitIdleLocked();
|
||||
|
||||
enum DispatchMode { AbortIfFlushing, IgnoreFlushing, Forced };
|
||||
enum DispatchMode { AbortIfFlushing, IgnoreFlushing };
|
||||
|
||||
nsresult DispatchLocked(TemporaryRef<nsIRunnable> aRunnable,
|
||||
DispatchMode aMode);
|
||||
nsresult DispatchLocked(already_AddRefed<nsIRunnable> aRunnable, DispatchMode aMode);
|
||||
|
||||
RefPtr<SharedThreadPool> mPool;
|
||||
|
||||
// Monitor that protects the queue and mIsRunning;
|
||||
Monitor mQueueMonitor;
|
||||
|
||||
struct TaskQueueEntry {
|
||||
RefPtr<nsIRunnable> mRunnable;
|
||||
bool mForceDispatch;
|
||||
|
||||
explicit TaskQueueEntry(TemporaryRef<nsIRunnable> aRunnable,
|
||||
bool aForceDispatch = false)
|
||||
: mRunnable(aRunnable), mForceDispatch(aForceDispatch) {}
|
||||
};
|
||||
|
||||
// Queue of tasks to run.
|
||||
std::queue<TaskQueueEntry> mTasks;
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mTasks;
|
||||
|
||||
// The thread currently running the task queue. We store a reference
|
||||
// to this so that IsCurrentThreadIn() can tell if the current thread
|
||||
@ -217,6 +212,8 @@ public:
|
||||
nsresult FlushAndDispatch(TemporaryRef<nsIRunnable> aRunnable);
|
||||
void Flush();
|
||||
|
||||
bool IsDispatchReliable() override { return false; }
|
||||
|
||||
private:
|
||||
|
||||
class MOZ_STACK_CLASS AutoSetFlushing
|
||||
|
@ -43,7 +43,7 @@ public:
|
||||
already_AddRefed<nsIRunnable> aRunnable) = 0;
|
||||
virtual void AddTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable,
|
||||
bool aAssertDispatchSuccess = true) = 0;
|
||||
AbstractThread::DispatchFailureHandling aFailureHandling = AbstractThread::AssertDispatchSuccess) = 0;
|
||||
|
||||
#ifdef DEBUG
|
||||
void AssertIsTailDispatcherIfRequired();
|
||||
@ -65,12 +65,10 @@ public:
|
||||
for (size_t i = 0; i < mTaskGroups.Length(); ++i) {
|
||||
UniquePtr<PerThreadTaskGroup> group(Move(mTaskGroups[i]));
|
||||
nsRefPtr<AbstractThread> thread = group->mThread;
|
||||
bool assertDispatchSuccess = group->mAssertDispatchSuccess;
|
||||
|
||||
AbstractThread::DispatchFailureHandling failureHandling = group->mFailureHandling;
|
||||
nsCOMPtr<nsIRunnable> r = new TaskGroupRunnable(Move(group));
|
||||
nsresult rv = thread->Dispatch(r.forget());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!assertDispatchSuccess || NS_SUCCEEDED(rv));
|
||||
unused << assertDispatchSuccess;
|
||||
unused << rv;
|
||||
thread->Dispatch(r.forget(), failureHandling);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,14 +80,16 @@ public:
|
||||
|
||||
void AddTask(AbstractThread* aThread,
|
||||
already_AddRefed<nsIRunnable> aRunnable,
|
||||
bool aAssertDispatchSuccess) override
|
||||
AbstractThread::DispatchFailureHandling aFailureHandling) override
|
||||
{
|
||||
PerThreadTaskGroup& group = EnsureTaskGroup(aThread);
|
||||
group.mRegularTasks.AppendElement(aRunnable);
|
||||
|
||||
// The task group needs to assert dispatch success if any of the runnables
|
||||
// it's dispatching want to assert it.
|
||||
group.mAssertDispatchSuccess = group.mAssertDispatchSuccess || aAssertDispatchSuccess;
|
||||
if (aFailureHandling == AbstractThread::AssertDispatchSuccess) {
|
||||
group.mFailureHandling = AbstractThread::AssertDispatchSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@ -98,7 +98,7 @@ private:
|
||||
{
|
||||
public:
|
||||
explicit PerThreadTaskGroup(AbstractThread* aThread)
|
||||
: mThread(aThread), mAssertDispatchSuccess(false)
|
||||
: mThread(aThread), mFailureHandling(AbstractThread::DontAssertDispatchSuccess)
|
||||
{
|
||||
MOZ_COUNT_CTOR(PerThreadTaskGroup);
|
||||
}
|
||||
@ -108,7 +108,7 @@ private:
|
||||
nsRefPtr<AbstractThread> mThread;
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mStateChangeTasks;
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mRegularTasks;
|
||||
bool mAssertDispatchSuccess;
|
||||
AbstractThread::DispatchFailureHandling mFailureHandling;
|
||||
};
|
||||
|
||||
class TaskGroupRunnable : public nsRunnable
|
||||
|
@ -24,10 +24,10 @@
|
||||
#include "AudioNodeEngine.h"
|
||||
#include "AudioNodeStream.h"
|
||||
#include "AudioNodeExternalInputStream.h"
|
||||
#include "webaudio/MediaStreamAudioDestinationNode.h"
|
||||
#include <algorithm>
|
||||
#include "DOMMediaStream.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "mozilla/unused.h"
|
||||
#ifdef MOZ_WEBRTC
|
||||
#include "AudioOutputObserver.h"
|
||||
#endif
|
||||
@ -275,12 +275,16 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) :
|
||||
} else if (InMutedCycle()) {
|
||||
segment->AppendNullData(ticks);
|
||||
} 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) {
|
||||
|
@ -564,12 +564,7 @@ TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
|
||||
NS_NewRunnableMethodWithArg<SourceBufferDecoder*>(this,
|
||||
&TrackBuffer::InitializeDecoder,
|
||||
aDecoder);
|
||||
if (NS_FAILED(mTaskQueue->Dispatch(task))) {
|
||||
MSE_DEBUG("failed to enqueue decoder initialization task");
|
||||
RemoveDecoder(aDecoder);
|
||||
mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
|
||||
return false;
|
||||
}
|
||||
mTaskQueue->Dispatch(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
# strictContentSandbox - bug 1042735, Android 2.3 - bug 981881
|
||||
# won't run on b2g desktop tests - bug 1119993
|
||||
# broken HTTPS on b2g emulator - bug 1135339
|
||||
skip-if = (os == 'win' && strictContentSandbox) || android_version == '10' || android_version == '18' || (buildapp == 'b2g' && toolkit != 'gonk') || (buildapp == 'b2g' && toolkit == 'gonk')
|
||||
skip-if = (os == 'win' && strictContentSandbox) || android_version == '10' || android_version == '18' || (buildapp == 'b2g' && toolkit != 'gonk') || (buildapp == 'b2g' && toolkit == 'gonk') || buildapp == 'mulet'
|
||||
support-files =
|
||||
/.well-known/idp-proxy/idp.js
|
||||
identityPcTest.js
|
||||
|
@ -1,6 +1,6 @@
|
||||
[DEFAULT]
|
||||
# strictContentSandbox - bug 1042735, Android 2.3 - bug 981881
|
||||
skip-if = (os == 'win' && strictContentSandbox) || android_version == '10' || android_version == '18'
|
||||
skip-if = (os == 'win' && strictContentSandbox) || android_version == '10' || android_version == '18' || (buildapp == 'mulet')
|
||||
support-files =
|
||||
head.js
|
||||
dataChannel.js
|
||||
@ -14,71 +14,71 @@ support-files =
|
||||
turnConfig.js
|
||||
|
||||
[test_dataChannel_basicAudio.html]
|
||||
skip-if = toolkit == 'gonk' # Bug 962984 for debug, bug 963244 for opt
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # Bug 962984 for debug, bug 963244 for opt
|
||||
[test_dataChannel_basicAudioVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_dataChannel_basicAudioVideoNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_dataChannel_basicAudioVideoCombined.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_dataChannel_basicDataOnly.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_dataChannel_basicVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_dataChannel_bug1013809.html]
|
||||
skip-if = toolkit == 'gonk' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
|
||||
[test_dataChannel_noOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_getUserMedia_basicAudio.html]
|
||||
skip-if = (toolkit == 'gonk' && debug) # debug-only failure
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
|
||||
[test_getUserMedia_basicVideo.html]
|
||||
skip-if = (toolkit == 'gonk' && debug) # debug-only failure
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
|
||||
[test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
|
||||
skip-if = (toolkit == 'gonk' && debug) # debug-only failure
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
|
||||
[test_getUserMedia_basicScreenshare.html]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
|
||||
[test_getUserMedia_basicWindowshare.html]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no windowshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
|
||||
[test_getUserMedia_basicVideoAudio.html]
|
||||
skip-if = (toolkit == 'gonk' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
|
||||
[test_getUserMedia_constraints.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_callbacks.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' || buildapp == 'mulet' # Bug 1063290, intermittent timeout # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' || buildapp == 'mulet' # Bug 1063290, intermittent timeout # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables.
|
||||
[test_getUserMedia_gumWithinGum.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_playAudioTwice.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_playVideoAudioTwice.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure
|
||||
[test_getUserMedia_playVideoTwice.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_stopAudioStream.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_stopVideoAudioStream.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure
|
||||
[test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_stopVideoStream.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
|
||||
skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' || toolkit == 'android' # Bug 1063290, intermittent timeout
|
||||
[test_getUserMedia_peerIdentity.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 1021776, too --ing slow on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 1021776, too --ing slow on b2g)
|
||||
[test_peerConnection_addCandidateInHaveLocalOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_basicAudio.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_basicAudioVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_basicAudioVideoCombined.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_basicAudioVideoNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_basicVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_basicScreenshare.html]
|
||||
# no screenshare on b2g/android
|
||||
# frequent timeouts/crashes on e10s (bug 1048455)
|
||||
@ -90,104 +90,104 @@ skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android' || e1
|
||||
[test_peerConnection_basicH264Video.html]
|
||||
skip-if = buildapp == 'b2g' || buildapp == 'mulet' || os == 'android' # bug 1043403 # Bug 1141029 Mulet parity with B2G Desktop for TC
|
||||
[test_peerConnection_bug822674.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_bug825703.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_bug827843.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_bug834153.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_bug1013809.html]
|
||||
skip-if = toolkit == 'gonk' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
|
||||
[test_peerConnection_bug1042791.html]
|
||||
skip-if = buildapp == 'b2g' || buildapp == 'mulet' || os == 'android' # bug 1043403 # Bug 1141029 Mulet parity with B2G Desktop for TC
|
||||
[test_peerConnection_capturedVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_close.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_errorCallbacks.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_forwarding_basicAudioVideoCombined.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_noTrickleAnswer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_noTrickleOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_noTrickleOfferAnswer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_offerRequiresReceiveAudio.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_offerRequiresReceiveVideo.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_offerRequiresReceiveVideoAudio.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_promiseSendOnly.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_callbacks.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_replaceTrack.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_syncSetDescription.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_setLocalAnswerInStable.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_setLocalOfferInHaveRemoteOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_setRemoteAnswerInStable.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_throwInCallbacks.html]
|
||||
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
|
||||
[test_peerConnection_toJSON.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
|
||||
[test_peerConnection_twoAudioStreams.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoAudioTracksInOneStream.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoAudioVideoStreams.html]
|
||||
skip-if = (toolkit == 'gonk') # b2g (Bug 1059867)
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet') # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoAudioVideoStreamsCombined.html]
|
||||
skip-if = (toolkit == 'gonk') # b2g (Bug 1059867)
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet') # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoVideoStreams.html]
|
||||
skip-if = (toolkit == 'gonk') # b2g (Bug 1059867)
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet') # b2g (Bug 1059867)
|
||||
[test_peerConnection_twoVideoTracksInOneStream.html]
|
||||
skip-if = (toolkit == 'gonk') # b2g (Bug 1059867)
|
||||
skip-if = (toolkit == 'gonk' || buildapp == 'mulet') # b2g (Bug 1059867)
|
||||
[test_peerConnection_addSecondAudioStream.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_answererAddSecondAudioStream.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeAudioTrack.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeThenAddAudioTrack.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_addSecondVideoStream.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeVideoTrack.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeThenAddVideoTrack.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_replaceVideoThenRenegotiate.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_addSecondAudioStreamNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeThenAddAudioTrackNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_addSecondVideoStreamNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_removeThenAddVideoTrackNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_addDataChannel.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_addDataChannelNoBundle.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
[test_peerConnection_webAudio.html]
|
||||
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
|
||||
skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g (Bug 1059867)
|
||||
|
||||
# Bug 950317: Hack for making a cleanup hook after finishing all WebRTC cases
|
||||
[test_zmedia_cleanup.html]
|
||||
|
@ -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<AudioBufferSourceNode>
|
||||
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<AudioBufferSourceNode>
|
||||
AudioContext::CreateBufferSource(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<AudioBufferSourceNode> bufferNode =
|
||||
new AudioBufferSourceNode(this);
|
||||
return bufferNode.forget();
|
||||
@ -247,6 +267,10 @@ AudioContext::CreateMediaStreamDestination(ErrorResult& aRv)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaStreamAudioDestinationNode> node =
|
||||
new MediaStreamAudioDestinationNode(this);
|
||||
return node.forget();
|
||||
@ -266,6 +290,10 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<ScriptProcessorNode> scriptProcessor =
|
||||
new ScriptProcessorNode(this, aBufferSize, aNumberOfInputChannels,
|
||||
aNumberOfOutputChannels);
|
||||
@ -273,15 +301,23 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize,
|
||||
}
|
||||
|
||||
already_AddRefed<AnalyserNode>
|
||||
AudioContext::CreateAnalyser()
|
||||
AudioContext::CreateAnalyser(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<AnalyserNode> analyserNode = new AnalyserNode(this);
|
||||
return analyserNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<StereoPannerNode>
|
||||
AudioContext::CreateStereoPanner()
|
||||
AudioContext::CreateStereoPanner(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<StereoPannerNode> stereoPannerNode = new StereoPannerNode(this);
|
||||
return stereoPannerNode.forget();
|
||||
}
|
||||
@ -300,6 +336,11 @@ AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement,
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<DOMMediaStream> stream = aMediaElement.MozCaptureStream(aRv,
|
||||
mDestination->Stream()->Graph());
|
||||
if (aRv.Failed()) {
|
||||
@ -318,21 +359,34 @@ AudioContext::CreateMediaStreamSource(DOMMediaStream& aMediaStream,
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaStreamAudioSourceNode> mediaStreamAudioSourceNode =
|
||||
new MediaStreamAudioSourceNode(this, &aMediaStream);
|
||||
return mediaStreamAudioSourceNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<GainNode>
|
||||
AudioContext::CreateGain()
|
||||
AudioContext::CreateGain(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<GainNode> gainNode = new GainNode(this);
|
||||
return gainNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<WaveShaperNode>
|
||||
AudioContext::CreateWaveShaper()
|
||||
AudioContext::CreateWaveShaper(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<WaveShaperNode> waveShaperNode = new WaveShaperNode(this);
|
||||
return waveShaperNode.forget();
|
||||
}
|
||||
@ -340,25 +394,38 @@ AudioContext::CreateWaveShaper()
|
||||
already_AddRefed<DelayNode>
|
||||
AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aMaxDelayTime > 0. && aMaxDelayTime < 180.) {
|
||||
nsRefPtr<DelayNode> delayNode = new DelayNode(this, aMaxDelayTime);
|
||||
return delayNode.forget();
|
||||
}
|
||||
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<PannerNode>
|
||||
AudioContext::CreatePanner()
|
||||
AudioContext::CreatePanner(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<PannerNode> pannerNode = new PannerNode(this);
|
||||
mPannerNodes.PutEntry(pannerNode);
|
||||
return pannerNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<ConvolverNode>
|
||||
AudioContext::CreateConvolver()
|
||||
AudioContext::CreateConvolver(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<ConvolverNode> convolverNode = new ConvolverNode(this);
|
||||
return convolverNode.forget();
|
||||
}
|
||||
@ -372,6 +439,10 @@ AudioContext::CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<ChannelSplitterNode> splitterNode =
|
||||
new ChannelSplitterNode(this, aNumberOfOutputs);
|
||||
return splitterNode.forget();
|
||||
@ -386,30 +457,46 @@ AudioContext::CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<ChannelMergerNode> mergerNode =
|
||||
new ChannelMergerNode(this, aNumberOfInputs);
|
||||
return mergerNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<DynamicsCompressorNode>
|
||||
AudioContext::CreateDynamicsCompressor()
|
||||
AudioContext::CreateDynamicsCompressor(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<DynamicsCompressorNode> compressorNode =
|
||||
new DynamicsCompressorNode(this);
|
||||
return compressorNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<BiquadFilterNode>
|
||||
AudioContext::CreateBiquadFilter()
|
||||
AudioContext::CreateBiquadFilter(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<BiquadFilterNode> filterNode =
|
||||
new BiquadFilterNode(this);
|
||||
return filterNode.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<OscillatorNode>
|
||||
AudioContext::CreateOscillator()
|
||||
AudioContext::CreateOscillator(ErrorResult& aRv)
|
||||
{
|
||||
if (CheckClosed(aRv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<OscillatorNode> oscillatorNode =
|
||||
new OscillatorNode(this);
|
||||
return oscillatorNode.forget();
|
||||
@ -597,22 +684,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<nsPIDOMWindow> 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<DOMEventTargetHelper*>(mAudioContext),
|
||||
NS_LITERAL_STRING("statechange"),
|
||||
false, false);
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<AudioContext> 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<Promise*>(aPromise);
|
||||
promise->MaybeResolve(JS::UndefinedHandleValue);
|
||||
DebugOnly<bool> rv = mPromiseGripArray.RemoveElement(promise);
|
||||
MOZ_ASSERT(rv, "Promise wasn't in the grip array?");
|
||||
}
|
||||
|
||||
if (mAudioContextState != aNewState) {
|
||||
nsRefPtr<OnStateChangeTask> onStateChangeTask =
|
||||
new OnStateChangeTask(this);
|
||||
NS_DispatchToMainThread(onStateChangeTask);
|
||||
}
|
||||
|
||||
mAudioContextState = aNewState;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
AudioContext::Suspend(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
|
||||
nsRefPtr<Promise> 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<Promise>
|
||||
AudioContext::Resume(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
|
||||
nsRefPtr<Promise> 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<Promise>
|
||||
AudioContext::Close(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
|
||||
nsRefPtr<Promise> 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
|
||||
@ -653,6 +957,9 @@ AudioContext::StartRendering(ErrorResult& aRv)
|
||||
mIsStarted = true;
|
||||
nsRefPtr<Promise> promise = Promise::Create(parentObject, aRv);
|
||||
mDestination->StartRendering(promise);
|
||||
|
||||
OnStateChanged(nullptr, AudioContextState::Running);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
|
@ -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<AudioContext> mAudioContext;
|
||||
void* mPromise;
|
||||
nsRefPtr<AudioNodeStream> 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<JSObject*> aGivenProto) override;
|
||||
|
||||
@ -124,11 +151,31 @@ public:
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
AudioContextId Id() const
|
||||
{
|
||||
return mId;
|
||||
}
|
||||
|
||||
double CurrentTime() const;
|
||||
|
||||
AudioListener* Listener();
|
||||
|
||||
already_AddRefed<AudioBufferSourceNode> 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<Promise> Suspend(ErrorResult& aRv);
|
||||
already_AddRefed<Promise> Resume(ErrorResult& aRv);
|
||||
already_AddRefed<Promise> Close(ErrorResult& aRv);
|
||||
IMPL_EVENT_HANDLER(statechange)
|
||||
|
||||
already_AddRefed<AudioBufferSourceNode> CreateBufferSource(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<AudioBuffer>
|
||||
CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
|
||||
@ -145,16 +192,16 @@ public:
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<StereoPannerNode>
|
||||
CreateStereoPanner();
|
||||
CreateStereoPanner(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<AnalyserNode>
|
||||
CreateAnalyser();
|
||||
CreateAnalyser(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<GainNode>
|
||||
CreateGain();
|
||||
CreateGain(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<WaveShaperNode>
|
||||
CreateWaveShaper();
|
||||
CreateWaveShaper(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<MediaElementAudioSourceNode>
|
||||
CreateMediaElementSource(HTMLMediaElement& aMediaElement, ErrorResult& aRv);
|
||||
@ -165,10 +212,10 @@ public:
|
||||
CreateDelay(double aMaxDelayTime, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<PannerNode>
|
||||
CreatePanner();
|
||||
CreatePanner(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<ConvolverNode>
|
||||
CreateConvolver();
|
||||
CreateConvolver(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<ChannelSplitterNode>
|
||||
CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv);
|
||||
@ -177,13 +224,13 @@ public:
|
||||
CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<DynamicsCompressorNode>
|
||||
CreateDynamicsCompressor();
|
||||
CreateDynamicsCompressor(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<BiquadFilterNode>
|
||||
CreateBiquadFilter();
|
||||
CreateBiquadFilter(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<OscillatorNode>
|
||||
CreateOscillator();
|
||||
CreateOscillator(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<PeriodicWave>
|
||||
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<AudioDestinationNode> mDestination;
|
||||
nsRefPtr<AudioListener> mListener;
|
||||
nsTArray<nsRefPtr<WebAudioDecodeJob> > 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<nsRefPtr<Promise>> mPromiseGripArray;
|
||||
// See RegisterActiveNode. These will keep the AudioContext alive while it
|
||||
// is rendering and the window remains alive.
|
||||
nsTHashtable<nsRefPtrHashKey<AudioNode> > 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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<OnCompleteTask> task =
|
||||
nsRefPtr<OnCompleteTask> 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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,7 @@ public:
|
||||
NS_ERROR("MediaStreamAudioSourceNodeEngine bad parameter index");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool mEnabled;
|
||||
};
|
||||
|
@ -34,6 +34,7 @@ EXPORTS += [
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'FFTBlock.h',
|
||||
'MediaStreamAudioDestinationNode.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
|
@ -45,6 +45,8 @@ skip-if = (toolkit == 'gonk') || (toolkit == 'android') || debug #bug 906752
|
||||
[test_audioBufferSourceNodePassThrough.html]
|
||||
[test_audioBufferSourceNodeRate.html]
|
||||
[test_AudioContext.html]
|
||||
skip-if = android_version == '10' # bug 1138462
|
||||
[test_audioContextSuspendResumeClose.html]
|
||||
[test_audioDestinationNode.html]
|
||||
[test_AudioListener.html]
|
||||
[test_audioParamExponentialRamp.html]
|
||||
|
400
dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
Normal file
400
dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
Normal file
@ -0,0 +1,400 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test suspend, resume and close method of the AudioContext</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.requestCompleteLog();
|
||||
|
||||
function tryToToCreateNodeOnClosedContext(ctx) {
|
||||
ok(ctx.state, "closed", "The context is in closed state");
|
||||
|
||||
[ { name: "createBufferSource" },
|
||||
{ name: "createMediaStreamDestination",
|
||||
onOfflineAudioContext: false},
|
||||
{ name: "createScriptProcessor" },
|
||||
{ name: "createStereoPanner" },
|
||||
{ name: "createAnalyser" },
|
||||
{ name: "createGain" },
|
||||
{ name: "createDelay" },
|
||||
{ name: "createBiquadFilter" },
|
||||
{ name: "createWaveShaper" },
|
||||
{ name: "createPanner" },
|
||||
{ name: "createConvolver" },
|
||||
{ name: "createChannelSplitter" },
|
||||
{ name: "createChannelMerger" },
|
||||
{ name: "createDynamicsCompressor" },
|
||||
{ name: "createOscillator" },
|
||||
{ name: "createMediaElementSource",
|
||||
args: [new Audio()],
|
||||
onOfflineAudioContext: false },
|
||||
{ name: "createMediaStreamSource",
|
||||
args: [new Audio().mozCaptureStream()],
|
||||
onOfflineAudioContext: false } ].forEach(function(e) {
|
||||
|
||||
if (e.onOfflineAudioContext == false &&
|
||||
ctx instanceof OfflineAudioContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
expectException(function() {
|
||||
ctx[e.name].apply(ctx, e.args);
|
||||
}, DOMException.INVALID_STATE_ERR);
|
||||
});
|
||||
}
|
||||
|
||||
function loadFile(url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function() {
|
||||
callback(xhr.response);
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// createBuffer, createPeriodicWave and decodeAudioData should work on a context
|
||||
// that has `state` == "closed"
|
||||
function tryLegalOpeerationsOnClosedContext(ctx) {
|
||||
ok(ctx.state, "closed", "The context is in closed state");
|
||||
|
||||
[ { name: "createBuffer",
|
||||
args: [1, 44100, 44100] },
|
||||
{ name: "createPeriodicWave",
|
||||
args: [new Float32Array(10), new Float32Array(10)] }
|
||||
].forEach(function(e) {
|
||||
expectNoException(function() {
|
||||
ctx[e.name].apply(ctx, e.args);
|
||||
});
|
||||
});
|
||||
loadFile("ting-44.1k-1ch.ogg", function(buf) {
|
||||
ctx.decodeAudioData(buf).then(function(decodedBuf) {
|
||||
ok(true, "decodeAudioData on a closed context should work, it did.")
|
||||
todo(false, "0 " + (ctx instanceof OfflineAudioContext ? "Offline" : "Realtime"));
|
||||
finish();
|
||||
}).catch(function(e){
|
||||
ok(false, "decodeAudioData on a closed context should work, it did not");
|
||||
finish();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Test that MediaStreams that are the output of a suspended AudioContext are
|
||||
// producing silence
|
||||
// ac1 produce a sine fed to a MediaStreamAudioDestinationNode
|
||||
// ac2 is connected to ac1 with a MediaStreamAudioSourceNode, and check that
|
||||
// there is silence when ac1 is suspended
|
||||
function testMultiContextOutput() {
|
||||
var ac1 = new AudioContext(),
|
||||
ac2 = new AudioContext();
|
||||
|
||||
var osc1 = ac1.createOscillator(),
|
||||
mediaStreamDestination1 = ac1.createMediaStreamDestination();
|
||||
|
||||
var mediaStreamAudioSourceNode2 =
|
||||
ac2.createMediaStreamSource(mediaStreamDestination1.stream),
|
||||
sp2 = ac2.createScriptProcessor(),
|
||||
suspendCalled = false,
|
||||
silentBuffersInARow = 0;
|
||||
|
||||
|
||||
sp2.onaudioprocess = function(e) {
|
||||
if (!suspendCalled) {
|
||||
ac1.suspend();
|
||||
suspendCalled = true;
|
||||
} else {
|
||||
// Wait until the context that produce the tone is actually suspended. It
|
||||
// can be that the second context receives a little amount of data because
|
||||
// of the buffering between the two contexts.
|
||||
if (ac1.state == "suspended") {
|
||||
var input = e.inputBuffer.getChannelData(0);
|
||||
var silent = true;
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
if (input[i] != 0.0) {
|
||||
silent = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
silentBuffersInARow++;
|
||||
if (silentBuffersInARow == 10) {
|
||||
ok(true,
|
||||
"MediaStreams produce silence when their input is blocked.");
|
||||
sp2.onaudioprocess = null;
|
||||
ac1.close();
|
||||
ac2.close();
|
||||
todo(false,"1");
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
is(silentBuffersInARow, 0,
|
||||
"No non silent buffer inbetween silent buffers.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osc1.connect(mediaStreamDestination1);
|
||||
|
||||
mediaStreamAudioSourceNode2.connect(sp2);
|
||||
osc1.start();
|
||||
}
|
||||
|
||||
|
||||
// Test that there is no buffering between contexts when connecting a running
|
||||
// AudioContext to a suspended AudioContext. Our ScriptProcessorNode does some
|
||||
// buffering internally, so we ensure this by using a very very low frequency
|
||||
// on a sine, and oberve that the phase has changed by a big enough margin.
|
||||
function testMultiContextInput() {
|
||||
var ac1 = new AudioContext(),
|
||||
ac2 = new AudioContext();
|
||||
|
||||
var osc1 = ac1.createOscillator(),
|
||||
mediaStreamDestination1 = ac1.createMediaStreamDestination(),
|
||||
sp1 = ac1.createScriptProcessor();
|
||||
|
||||
var mediaStreamAudioSourceNode2 =
|
||||
ac2.createMediaStreamSource(mediaStreamDestination1.stream),
|
||||
sp2 = ac2.createScriptProcessor(),
|
||||
resumed = false,
|
||||
suspended = false,
|
||||
countEventOnFirstSP = true,
|
||||
eventReceived = 0;
|
||||
|
||||
|
||||
osc1.frequency.value = 0.0001;
|
||||
|
||||
// We keep a first ScriptProcessor to get a periodic callback, since we can't
|
||||
// use setTimeout anymore.
|
||||
sp1.onaudioprocess = function(e) {
|
||||
if (countEventOnFirstSP) {
|
||||
eventReceived++;
|
||||
}
|
||||
if (eventReceived > 3 && suspended) {
|
||||
countEventOnFirstSP = false;
|
||||
eventReceived = 0;
|
||||
ac2.resume().then(function() {
|
||||
resumed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sp2.onaudioprocess = function(e) {
|
||||
var inputBuffer = e.inputBuffer.getChannelData(0);
|
||||
if (!resumed) {
|
||||
// save the last value of the buffer before suspending.
|
||||
sp2.value = inputBuffer[inputBuffer.length - 1];
|
||||
ac2.suspend().then(function() {
|
||||
suspended = true;
|
||||
});
|
||||
} else {
|
||||
eventReceived++;
|
||||
if (eventReceived == 3) {
|
||||
var delta = Math.abs(inputBuffer[1] - sp2.value),
|
||||
theoreticalIncrement = 2048 * 3 * Math.PI * 2 * osc1.frequency.value / ac1.sampleRate;
|
||||
ok(delta >= theoreticalIncrement,
|
||||
"Buffering did not occur when the context was suspended (delta:" + delta + " increment: " + theoreticalIncrement+")");
|
||||
ac1.close();
|
||||
ac2.close();
|
||||
sp1.onaudioprocess = null;
|
||||
sp2.onaudioprocess = null;
|
||||
todo(false, "2");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
osc1.connect(mediaStreamDestination1);
|
||||
osc1.connect(sp1);
|
||||
|
||||
mediaStreamAudioSourceNode2.connect(sp2);
|
||||
osc1.start();
|
||||
}
|
||||
|
||||
// Test that ScriptProcessorNode's onaudioprocess don't get called while the
|
||||
// context is suspended/closed. It is possible that we get the handler called
|
||||
// exactly once after suspend, because the event has already been sent to the
|
||||
// event loop.
|
||||
function testScriptProcessNodeSuspended() {
|
||||
var ac = new AudioContext();
|
||||
var sp = ac.createScriptProcessor();
|
||||
var remainingIterations = 30;
|
||||
var afterResume = false;
|
||||
sp.onaudioprocess = function() {
|
||||
ok(ac.state == "running" || remainingIterations == 3, "If onaudioprocess is called, the context" +
|
||||
" must be running (was " + ac.state + ", remainingIterations:" + remainingIterations +")");
|
||||
remainingIterations--;
|
||||
if (!afterResume) {
|
||||
if (remainingIterations == 0) {
|
||||
ac.suspend().then(function() {
|
||||
ac.resume().then(function() {
|
||||
remainingIterations = 30;
|
||||
afterResume = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
sp.onaudioprocess = null;
|
||||
todo(false,"3");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
sp.connect(ac.destination);
|
||||
}
|
||||
|
||||
// Take an AudioContext, make sure it switches to running when the audio starts
|
||||
// flowing, and then, call suspend, resume and close on it, tracking its state.
|
||||
function testAudioContext() {
|
||||
var ac = new AudioContext();
|
||||
is(ac.state, "suspended", "AudioContext should start in suspended state.");
|
||||
var stateTracker = {
|
||||
previous: ac.state,
|
||||
// no promise for the initial suspended -> running
|
||||
initial: { handler: false },
|
||||
suspend: { promise: false, handler: false },
|
||||
resume: { promise: false, handler: false },
|
||||
close: { promise: false, handler: false }
|
||||
};
|
||||
|
||||
function initialSuspendToRunning() {
|
||||
ok(stateTracker.previous == "suspended" &&
|
||||
ac.state == "running",
|
||||
"AudioContext should switch to \"running\" when the audio hardware is" +
|
||||
" ready.");
|
||||
|
||||
stateTracker.previous = ac.state;
|
||||
ac.onstatechange = afterSuspend;
|
||||
stateTracker.initial.handler = true;
|
||||
|
||||
ac.suspend().then(function() {
|
||||
ok(!stateTracker.suspend.promise && !stateTracker.suspend.handler,
|
||||
"Promise should be resolved before the callback, and only once.")
|
||||
stateTracker.suspend.promise = true;
|
||||
});
|
||||
}
|
||||
|
||||
function afterSuspend() {
|
||||
ok(stateTracker.previous == "running" &&
|
||||
ac.state == "suspended",
|
||||
"AudioContext should switch to \"suspend\" when the audio stream is" +
|
||||
"suspended.");
|
||||
ok(stateTracker.suspend.promise && !stateTracker.suspend.handler,
|
||||
"Handler should be called after the callback, and only once");
|
||||
|
||||
stateTracker.suspend.handler = true;
|
||||
stateTracker.previous = ac.state;
|
||||
ac.onstatechange = afterResume;
|
||||
|
||||
ac.resume().then(function() {
|
||||
ok(!stateTracker.resume.promise && !stateTracker.resume.handler,
|
||||
"Promise should be called before the callback, and only once");
|
||||
stateTracker.resume.promise = true;
|
||||
});
|
||||
}
|
||||
|
||||
function afterResume() {
|
||||
ok(stateTracker.previous == "suspended" &&
|
||||
ac.state == "running",
|
||||
"AudioContext should switch to \"running\" when the audio stream resumes.");
|
||||
|
||||
ok(stateTracker.resume.promise && !stateTracker.resume.handler,
|
||||
"Handler should be called after the callback, and only once");
|
||||
|
||||
stateTracker.resume.handler = true;
|
||||
stateTracker.previous = ac.state;
|
||||
ac.onstatechange = afterClose;
|
||||
|
||||
ac.close().then(function() {
|
||||
ok(!stateTracker.close.promise && !stateTracker.close.handler,
|
||||
"Promise should be called before the callback, and only once");
|
||||
stateTracker.close.promise = true;
|
||||
tryToToCreateNodeOnClosedContext(ac);
|
||||
tryLegalOpeerationsOnClosedContext(ac);
|
||||
});
|
||||
}
|
||||
|
||||
function afterClose() {
|
||||
ok(stateTracker.previous == "running" &&
|
||||
ac.state == "closed",
|
||||
"AudioContext should switch to \"closed\" when the audio stream is" +
|
||||
" closed.");
|
||||
ok(stateTracker.close.promise && !stateTracker.close.handler,
|
||||
"Handler should be called after the callback, and only once");
|
||||
}
|
||||
|
||||
ac.onstatechange = initialSuspendToRunning;
|
||||
}
|
||||
|
||||
function testOfflineAudioContext() {
|
||||
var o = new OfflineAudioContext(1, 44100, 44100);
|
||||
is(o.state, "suspended", "OfflineAudioContext should start in suspended state.");
|
||||
|
||||
expectRejectedPromise(o, "suspend", "NotSupportedError");
|
||||
expectRejectedPromise(o, "resume", "NotSupportedError");
|
||||
expectRejectedPromise(o, "close", "NotSupportedError");
|
||||
|
||||
var previousState = o.state,
|
||||
finishedRendering = false;
|
||||
function beforeStartRendering() {
|
||||
ok(previousState == "suspended" && o.state == "running", "onstatechanged" +
|
||||
"handler is called on state changed, and the new state is running");
|
||||
previousState = o.state;
|
||||
o.onstatechange = onRenderingFinished;
|
||||
}
|
||||
|
||||
function onRenderingFinished() {
|
||||
ok(previousState == "running" && o.state == "closed",
|
||||
"onstatechanged handler is called when rendering finishes, " +
|
||||
"and the new state is closed");
|
||||
ok(finishedRendering, "The Promise that is resolved when the rendering is" +
|
||||
"done should be resolved earlier than the state change.");
|
||||
previousState = o.state;
|
||||
o.onstatechange = afterRenderingFinished;
|
||||
|
||||
tryToToCreateNodeOnClosedContext(o);
|
||||
tryLegalOpeerationsOnClosedContext(o);
|
||||
}
|
||||
|
||||
function afterRenderingFinished() {
|
||||
ok(false, "There should be no transition out of the closed state.");
|
||||
}
|
||||
|
||||
o.onstatechange = beforeStartRendering;
|
||||
|
||||
o.startRendering().then(function(buffer) {
|
||||
finishedRendering = true;
|
||||
});
|
||||
}
|
||||
|
||||
var remaining = 0;
|
||||
function finish() {
|
||||
remaining--;
|
||||
if (remaining == 0) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
var tests = [
|
||||
testAudioContext,
|
||||
testOfflineAudioContext,
|
||||
testScriptProcessNodeSuspended,
|
||||
testMultiContextOutput,
|
||||
testMultiContextInput
|
||||
];
|
||||
remaining = tests.length;
|
||||
tests.forEach(function(f) { f() });
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -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;
|
||||
}
|
||||
|
@ -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<void> suspend();
|
||||
[Throws]
|
||||
Promise<void> resume();
|
||||
[Throws]
|
||||
Promise<void> 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);
|
||||
|
@ -12,15 +12,18 @@
|
||||
|
||||
interface AudioListener {
|
||||
|
||||
// same as OpenAL (default 1)
|
||||
// same as OpenAL (default 1)
|
||||
[Deprecated="PannerNodeDoppler"]
|
||||
attribute double dopplerFactor;
|
||||
|
||||
// in meters / second (default 343.3)
|
||||
// in meters / second (default 343.3)
|
||||
[Deprecated="PannerNodeDoppler"]
|
||||
attribute double speedOfSound;
|
||||
|
||||
// Uses a 3D cartesian coordinate system
|
||||
// Uses a 3D cartesian coordinate system
|
||||
void setPosition(double x, double y, double z);
|
||||
void setOrientation(double x, double y, double z, double xUp, double yUp, double zUp);
|
||||
[Deprecated="PannerNodeDoppler"]
|
||||
void setVelocity(double x, double y, double z);
|
||||
|
||||
};
|
||||
|
@ -29,6 +29,7 @@ interface PannerNode : AudioNode {
|
||||
// Uses a 3D cartesian coordinate system
|
||||
void setPosition(double x, double y, double z);
|
||||
void setOrientation(double x, double y, double z);
|
||||
[Deprecated="PannerNodeDoppler"]
|
||||
void setVelocity(double x, double y, double z);
|
||||
|
||||
// Distance model and attributes
|
||||
|
@ -2,12 +2,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_FILES = \
|
||||
bootstrap.js \
|
||||
install.rdf \
|
||||
worker.js \
|
||||
$(NULL)
|
||||
|
||||
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
|
||||
|
||||
GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
|
||||
|
@ -5,3 +5,9 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPI_NAME = 'workerbootstrap'
|
||||
|
||||
DIST_FILES += [
|
||||
'bootstrap.js',
|
||||
'install.rdf',
|
||||
'worker.js',
|
||||
]
|
||||
|
@ -2,11 +2,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_FILES = \
|
||||
install.rdf \
|
||||
worker.js \
|
||||
$(NULL)
|
||||
|
||||
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
|
||||
|
||||
GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
|
||||
|
@ -16,3 +16,8 @@ EXTRA_COMPONENTS += [
|
||||
]
|
||||
|
||||
XPI_NAME = 'worker'
|
||||
|
||||
DIST_FILES += [
|
||||
'install.rdf',
|
||||
'worker.js',
|
||||
]
|
||||
|
@ -213,6 +213,21 @@ struct EventRegions {
|
||||
mDispatchToContentHitRegion.Sub(aMinuend.mDispatchToContentHitRegion, aSubtrahend);
|
||||
}
|
||||
|
||||
void ApplyTranslationAndScale(float aXTrans, float aYTrans, float aXScale, float aYScale)
|
||||
{
|
||||
mHitRegion.ScaleRoundOut(aXScale, aYScale);
|
||||
mDispatchToContentHitRegion.ScaleRoundOut(aXScale, aYScale);
|
||||
mNoActionRegion.ScaleRoundOut(aXScale, aYScale);
|
||||
mHorizontalPanRegion.ScaleRoundOut(aXScale, aYScale);
|
||||
mVerticalPanRegion.ScaleRoundOut(aXScale, aYScale);
|
||||
|
||||
mHitRegion.MoveBy(aXTrans, aYTrans);
|
||||
mDispatchToContentHitRegion.MoveBy(aXTrans, aYTrans);
|
||||
mNoActionRegion.MoveBy(aXTrans, aYTrans);
|
||||
mHorizontalPanRegion.MoveBy(aXTrans, aYTrans);
|
||||
mVerticalPanRegion.MoveBy(aXTrans, aYTrans);
|
||||
}
|
||||
|
||||
void Transform(const gfx3DMatrix& aTransform)
|
||||
{
|
||||
mHitRegion.Transform(aTransform);
|
||||
|
@ -565,6 +565,11 @@ uint64_t nsRegion::Area () const
|
||||
|
||||
nsRegion& nsRegion::ScaleRoundOut (float aXScale, float aYScale)
|
||||
{
|
||||
if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) &&
|
||||
mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
int n;
|
||||
pixman_box32_t *boxes = pixman_region32_rectangles(&mImpl, &n);
|
||||
for (int i=0; i<n; i++) {
|
||||
|
@ -2315,12 +2315,6 @@ gfxPlatform::UsesOffMainThreadCompositing()
|
||||
// Linux users who chose OpenGL are being grandfathered in to OMTC
|
||||
result |= gfxPrefs::LayersAccelerationForceEnabled();
|
||||
|
||||
#if !defined(NIGHTLY_BUILD)
|
||||
// Yeah, these two env vars do the same thing.
|
||||
// I'm told that one of them is enabled on some test slaves config,
|
||||
// so be slightly careful if you think you can remove one of them.
|
||||
result &= PR_GetEnv("MOZ_USE_OMTC") || PR_GetEnv("MOZ_OMTC_ENABLED");
|
||||
#endif
|
||||
#endif
|
||||
firstTime = false;
|
||||
}
|
||||
|
@ -123,9 +123,9 @@ function ArrayEvery(callbackfn/*, thisArg*/) {
|
||||
|
||||
/* Step 4. */
|
||||
if (arguments.length === 0)
|
||||
ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.every');
|
||||
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.prototype.every');
|
||||
if (!IsCallable(callbackfn))
|
||||
ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
|
||||
/* Step 5. */
|
||||
var T = arguments.length > 1 ? arguments[1] : void 0;
|
||||
|
@ -539,10 +539,10 @@ function CanonicalizeLocaleList(locales) {
|
||||
if (kPresent) {
|
||||
var kValue = O[k];
|
||||
if (!(typeof kValue === "string" || IsObject(kValue)))
|
||||
ThrowError(JSMSG_INVALID_LOCALES_ELEMENT);
|
||||
ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT);
|
||||
var tag = ToString(kValue);
|
||||
if (!IsStructurallyValidLanguageTag(tag))
|
||||
ThrowError(JSMSG_INVALID_LANGUAGE_TAG, tag);
|
||||
ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag);
|
||||
tag = CanonicalizeLanguageTag(tag);
|
||||
if (seen.indexOf(tag) === -1)
|
||||
seen.push(tag);
|
||||
|
@ -105,9 +105,9 @@ function TypedArrayEvery(callbackfn, thisArg = undefined) {
|
||||
|
||||
// Step 6.
|
||||
if (arguments.length === 0)
|
||||
ThrowError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.every");
|
||||
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.every");
|
||||
if (!IsCallable(callbackfn))
|
||||
ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
|
||||
// Step 7.
|
||||
var T = thisArg;
|
||||
@ -186,9 +186,9 @@ function TypedArrayFilter(callbackfn, thisArg = undefined) {
|
||||
|
||||
// Step 5.
|
||||
if (arguments.length === 0)
|
||||
ThrowError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter");
|
||||
ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "%TypedArray%.prototype.filter");
|
||||
if (!IsCallable(callbackfn))
|
||||
ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
|
||||
|
||||
// Step 6.
|
||||
var T = thisArg;
|
||||
|
@ -4807,39 +4807,11 @@ Parser<FullParseHandler>::forStatement()
|
||||
if (isForDecl) {
|
||||
pn2 = pn1->pn_head;
|
||||
if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) {
|
||||
/*
|
||||
* Declaration with initializer.
|
||||
*
|
||||
* Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or
|
||||
* 'const' to hoist the initializer or the entire decl out of
|
||||
* the loop head.
|
||||
*/
|
||||
if (headKind == PNK_FOROF) {
|
||||
report(ParseError, false, pn2, JSMSG_INVALID_FOR_OF_INIT);
|
||||
return null();
|
||||
}
|
||||
if (blockObj) {
|
||||
report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT);
|
||||
return null();
|
||||
}
|
||||
|
||||
hoistedVar = pn1;
|
||||
|
||||
/*
|
||||
* All of 'var x = i' is hoisted above 'for (x in o)'.
|
||||
*
|
||||
* Request JSOP_POP here since the var is for a simple
|
||||
* name (it is not a destructuring binding's left-hand
|
||||
* side) and it has an initializer.
|
||||
*/
|
||||
pn1->pn_xflags |= PNX_POPVAR;
|
||||
pn1 = nullptr;
|
||||
|
||||
if (pn2->isKind(PNK_ASSIGN)) {
|
||||
pn2 = pn2->pn_left;
|
||||
MOZ_ASSERT(pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT) ||
|
||||
pn2->isKind(PNK_NAME));
|
||||
}
|
||||
// We have a bizarre |for (var/const/let x = ... in/of ...)|
|
||||
// loop erroneously permitted by ES1-5 but removed in ES6.
|
||||
report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT,
|
||||
headKind == PNK_FOROF ? "of" : "in");
|
||||
return null();
|
||||
}
|
||||
} else {
|
||||
/* Not a declaration. */
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Binary: cache/js-dbg-32-f561f17e6c27-linux
|
||||
// Flags:
|
||||
//
|
||||
Reflect.parse("for (var x = 3 in []) { }")
|
||||
Reflect.parse("for (var x in []) { }")
|
||||
|
@ -3,4 +3,10 @@
|
||||
//
|
||||
load(libdir + 'asserts.js');
|
||||
// value is not iterable
|
||||
assertThrowsInstanceOf(function(){for(var[x]=x>>x in[[]<[]]){[]}}, TypeError);
|
||||
(function() {
|
||||
for (var [x] in [[] < []])
|
||||
{
|
||||
// Just a useless expression.
|
||||
[];
|
||||
}
|
||||
})();
|
||||
|
@ -1,17 +0,0 @@
|
||||
// |jit-test| error: TypeError
|
||||
|
||||
(eval("\
|
||||
(function () {\
|
||||
for (var[x] = function(){} in \
|
||||
(function m(a) {\
|
||||
if (a < 1) {\
|
||||
x;\
|
||||
return\
|
||||
}\
|
||||
return m(a - 1) + m(a - 2)\
|
||||
})(7)\
|
||||
(eval(\"\"))\
|
||||
)\
|
||||
([])\
|
||||
})\
|
||||
"))()
|
@ -1,3 +0,0 @@
|
||||
(function() {
|
||||
for (var [e] = [] in (eval("for (b = 0; b < 6; ++b) gc()"))) {}
|
||||
})()
|
@ -3,7 +3,7 @@ function test() {
|
||||
function f()
|
||||
k.apply(this, arguments);
|
||||
if (undefined >> undefined !== 0) {}
|
||||
for (var [ v , c ] = 0 in this.tracemonkey) { }
|
||||
for (var [ v , c ] in this.tracemonkey) { }
|
||||
}
|
||||
try { test(); } catch(exc1) {}
|
||||
try { test(); } catch(exc1) {}
|
||||
|
@ -1,8 +0,0 @@
|
||||
// |jit-test| error: TypeError
|
||||
(function () {
|
||||
var b = e
|
||||
for (var [e] = b in w) c
|
||||
})()
|
||||
|
||||
/* Don't assert. */
|
||||
|
@ -1,10 +0,0 @@
|
||||
|
||||
function test() {
|
||||
try {
|
||||
for (var i = 0 in this) throw p;
|
||||
} catch (e) {
|
||||
if (i !== 94)
|
||||
return "what";
|
||||
}
|
||||
}
|
||||
test();
|
@ -253,8 +253,7 @@ MSG_DEF(JSMSG_GARBAGE_AFTER_INPUT, 2, JSEXN_SYNTAXERR, "unexpected garbage a
|
||||
MSG_DEF(JSMSG_IDSTART_AFTER_NUMBER, 0, JSEXN_SYNTAXERR, "identifier starts immediately after numeric literal")
|
||||
MSG_DEF(JSMSG_ILLEGAL_CHARACTER, 0, JSEXN_SYNTAXERR, "illegal character")
|
||||
MSG_DEF(JSMSG_IMPORT_DECL_AT_TOP_LEVEL, 0, JSEXN_SYNTAXERR, "import declarations may only appear at top level")
|
||||
MSG_DEF(JSMSG_INVALID_FOR_IN_INIT, 0, JSEXN_SYNTAXERR, "for-in loop let declaration may not have an initializer")
|
||||
MSG_DEF(JSMSG_INVALID_FOR_OF_INIT, 0, JSEXN_SYNTAXERR, "for-of loop variable declaration may not have an initializer")
|
||||
MSG_DEF(JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT,1,JSEXN_SYNTAXERR,"for-{0} loop head declarations may not have initializers")
|
||||
MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for")
|
||||
MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found")
|
||||
MSG_DEF(JSMSG_LET_CLASS_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class")
|
||||
@ -456,8 +455,8 @@ MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 0, JSEXN_ERR, "Type is too large to alloc
|
||||
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_OBJECT, 0, JSEXN_TYPEERR, "invalid object argument")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX, 0, JSEXN_ERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_ERR, "argument {0} must be >= 0")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
|
||||
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
|
||||
|
||||
// Shared array buffer
|
||||
|
@ -824,6 +824,8 @@ bool intrinsic_ToInteger(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_ToString(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_IsCallable(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_ThrowError(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp);
|
||||
bool intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
35
js/src/tests/ecma_6/TypedArray/set-negative-offset.js
Normal file
35
js/src/tests/ecma_6/TypedArray/set-negative-offset.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
var gTestfile = "set-negative-offset.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
var BUGNUMBER = 1140752;
|
||||
var summary =
|
||||
"%TypedArray%.prototype.set must throw a RangeError when passed a negative " +
|
||||
"offset";
|
||||
|
||||
print(BUGNUMBER + ": " + summary);
|
||||
|
||||
/**************
|
||||
* BEGIN TEST *
|
||||
**************/
|
||||
|
||||
try
|
||||
{
|
||||
new Uint8Array().set([], -1);
|
||||
throw new Error("didn't throw at all");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
assertEq(e instanceof RangeError, true,
|
||||
"expected RangeError, instead got: " + e);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
||||
print("Tests complete");
|
@ -19,8 +19,8 @@ var dodis;
|
||||
function f1(o){for(var x in o)printStatus(o[x]); return x}
|
||||
function f2(o){with(this)for(var x in o)printStatus(o[x]); return x}
|
||||
function f2novar(o){with(this)for(x in o)printStatus(o[x]); return x}
|
||||
function f3(i,o){for(var x=i in o)printStatus(o[x]); return x}
|
||||
function f4(i,o){with(this)for(var x=i in o)printStatus(o[x]); return x}
|
||||
function f3(i,o){for(var x in o)printStatus(o[x]); return x}
|
||||
function f4(i,o){with(this)for(var x in o)printStatus(o[x]); return x}
|
||||
|
||||
var t=0;
|
||||
function assert(c)
|
||||
@ -38,28 +38,31 @@ function assert(c)
|
||||
reportCompare(expect, actual, summary);
|
||||
}
|
||||
|
||||
assert(f1([]) == undefined);
|
||||
assertEq(f1([]), undefined);
|
||||
|
||||
assert(f1(['first']) == 0);
|
||||
assertEq(f1(['first']), "0");
|
||||
|
||||
assert(f2([]) == undefined);
|
||||
assertEq(f2([]), undefined);
|
||||
|
||||
assert(f2(['first']) == 0);
|
||||
assertEq(f2(['first']), "0");
|
||||
|
||||
assert(f3(42, []) == 42);
|
||||
assertEq(f3(42, []), undefined);
|
||||
|
||||
assert(f3(42, ['first']) == 0);
|
||||
assertEq(f3(42, ['first']), "0");
|
||||
|
||||
assert(f4(42, []) == 42);
|
||||
assertEq(f4(42, []), undefined);
|
||||
|
||||
assert(f4(42, ['first']) == 0);
|
||||
assertEq(f4(42, ['first']), "0");
|
||||
|
||||
this.x = 41;
|
||||
|
||||
assert(f2([]) == undefined);
|
||||
assertEq(f2([]), undefined);
|
||||
|
||||
assert(f2novar([]) == 41);
|
||||
assertEq(f2novar([]), 41);
|
||||
|
||||
assert(f2(['first']) == undefined);
|
||||
assertEq(f2(['first']), undefined);
|
||||
|
||||
assert(f2novar(['first']) == 0);
|
||||
assertEq(f2novar(['first']), "0");
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
||||
|
@ -15,7 +15,7 @@ printStatus (summary);
|
||||
|
||||
function SetLangHead(l){
|
||||
with(p){
|
||||
for(var i=0 in x)
|
||||
for(var i in x)
|
||||
if(getElementById("TxtH"+i)!=undefined)
|
||||
printStatus('huh');
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ function test()
|
||||
|
||||
try
|
||||
{
|
||||
for (var {a: []} = 2 in []) { }
|
||||
for (var {a: []} in []) { }
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
|
@ -22,7 +22,7 @@ function test()
|
||||
|
||||
try
|
||||
{
|
||||
for (var [,{y}] = 1 in []) {}
|
||||
for (var [,{y}] in []) {}
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ function test()
|
||||
// Crash in NoteLValue, called from BindDestructuringVar.
|
||||
// NoteLValue assumes pn->pn_lexdef is non-null, but here
|
||||
// pn is itself the definition of x.
|
||||
for (var [x]=[] in null) ;
|
||||
for (var [x] in null) ;
|
||||
|
||||
// This one only crashes when executed from a file.
|
||||
// Assertion failure: pn != dn->dn_uses, at ../jsparse.cpp:1131
|
||||
|
@ -52,7 +52,7 @@ function test()
|
||||
//
|
||||
try
|
||||
{
|
||||
for (var [x] = x in y) var x;
|
||||
for (var [x] in y) var x;
|
||||
}
|
||||
catch(ex)
|
||||
{
|
||||
|
@ -75,7 +75,7 @@ function test()
|
||||
|
||||
// Assertion failure: pnu->pn_lexdef == dn, at ../jsemit.cpp:1817
|
||||
// =====
|
||||
uneval(function(){for(var [arguments] = ({ get y(){} }) in y ) (x);});
|
||||
uneval(function(){arguments = ({ get y(){} }); for(var [arguments] in y ) (x);});
|
||||
|
||||
// Assertion failure: n != 0, at ../jsfun.cpp:2689
|
||||
// =====
|
||||
|
@ -35,7 +35,7 @@ function test()
|
||||
|
||||
// Assertion failure: !(pn->pn_dflags & flag), at ../jsparse.h:651
|
||||
// =====
|
||||
(function(){for(var x = arguments in []){} function x(){}})();
|
||||
(function(){for(var x in [arguments]){} function x(){}})();
|
||||
|
||||
// Assertion failure: dn->pn_defn, at ../jsemit.cpp:1873
|
||||
// =====
|
||||
|
@ -36,22 +36,6 @@ assertError("for each (const x in foo);", SyntaxError);
|
||||
assertError("for each (const {a:x,b:y,c:z} in foo);", SyntaxError);
|
||||
assertError("for each (const [x,y,z] in foo);", SyntaxError);
|
||||
|
||||
// destructuring in for-in and for-each-in loop heads with initializers
|
||||
|
||||
assertStmt("for (var {a:x,b:y,c:z} = 22 in foo);", forInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt));
|
||||
assertStmt("for (var [x,y,z] = 22 in foo);", forInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt));
|
||||
assertStmt("for each (var {a:x,b:y,c:z} = 22 in foo);", forEachInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt));
|
||||
assertStmt("for each (var [x,y,z] = 22 in foo);", forEachInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt));
|
||||
assertError("for (x = 22 in foo);", SyntaxError);
|
||||
assertError("for ({a:x,b:y,c:z} = 22 in foo);", SyntaxError);
|
||||
assertError("for ([x,y,z] = 22 in foo);", SyntaxError);
|
||||
assertError("for (const x = 22 in foo);", SyntaxError);
|
||||
assertError("for (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError);
|
||||
assertError("for (const [x,y,z] = 22 in foo);", SyntaxError);
|
||||
assertError("for each (const x = 22 in foo);", SyntaxError);
|
||||
assertError("for each (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError);
|
||||
assertError("for each (const [x,y,z] = 22 in foo);", SyntaxError);
|
||||
|
||||
}
|
||||
|
||||
runtest(test);
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
if (typeof evalcx == 'function') {
|
||||
var src = 'try {\n' +
|
||||
' for (var [e] = /x/ in d) {\n' +
|
||||
' for (var [e] in d) {\n' +
|
||||
' (function () {});\n' +
|
||||
' }\n' +
|
||||
'} catch (e) {}\n' +
|
||||
|
@ -2,7 +2,7 @@
|
||||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
with (0)
|
||||
for (var b = 0 in 0) // don't assert in parser
|
||||
for (var b in 0) // don't assert in parser
|
||||
;
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
||||
|
@ -42,6 +42,7 @@ using namespace js;
|
||||
using namespace js::selfhosted;
|
||||
|
||||
using JS::AutoCheckCannotGC;
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
static void
|
||||
selfHosting_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
|
||||
@ -150,16 +151,15 @@ intrinsic_OwnPropertyKeys(JSContext* cx, unsigned argc, Value* vp)
|
||||
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_ThrowError(JSContext* cx, unsigned argc, Value* vp)
|
||||
static void
|
||||
ThrowErrorWithType(JSContext* cx, JSExnType type, const CallArgs& args)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() >= 1);
|
||||
uint32_t errorNumber = args[0].toInt32();
|
||||
|
||||
#ifdef DEBUG
|
||||
const JSErrorFormatString* efs = GetErrorMessage(nullptr, errorNumber);
|
||||
MOZ_ASSERT(efs->argCount == args.length() - 1);
|
||||
MOZ_ASSERT(efs->exnType == type, "error-throwing intrinsic and error number are inconsistent");
|
||||
#endif
|
||||
|
||||
JSAutoByteString errorArgs[3];
|
||||
@ -168,19 +168,53 @@ js::intrinsic_ThrowError(JSContext* cx, unsigned argc, Value* vp)
|
||||
if (val.isInt32()) {
|
||||
JSString* str = ToString<CanGC>(cx, val);
|
||||
if (!str)
|
||||
return false;
|
||||
return;
|
||||
errorArgs[i - 1].encodeLatin1(cx, str);
|
||||
} else if (val.isString()) {
|
||||
errorArgs[i - 1].encodeLatin1(cx, val.toString());
|
||||
} else {
|
||||
errorArgs[i - 1].initBytes(DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr()).release());
|
||||
UniquePtr<char[], JS::FreePolicy> bytes =
|
||||
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr());
|
||||
if (!bytes)
|
||||
return;
|
||||
errorArgs[i - 1].initBytes(bytes.release());
|
||||
}
|
||||
if (!errorArgs[i - 1])
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber,
|
||||
errorArgs[0].ptr(), errorArgs[1].ptr(), errorArgs[2].ptr());
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_ThrowError(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() >= 1);
|
||||
|
||||
uint32_t errorNumber = args[0].toInt32();
|
||||
ThrowErrorWithType(cx, JSExnType(GetErrorMessage(nullptr, errorNumber)->exnType), args);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() >= 1);
|
||||
|
||||
ThrowErrorWithType(cx, JSEXN_RANGEERR, args);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() >= 1);
|
||||
|
||||
ThrowErrorWithType(cx, JSEXN_TYPEERR, args);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -951,6 +985,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
|
||||
JS_FN("IsConstructor", intrinsic_IsConstructor, 1,0),
|
||||
JS_FN("OwnPropertyKeys", intrinsic_OwnPropertyKeys, 1,0),
|
||||
JS_FN("ThrowError", intrinsic_ThrowError, 4,0),
|
||||
JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4,0),
|
||||
JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4,0),
|
||||
JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0),
|
||||
JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2,0),
|
||||
JS_FN("_IsConstructing", intrinsic_IsConstructing, 0,0),
|
||||
|
@ -686,8 +686,7 @@ class TypedArrayMethods
|
||||
|
||||
if (offset < 0 || uint32_t(offset) > target->length()) {
|
||||
// the given offset is bogus
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TYPED_ARRAY_BAD_INDEX, "2");
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -29,11 +29,11 @@ namespace js {
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
|
||||
*/
|
||||
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 276;
|
||||
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 277;
|
||||
static const uint32_t XDR_BYTECODE_VERSION =
|
||||
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
|
||||
|
||||
static_assert(JSErr_Limit == 395,
|
||||
static_assert(JSErr_Limit == 394,
|
||||
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
|
||||
"removed MSG_DEFs from js.msg, you should increment "
|
||||
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
|
||||
|
@ -3034,12 +3034,9 @@ void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueB
|
||||
ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion));
|
||||
regions.mHitRegion.OrWith(maybeHitRegion);
|
||||
|
||||
nsIntPoint translation = -GetTranslationForPaintedLayer(data->mLayer);
|
||||
regions.mHitRegion.MoveBy(translation);
|
||||
regions.mDispatchToContentHitRegion.MoveBy(translation);
|
||||
regions.mNoActionRegion.MoveBy(translation);
|
||||
regions.mHorizontalPanRegion.MoveBy(translation);
|
||||
regions.mVerticalPanRegion.MoveBy(translation);
|
||||
Matrix mat = layer->GetBaseTransform().As2D();
|
||||
mat.Invert();
|
||||
regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22);
|
||||
|
||||
layer->SetEventRegions(regions);
|
||||
}
|
||||
|
@ -142,12 +142,11 @@ nsContainerFrame::RemoveFrame(ChildListID aListID,
|
||||
nsIPresShell* shell = PresContext()->PresShell();
|
||||
nsContainerFrame* lastParent = nullptr;
|
||||
while (aOldFrame) {
|
||||
//XXXfr probably should use StealFrame here. I'm not sure if we need to
|
||||
// check the overflow lists atm, but we'll need a prescontext lookup
|
||||
// for overflow containers once we can split abspos elements with
|
||||
// inline containing blocks.
|
||||
nsIFrame* oldFrameNextContinuation = aOldFrame->GetNextContinuation();
|
||||
nsContainerFrame* parent = aOldFrame->GetParent();
|
||||
// Please note that 'parent' may not actually be where 'aOldFrame' lives.
|
||||
// We really MUST use StealFrame() and nothing else here.
|
||||
// @see nsInlineFrame::StealFrame for details.
|
||||
parent->StealFrame(aOldFrame, true);
|
||||
aOldFrame->Destroy();
|
||||
aOldFrame = oldFrameNextContinuation;
|
||||
@ -1621,7 +1620,6 @@ nsContainerFrame::DrainSelfOverflowList()
|
||||
{
|
||||
AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
|
||||
if (overflowFrames) {
|
||||
NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
|
||||
mFrames.AppendFrames(nullptr, *overflowFrames);
|
||||
return true;
|
||||
}
|
||||
|
@ -204,6 +204,45 @@ nsInlineFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
||||
nsContainerFrame::DestroyFrom(aDestructRoot);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsInlineFrame::StealFrame(nsIFrame* aChild,
|
||||
bool aForceNormal)
|
||||
{
|
||||
if (aChild->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
|
||||
!aForceNormal) {
|
||||
return nsContainerFrame::StealFrame(aChild, aForceNormal);
|
||||
}
|
||||
|
||||
nsInlineFrame* parent = this;
|
||||
bool removed = false;
|
||||
do {
|
||||
removed = parent->mFrames.StartRemoveFrame(aChild);
|
||||
if (removed) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We didn't find the child in our principal child list.
|
||||
// Maybe it's on the overflow list?
|
||||
nsFrameList* frameList = parent->GetOverflowFrames();
|
||||
if (frameList) {
|
||||
removed = frameList->ContinueRemoveFrame(aChild);
|
||||
if (frameList->IsEmpty()) {
|
||||
parent->DestroyOverflowList();
|
||||
}
|
||||
if (removed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Due to our "lazy reparenting" optimization 'aChild' might not actually
|
||||
// be on any of our child lists, but instead in one of our next-in-flows.
|
||||
parent = static_cast<nsInlineFrame*>(parent->GetNextInFlow());
|
||||
} while (parent);
|
||||
|
||||
MOZ_ASSERT(removed, "nsInlineFrame::StealFrame: can't find aChild");
|
||||
return removed ? NS_OK : NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
void
|
||||
nsInlineFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsRect& aDirtyRect,
|
||||
@ -469,7 +508,6 @@ nsInlineFrame::DrainSelfOverflowListInternal(DrainFlags aFlags,
|
||||
{
|
||||
AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
|
||||
if (overflowFrames) {
|
||||
NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
|
||||
// The frames on our own overflowlist may have been pushed by a
|
||||
// previous lazilySetParentPointer Reflow so we need to ensure the
|
||||
// correct parent pointer. This is sometimes skipped by Reflow.
|
||||
@ -1161,8 +1199,6 @@ nsFirstLineFrame::DrainSelfOverflowList()
|
||||
{
|
||||
AutoFrameListPtr overflowFrames(PresContext(), StealOverflowFrames());
|
||||
if (overflowFrames) {
|
||||
NS_ASSERTION(mFrames.NotEmpty(), "overflow list w/o frames");
|
||||
|
||||
bool result = !overflowFrames->IsEmpty();
|
||||
const nsFrameList::Slice& newFrames =
|
||||
mFrames.AppendFrames(nullptr, *overflowFrames);
|
||||
|
@ -64,6 +64,7 @@ public:
|
||||
bool aRespectClusters = true) override;
|
||||
|
||||
virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
|
||||
virtual nsresult StealFrame(nsIFrame* aChild, bool aForceNormal) override;
|
||||
|
||||
// nsIHTMLReflow overrides
|
||||
virtual void AddInlineMinISize(nsRenderingContext *aRenderingContext,
|
||||
|
@ -3,12 +3,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_FILES = install.rdf
|
||||
|
||||
ifeq ($(MOZ_BUILD_APP),mobile/android)
|
||||
DIST_FILES += bootstrap.js
|
||||
endif
|
||||
|
||||
# Used in install.rdf
|
||||
USE_EXTENSION_MANIFEST=1
|
||||
|
||||
|
@ -20,3 +20,8 @@ else:
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
XPI_NAME = 'reftest'
|
||||
|
||||
DIST_FILES += ['install.rdf']
|
||||
|
||||
if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
|
||||
DIST_FILES += ['bootstrap.js']
|
||||
|
@ -215,6 +215,8 @@ struct cubeb_stream
|
||||
|
||||
/* Main handle on the WASAPI stream. */
|
||||
IAudioClient * client;
|
||||
/* Interface pointer to the clock, to estimate latency. */
|
||||
IAudioClock * audio_clock;
|
||||
/* Interface pointer to use the event-driven interface. */
|
||||
IAudioRenderClient * render_client;
|
||||
/* Interface pointer to use the volume facilities. */
|
||||
@ -236,8 +238,16 @@ struct cubeb_stream
|
||||
HANDLE refill_event;
|
||||
/* Each cubeb_stream has its own thread. */
|
||||
HANDLE thread;
|
||||
/* We synthesize our clock from the callbacks. */
|
||||
LONG64 clock;
|
||||
/* We synthesize our clock from the callbacks. This in fractional frames, in
|
||||
* the stream samplerate. */
|
||||
double clock;
|
||||
UINT64 prev_position;
|
||||
/* This is the clock value last time we reset the stream. This is in
|
||||
* fractional frames, in the stream samplerate. */
|
||||
double base_clock;
|
||||
/* The latency in frames of the stream */
|
||||
UINT32 latency_frames;
|
||||
UINT64 device_frequency;
|
||||
owned_critical_section * stream_reset_lock;
|
||||
/* Maximum number of frames we can be requested in a callback. */
|
||||
uint32_t buffer_frame_count;
|
||||
@ -342,14 +352,28 @@ private:
|
||||
};
|
||||
|
||||
namespace {
|
||||
void clock_add(cubeb_stream * stm, LONG64 value)
|
||||
void clock_add(cubeb_stream * stm, double value)
|
||||
{
|
||||
InterlockedExchangeAdd64(&stm->clock, value);
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
stm->clock += value;
|
||||
}
|
||||
|
||||
LONG64 clock_get(cubeb_stream * stm)
|
||||
UINT64 clock_get(cubeb_stream * stm)
|
||||
{
|
||||
return InterlockedExchangeAdd64(&stm->clock, 0);
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
return UINT64(stm->clock);
|
||||
}
|
||||
|
||||
void latency_set(cubeb_stream * stm, UINT32 value)
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
stm->latency_frames = value;
|
||||
}
|
||||
|
||||
UINT32 latency_get(cubeb_stream * stm)
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
return stm->latency_frames;
|
||||
}
|
||||
|
||||
bool should_upmix(cubeb_stream * stream)
|
||||
@ -443,8 +467,6 @@ refill(cubeb_stream * stm, float * data, long frames_needed)
|
||||
|
||||
long out_frames = cubeb_resampler_fill(stm->resampler, dest, frames_needed);
|
||||
|
||||
clock_add(stm, roundf(frames_needed * stream_to_mix_samplerate_ratio(stm)));
|
||||
|
||||
/* XXX: Handle this error. */
|
||||
if (out_frames < 0) {
|
||||
XASSERT(false);
|
||||
@ -516,8 +538,10 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
/* We don't check if the drain is actually finished here, we just want to
|
||||
* shutdown. */
|
||||
if (stm->draining) {
|
||||
LOG("DRAINED");
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 1: { /* reconfigure */
|
||||
@ -526,6 +550,8 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
stm->base_clock = stm->clock;
|
||||
stm->latency_frames = 0;
|
||||
/* Reopen a stream and start it immediately. This will automatically pick the
|
||||
* new default device for this role. */
|
||||
int r = setup_wasapi_stream(stm);
|
||||
@ -551,6 +577,18 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
}
|
||||
XASSERT(padding <= stm->buffer_frame_count);
|
||||
|
||||
long available = stm->buffer_frame_count - padding;
|
||||
|
||||
clock_add(stm, available * stream_to_mix_samplerate_ratio(stm));
|
||||
|
||||
UINT64 position = 0;
|
||||
HRESULT hr = stm->audio_clock->GetPosition(&position, NULL);
|
||||
if (SUCCEEDED(hr)) {
|
||||
double playing_frame = stm->mix_params.rate * (double)position / stm->device_frequency;
|
||||
double last_written_frame = stm->clock - stm->base_clock;
|
||||
latency_set(stm, max(last_written_frame - playing_frame, 0));
|
||||
}
|
||||
|
||||
if (stm->draining) {
|
||||
if (padding == 0) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
@ -559,8 +597,6 @@ wasapi_stream_render_loop(LPVOID stream)
|
||||
continue;
|
||||
}
|
||||
|
||||
long available = stm->buffer_frame_count - padding;
|
||||
|
||||
if (available == 0) {
|
||||
continue;
|
||||
}
|
||||
@ -1045,6 +1081,19 @@ int setup_wasapi_stream(cubeb_stream * stm)
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
hr = stm->client->GetService(__uuidof(IAudioClock),
|
||||
(void **)&stm->audio_clock);
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get IAudioClock: %x.\n", hr);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
hr = stm->audio_clock->GetFrequency(&stm->device_frequency);
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get the device frequency from IAudioClock: %x.\n", hr);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
hr = stm->client->GetService(__uuidof(IAudioRenderClient),
|
||||
(void **)&stm->render_client);
|
||||
if (FAILED(hr)) {
|
||||
@ -1103,11 +1152,14 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
stm->stream_params = stream_params;
|
||||
stm->draining = false;
|
||||
stm->latency = latency;
|
||||
stm->clock = 0;
|
||||
stm->clock = 0.0;
|
||||
stm->base_clock = 0.0;
|
||||
stm->latency_frames = 0;
|
||||
|
||||
/* Null out WASAPI-specific state */
|
||||
stm->resampler = NULL;
|
||||
stm->client = NULL;
|
||||
stm->audio_clock = NULL;
|
||||
stm->render_client = NULL;
|
||||
stm->audio_stream_volume = NULL;
|
||||
stm->device_enumerator = NULL;
|
||||
@ -1165,6 +1217,9 @@ void close_wasapi_stream(cubeb_stream * stm)
|
||||
SafeRelease(stm->render_client);
|
||||
stm->render_client = NULL;
|
||||
|
||||
SafeRelease(stm->audio_clock);
|
||||
stm->audio_clock = NULL;
|
||||
|
||||
SafeRelease(stm->audio_stream_volume);
|
||||
stm->audio_stream_volume = NULL;
|
||||
|
||||
@ -1274,7 +1329,20 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
||||
{
|
||||
XASSERT(stm && position);
|
||||
|
||||
*position = clock_get(stm);
|
||||
UINT64 clock = clock_get(stm);
|
||||
UINT32 latency = latency_get(stm);
|
||||
|
||||
*position = clock >= latency ? clock - latency : 0;
|
||||
|
||||
/* This can happen if the clock does not increase, for example, because the
|
||||
* WASAPI endpoint buffer is full for now, and the latency naturally decreases
|
||||
* because more samples have been played by the mixer process.
|
||||
* We return the previous value to keep the clock monotonicaly increasing. */
|
||||
if (*position < stm->prev_position) {
|
||||
*position = stm->prev_position;
|
||||
}
|
||||
|
||||
stm->prev_position = *position;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
#ifndef __CUTILS_PROPERTIES_H
|
||||
#define __CUTILS_PROPERTIES_H
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -67,20 +67,15 @@ extern "C" {
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class nrappkitTimerCallback : public nsITimerCallback
|
||||
{
|
||||
public:
|
||||
// We're going to release ourself in the callback, so we need to be threadsafe
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
nrappkitTimerCallback(NR_async_cb cb, void *cb_arg,
|
||||
const char *function, int line)
|
||||
class nrappkitCallback {
|
||||
public:
|
||||
nrappkitCallback(NR_async_cb cb, void *cb_arg,
|
||||
const char *function, int line)
|
||||
: cb_(cb), cb_arg_(cb_arg), function_(function), line_(line) {
|
||||
}
|
||||
virtual ~nrappkitCallback() {}
|
||||
|
||||
private:
|
||||
virtual ~nrappkitTimerCallback() {}
|
||||
virtual void Cancel() = 0;
|
||||
|
||||
protected:
|
||||
/* additional members */
|
||||
@ -90,38 +85,113 @@ protected:
|
||||
int line_;
|
||||
};
|
||||
|
||||
class nrappkitTimerCallback : public nrappkitCallback,
|
||||
public nsITimerCallback {
|
||||
public:
|
||||
// We're going to release ourself in the callback, so we need to be threadsafe
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
nrappkitTimerCallback(NR_async_cb cb, void *cb_arg,
|
||||
const char *function, int line,
|
||||
nsITimer *timer)
|
||||
: nrappkitCallback(cb, cb_arg, function, line),
|
||||
timer_(timer) {}
|
||||
|
||||
virtual void Cancel() override {
|
||||
AddRef(); // Cancelling the timer causes the callback it holds to
|
||||
// be released. AddRef() keeps us alive.
|
||||
timer_->Cancel();
|
||||
timer_->Release();
|
||||
Release(); // Will cause deletion of this object.
|
||||
}
|
||||
|
||||
private:
|
||||
nsITimer* timer_;
|
||||
virtual ~nrappkitTimerCallback() {}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nrappkitTimerCallback, nsITimerCallback)
|
||||
|
||||
NS_IMETHODIMP nrappkitTimerCallback::Notify(nsITimer *timer) {
|
||||
r_log(LOG_GENERIC, LOG_DEBUG, "Timer callback fired (set in %s:%d)",
|
||||
function_.c_str(), line_);
|
||||
|
||||
MOZ_ASSERT(timer == timer_);
|
||||
cb_(0, 0, cb_arg_);
|
||||
|
||||
// Allow the timer to go away.
|
||||
timer->Release();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class nrappkitScheduledCallback : public nrappkitCallback {
|
||||
public:
|
||||
|
||||
nrappkitScheduledCallback(NR_async_cb cb, void *cb_arg,
|
||||
const char *function, int line)
|
||||
: nrappkitCallback(cb, cb_arg, function, line) {}
|
||||
|
||||
void Run() {
|
||||
if (cb_) {
|
||||
cb_(0, 0, cb_arg_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Cancel() override {
|
||||
cb_ = nullptr;
|
||||
}
|
||||
|
||||
~nrappkitScheduledCallback() {}
|
||||
};
|
||||
|
||||
} // close namespace
|
||||
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
// These timers must only be used from the STS thread.
|
||||
// This function is a helper that enforces that.
|
||||
static void CheckSTSThread() {
|
||||
static nsCOMPtr<nsIEventTarget> GetSTSThread() {
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIEventTarget> sts_thread;
|
||||
|
||||
sts_thread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
|
||||
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
return sts_thread;
|
||||
}
|
||||
|
||||
// These timers must only be used from the STS thread.
|
||||
// This function is a helper that enforces that.
|
||||
static void CheckSTSThread() {
|
||||
nsCOMPtr<nsIEventTarget> sts_thread = GetSTSThread();
|
||||
|
||||
ASSERT_ON_THREAD(sts_thread);
|
||||
}
|
||||
|
||||
int NR_async_timer_set(int timeout, NR_async_cb cb, void *arg, char *func,
|
||||
int l, void **handle) {
|
||||
static int nr_async_timer_set_zero(NR_async_cb cb, void *arg,
|
||||
char *func, int l,
|
||||
nrappkitCallback **handle) {
|
||||
nrappkitScheduledCallback* callback(new nrappkitScheduledCallback(
|
||||
cb, arg, func, l));
|
||||
|
||||
nsresult rv = GetSTSThread()->Dispatch(WrapRunnable(
|
||||
nsAutoPtr<nrappkitScheduledCallback>(callback),
|
||||
&nrappkitScheduledCallback::Run),
|
||||
NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv))
|
||||
return R_FAILED;
|
||||
|
||||
*handle = callback;
|
||||
|
||||
// On exit to this function, the only strong reference to callback is in
|
||||
// the Runnable. Because we are redispatching to the same thread,
|
||||
// this is always safe.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nr_async_timer_set_nonzero(int timeout, NR_async_cb cb, void *arg,
|
||||
char *func, int l,
|
||||
nrappkitCallback **handle) {
|
||||
nsresult rv;
|
||||
CheckSTSThread();
|
||||
|
||||
@ -130,8 +200,9 @@ int NR_async_timer_set(int timeout, NR_async_cb cb, void *arg, char *func,
|
||||
return(R_FAILED);
|
||||
}
|
||||
|
||||
rv = timer->InitWithCallback(new nrappkitTimerCallback(cb, arg, func, l),
|
||||
timeout, nsITimer::TYPE_ONE_SHOT);
|
||||
nrappkitTimerCallback* callback =
|
||||
new nrappkitTimerCallback(cb, arg, func, l, timer);
|
||||
rv = timer->InitWithCallback(callback, timeout, nsITimer::TYPE_ONE_SHOT);
|
||||
if (NS_FAILED(rv)) {
|
||||
return R_FAILED;
|
||||
}
|
||||
@ -139,11 +210,29 @@ int NR_async_timer_set(int timeout, NR_async_cb cb, void *arg, char *func,
|
||||
// We need an AddRef here to keep the timer alive, per the spec.
|
||||
timer->AddRef();
|
||||
|
||||
*handle = callback;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NR_async_timer_set(int timeout, NR_async_cb cb, void *arg,
|
||||
char *func, int l, void **handle) {
|
||||
CheckSTSThread();
|
||||
|
||||
nrappkitCallback *callback;
|
||||
int r;
|
||||
|
||||
if (!timeout) {
|
||||
r = nr_async_timer_set_zero(cb, arg, func, l, &callback);
|
||||
} else {
|
||||
r = nr_async_timer_set_nonzero(timeout, cb, arg, func, l, &callback);
|
||||
}
|
||||
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
if (handle)
|
||||
*handle = timer.get();
|
||||
// Bug 818806: if we have no handle to the timer, we have no way to avoid
|
||||
// it leaking (though not the callback object) if it never fires (or if
|
||||
// we exit before it fires).
|
||||
*handle = callback;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -163,11 +252,8 @@ int NR_async_timer_cancel(void *handle) {
|
||||
|
||||
CheckSTSThread();
|
||||
|
||||
nsITimer *timer = static_cast<nsITimer *>(handle);
|
||||
|
||||
timer->Cancel();
|
||||
// Allow the timer to go away.
|
||||
timer->Release();
|
||||
nrappkitCallback* callback = static_cast<nrappkitCallback *>(handle);
|
||||
callback->Cancel();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -44,10 +44,29 @@ class TimerTest : public ::testing::Test {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ArmCancelTimer(int timeout) {
|
||||
int ret;
|
||||
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnableRet(this, &TimerTest::ArmCancelTimer_w, timeout, &ret),
|
||||
NS_DISPATCH_SYNC);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ArmTimer_w(int timeout) {
|
||||
return NR_ASYNC_TIMER_SET(timeout, cb, this, &handle_);
|
||||
}
|
||||
|
||||
int ArmCancelTimer_w(int timeout) {
|
||||
int r;
|
||||
r = ArmTimer_w(timeout);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
return CancelTimer_w();
|
||||
}
|
||||
|
||||
int CancelTimer() {
|
||||
int ret;
|
||||
|
||||
@ -74,7 +93,7 @@ class TimerTest : public ::testing::Test {
|
||||
|
||||
int Schedule_w() {
|
||||
NR_ASYNC_SCHEDULE(cb, this);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -105,6 +124,12 @@ TEST_F(TimerTest, CancelTimer) {
|
||||
ASSERT_FALSE(fired_);
|
||||
}
|
||||
|
||||
TEST_F(TimerTest, CancelTimer0) {
|
||||
ArmCancelTimer(0);
|
||||
PR_Sleep(100);
|
||||
ASSERT_FALSE(fired_);
|
||||
}
|
||||
|
||||
TEST_F(TimerTest, ScheduleTest) {
|
||||
Schedule();
|
||||
ASSERT_TRUE_WAIT(fired_, 1000);
|
||||
|
@ -2,10 +2,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIST_FILES := \
|
||||
package-name.txt.in \
|
||||
$(NULL)
|
||||
|
||||
ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
|
||||
MIN_CPU_VERSION=7
|
||||
else
|
||||
|
@ -888,3 +888,5 @@ if CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
|
||||
if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
|
||||
# The Search Activity code is built as part of Fennec, so we follow suit in Eclipse.
|
||||
main.add_classpathentry('search', TOPSRCDIR + '/mobile/android/search/java', dstdir='search')
|
||||
|
||||
DIST_FILES += ['package-name.txt.in']
|
||||
|
@ -3015,11 +3015,8 @@ pref("intl.keyboard.per_window_layout", false);
|
||||
|
||||
#ifdef NS_ENABLE_TSF
|
||||
// Enable/Disable TSF support on Vista or later.
|
||||
#ifndef RELEASE_BUILD
|
||||
pref("intl.tsf.enable", true);
|
||||
#else
|
||||
pref("intl.tsf.enable", false);
|
||||
#endif
|
||||
|
||||
// Force enable TSF even on WinXP or WinServer 2003.
|
||||
// Be aware, TSF framework on prior to Vista is not enough stable.
|
||||
pref("intl.tsf.force_enable", false);
|
||||
@ -4016,7 +4013,7 @@ pref("layers.max-active", -1);
|
||||
pref("layers.tiles.adjust", true);
|
||||
|
||||
// Set the default values, and then override per-platform as needed
|
||||
pref("layers.offmainthreadcomposition.enabled", false);
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
// Compositor target frame rate. NOTE: If vsync is enabled the compositor
|
||||
// frame rate will still be capped.
|
||||
// -1 -> default (match layout.frame_rate or 60 FPS)
|
||||
@ -4028,25 +4025,11 @@ pref("layers.offmainthreadcomposition.frame-rate", -1);
|
||||
pref("layers.async-video.enabled", true);
|
||||
pref("layers.async-video-oop.enabled",true);
|
||||
|
||||
#ifdef XP_WIN
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_QT
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
pref("layers.enable-tiles", true);
|
||||
pref("layers.tiled-drawtarget.enabled", true);
|
||||
#endif
|
||||
|
||||
// ANDROID covers android and b2g
|
||||
#ifdef ANDROID
|
||||
pref("layers.offmainthreadcomposition.enabled", true);
|
||||
#endif
|
||||
|
||||
// same effect as layers.offmainthreadcomposition.enabled, but specifically for
|
||||
// use with tests.
|
||||
pref("layers.offmainthreadcomposition.testing.enabled", false);
|
||||
|
@ -32,6 +32,7 @@ from ..frontend.data import (
|
||||
ContextDerived,
|
||||
ContextWrapped,
|
||||
Defines,
|
||||
DistFiles,
|
||||
DirectoryTraversal,
|
||||
Exports,
|
||||
ExternalLibrary,
|
||||
@ -557,6 +558,15 @@ class RecursiveMakeBackend(CommonBackend):
|
||||
|
||||
elif isinstance(obj, FinalTargetFiles):
|
||||
self._process_final_target_files(obj, obj.files, obj.target)
|
||||
|
||||
elif isinstance(obj, DistFiles):
|
||||
# We'd like to install these via manifests as preprocessed files.
|
||||
# But they currently depend on non-standard flags being added via
|
||||
# some Makefiles, so for now we just pass them through to the
|
||||
# underlying Makefile.in.
|
||||
for f in obj.files:
|
||||
backend_file.write('DIST_FILES += %s\n' % f)
|
||||
|
||||
else:
|
||||
return
|
||||
obj.ack()
|
||||
|
@ -730,6 +730,12 @@ VARIABLES = {
|
||||
disabled.
|
||||
""", None),
|
||||
|
||||
'DIST_FILES': (StrictOrderingOnAppendList, list,
|
||||
"""Additional files to place in ``FINAL_TARGET`` (typically ``dist/bin``).
|
||||
|
||||
Unlike ``FINAL_TARGET_FILES``, these files are preprocessed.
|
||||
""", 'libs'),
|
||||
|
||||
'EXTRA_COMPONENTS': (StrictOrderingOnAppendList, list,
|
||||
"""Additional component files to distribute.
|
||||
|
||||
|
@ -853,6 +853,22 @@ class FinalTargetFiles(ContextDerived):
|
||||
self.target = target
|
||||
|
||||
|
||||
class DistFiles(ContextDerived):
|
||||
"""Sandbox container object for FINAL_TARGET_FILES, which is a
|
||||
HierarchicalStringList.
|
||||
|
||||
We need an object derived from ContextDerived for use in the backend, so
|
||||
this object fills that role. It just has a reference to the underlying
|
||||
HierarchicalStringList, which is created when parsing DIST_FILES.
|
||||
"""
|
||||
__slots__ = ('files', 'target')
|
||||
|
||||
def __init__(self, sandbox, files, target):
|
||||
ContextDerived.__init__(self, sandbox)
|
||||
self.files = files
|
||||
self.target = target
|
||||
|
||||
|
||||
class GeneratedFile(ContextDerived):
|
||||
"""Represents a generated file."""
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user