From 3d01f9c686fdc35c8caa8cdab0d04fb356f894f3 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Mon, 13 Apr 2015 22:08:47 -0700 Subject: [PATCH 01/43] Bug 1154782 - Detemplatize nsIThread wrapper and move it into AbstractThread.cpp. r=jww --- dom/media/AbstractThread.cpp | 37 ++++++++++++++++++++---------------- dom/media/AbstractThread.h | 11 ----------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/dom/media/AbstractThread.cpp b/dom/media/AbstractThread.cpp index cc9741586b5..2eb19ee0531 100644 --- a/dom/media/AbstractThread.cpp +++ b/dom/media/AbstractThread.cpp @@ -16,23 +16,28 @@ namespace mozilla { StaticRefPtr sMainThread; -template<> -nsresult -AbstractThreadImpl::Dispatch(already_AddRefed aRunnable) +class XPCOMThreadWrapper : public AbstractThread { - MediaTaskQueue::AssertInTailDispatchIfNeeded(); - nsCOMPtr r = aRunnable; - return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); -} +public: + explicit XPCOMThreadWrapper(nsIThread* aTarget) : mTarget(aTarget) {} -template<> -bool -AbstractThreadImpl::IsCurrentThreadIn() -{ - bool in = NS_GetCurrentThread() == mTarget; - MOZ_ASSERT_IF(in, MediaTaskQueue::GetCurrentQueue() == nullptr); - return in; -} + virtual nsresult Dispatch(already_AddRefed aRunnable) override + { + MediaTaskQueue::AssertInTailDispatchIfNeeded(); + nsCOMPtr r = aRunnable; + return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); + } + + virtual bool IsCurrentThreadIn() override + { + bool in = NS_GetCurrentThread() == mTarget; + MOZ_ASSERT_IF(in, MediaTaskQueue::GetCurrentQueue() == nullptr); + return in; + } + +private: + nsRefPtr mTarget; +}; void AbstractThread::MaybeTailDispatch(already_AddRefed aRunnable, @@ -64,7 +69,7 @@ AbstractThread::InitStatics() nsCOMPtr mainThread; NS_GetMainThread(getter_AddRefs(mainThread)); MOZ_DIAGNOSTIC_ASSERT(mainThread); - sMainThread = new AbstractThreadImpl(mainThread.get()); + sMainThread = new XPCOMThreadWrapper(mainThread.get()); ClearOnShutdown(&sMainThread); } diff --git a/dom/media/AbstractThread.h b/dom/media/AbstractThread.h index e1f7b5eeb5b..d512773d9c8 100644 --- a/dom/media/AbstractThread.h +++ b/dom/media/AbstractThread.h @@ -50,17 +50,6 @@ protected: virtual ~AbstractThread() {} }; -template -class AbstractThreadImpl : public AbstractThread -{ -public: - explicit AbstractThreadImpl(TargetType* aTarget) : mTarget(aTarget) {} - virtual nsresult Dispatch(already_AddRefed aRunnable); - virtual bool IsCurrentThreadIn(); -private: - nsRefPtr mTarget; -}; - } // namespace mozilla #endif From 3aa1882d759683f80435fd19c58e0fd803812e4a Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Tue, 14 Apr 2015 17:25:14 -0700 Subject: [PATCH 02/43] Bug 1154782 - Hoist dispatch failure handling into dispatch itself. r=jww This is nicer than scattering the failure handling to each callsite. Moreover, this is a necessary step on the road to automatic tail dispatch. --- dom/media/AbstractThread.cpp | 16 +++++++++------- dom/media/AbstractThread.h | 7 +++++-- dom/media/MediaPromise.h | 2 +- dom/media/MediaStreamGraph.cpp | 2 +- dom/media/MediaTaskQueue.h | 9 +++++++-- dom/media/TaskDispatcher.h | 20 ++++++++++---------- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/dom/media/AbstractThread.cpp b/dom/media/AbstractThread.cpp index 2eb19ee0531..e7a7e65973a 100644 --- a/dom/media/AbstractThread.cpp +++ b/dom/media/AbstractThread.cpp @@ -11,6 +11,7 @@ #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" +#include "mozilla/unused.h" namespace mozilla { @@ -21,11 +22,14 @@ class XPCOMThreadWrapper : public AbstractThread public: explicit XPCOMThreadWrapper(nsIThread* aTarget) : mTarget(aTarget) {} - virtual nsresult Dispatch(already_AddRefed aRunnable) override + virtual void Dispatch(already_AddRefed aRunnable, + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) override { MediaTaskQueue::AssertInTailDispatchIfNeeded(); nsCOMPtr r = aRunnable; - return mTarget->Dispatch(r, NS_DISPATCH_NORMAL); + nsresult rv = mTarget->Dispatch(r, NS_DISPATCH_NORMAL); + MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv)); + unused << rv; } virtual bool IsCurrentThreadIn() override @@ -41,15 +45,13 @@ private: void AbstractThread::MaybeTailDispatch(already_AddRefed 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); } } diff --git a/dom/media/AbstractThread.h b/dom/media/AbstractThread.h index d512773d9c8..a35388b3ff4 100644 --- a/dom/media/AbstractThread.h +++ b/dom/media/AbstractThread.h @@ -32,13 +32,16 @@ class AbstractThread { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractThread); - virtual nsresult Dispatch(already_AddRefed aRunnable) = 0; + + enum DispatchFailureHandling { AssertDispatchSuccess, DontAssertDispatchSuccess }; + virtual void Dispatch(already_AddRefed 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 aRunnable, - bool aAssertDispatchSuccess = true); + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess); // Convenience method for getting an AbstractThread for the main thread. static AbstractThread* MainThread(); diff --git a/dom/media/MediaPromise.h b/dom/media/MediaPromise.h index d437ecdd4f6..3794faad41f 100644 --- a/dom/media/MediaPromise.h +++ b/dom/media/MediaPromise.h @@ -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 diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 83614b3dc88..9804e92890b 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -282,7 +282,7 @@ MediaStreamGraphImpl::UpdateBufferSufficiencyState(SourceMediaStream* aStream) // win8 64 debug when invoked from noop_resampler::fill on the cubeb audio // thread. nsCOMPtr r = runnables[i].mRunnable; - runnables[i].mTarget->MaybeTailDispatch(r.forget(), /* aAssertDispatchSuccess = */ false); + runnables[i].mTarget->MaybeTailDispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess); } } diff --git a/dom/media/MediaTaskQueue.h b/dom/media/MediaTaskQueue.h index 81f118fed3e..9a4d2631f95 100644 --- a/dom/media/MediaTaskQueue.h +++ b/dom/media/MediaTaskQueue.h @@ -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" @@ -74,10 +75,14 @@ public: #endif // For AbstractThread. - nsresult Dispatch(already_AddRefed aRunnable) override + void Dispatch(already_AddRefed aRunnable, + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) override { RefPtr r(aRunnable); - return ForceDispatch(r); + MonitorAutoLock mon(mQueueMonitor); + nsresult rv = DispatchLocked(r, Forced); + MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv)); + unused << rv; } // This should only be used for things that absolutely can't afford to be diff --git a/dom/media/TaskDispatcher.h b/dom/media/TaskDispatcher.h index 2a7ae1c2ecf..56c114c8c26 100644 --- a/dom/media/TaskDispatcher.h +++ b/dom/media/TaskDispatcher.h @@ -43,7 +43,7 @@ public: already_AddRefed aRunnable) = 0; virtual void AddTask(AbstractThread* aThread, already_AddRefed 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 group(Move(mTaskGroups[i])); nsRefPtr thread = group->mThread; - bool assertDispatchSuccess = group->mAssertDispatchSuccess; + + AbstractThread::DispatchFailureHandling failureHandling = group->mFailureHandling; nsCOMPtr 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 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 mThread; nsTArray> mStateChangeTasks; nsTArray> mRegularTasks; - bool mAssertDispatchSuccess; + AbstractThread::DispatchFailureHandling mFailureHandling; }; class TaskGroupRunnable : public nsRunnable From 775574adac616ced592b713f010e29ea9ebe5bfa Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Tue, 14 Apr 2015 17:47:22 -0700 Subject: [PATCH 03/43] Bug 1154782 - Remove the concept of forced dispatch. r=jww I added this several months ago to make MediaPromise dispatch reliable in the presence of the god-awful Flush() feature of MediaTaskQueue. That feature has now been restricted to a special subclass (which we should eventually eliminate), so we can just assert that MediaPromises are never used on those task queues and simplify the rest of the code. --- dom/media/AbstractThread.h | 4 ++++ dom/media/MediaPromise.h | 2 ++ dom/media/MediaTaskQueue.cpp | 22 +++++----------------- dom/media/MediaTaskQueue.h | 21 +++++---------------- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/dom/media/AbstractThread.h b/dom/media/AbstractThread.h index a35388b3ff4..b5afd687445 100644 --- a/dom/media/AbstractThread.h +++ b/dom/media/AbstractThread.h @@ -43,6 +43,10 @@ public: void MaybeTailDispatch(already_AddRefed aRunnable, 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(); diff --git a/dom/media/MediaPromise.h b/dom/media/MediaPromise.h index 3794faad41f..275e149e75b 100644 --- a/dom/media/MediaPromise.h +++ b/dom/media/MediaPromise.h @@ -321,6 +321,7 @@ public: aDispatcher.AssertIsTailDispatcherIfRequired(); MutexAutoLock lock(mMutex); + MOZ_ASSERT(aResponseThread->IsDispatchReliable()); MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveConsumer); mHaveConsumer = true; nsRefPtr thenValue = new ThenValue( @@ -670,6 +671,7 @@ ProxyInternal(AbstractThread* aTarget, MethodCallBase* aMethodCall, { nsRefPtr p = new (typename PromiseType::Private)(aCallerName); nsRefPtr> r = new ProxyRunnable(p, aMethodCall); + MOZ_ASSERT(aTarget->IsDispatchReliable()); aDispatcher.AddTask(aTarget, r.forget()); return Move(p); } diff --git a/dom/media/MediaTaskQueue.cpp b/dom/media/MediaTaskQueue.cpp index adf3e643c69..6998831b214 100644 --- a/dom/media/MediaTaskQueue.cpp +++ b/dom/media/MediaTaskQueue.cpp @@ -56,14 +56,6 @@ MediaTaskQueue::TailDispatcher() return *mTailDispatcher; } -nsresult -MediaTaskQueue::ForceDispatch(TemporaryRef aRunnable) -{ - AssertInTailDispatchIfNeeded(); // Do this before acquiring the monitor. - MonitorAutoLock mon(mQueueMonitor); - return DispatchLocked(aRunnable, Forced); -} - nsresult MediaTaskQueue::DispatchLocked(TemporaryRef aRunnable, DispatchMode aMode) @@ -75,7 +67,7 @@ MediaTaskQueue::DispatchLocked(TemporaryRef aRunnable, if (mIsShutdown) { return NS_ERROR_FAILURE; } - mTasks.push(TaskQueueEntry(aRunnable, aMode == Forced)); + mTasks.push(aRunnable); if (mIsRunning) { return NS_OK; } @@ -199,13 +191,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 +226,7 @@ MediaTaskQueue::Runner::Run() mon.NotifyAll(); return NS_OK; } - event = mQueue->mTasks.front().mRunnable; + event = mQueue->mTasks.front(); mQueue->mTasks.pop(); } MOZ_ASSERT(event); diff --git a/dom/media/MediaTaskQueue.h b/dom/media/MediaTaskQueue.h index 9a4d2631f95..f54d5742530 100644 --- a/dom/media/MediaTaskQueue.h +++ b/dom/media/MediaTaskQueue.h @@ -80,15 +80,11 @@ public: { RefPtr r(aRunnable); MonitorAutoLock mon(mQueueMonitor); - nsresult rv = DispatchLocked(r, Forced); + nsresult rv = DispatchLocked(r, 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 aRunnable); - // DEPRECATED! Do not us, if a flush happens at the same time, this function // can hang and block forever! nsresult SyncDispatch(TemporaryRef aRunnable); @@ -123,7 +119,7 @@ protected: // mQueueMonitor must be held. void AwaitIdleLocked(); - enum DispatchMode { AbortIfFlushing, IgnoreFlushing, Forced }; + enum DispatchMode { AbortIfFlushing, IgnoreFlushing }; nsresult DispatchLocked(TemporaryRef aRunnable, DispatchMode aMode); @@ -133,17 +129,8 @@ protected: // Monitor that protects the queue and mIsRunning; Monitor mQueueMonitor; - struct TaskQueueEntry { - RefPtr mRunnable; - bool mForceDispatch; - - explicit TaskQueueEntry(TemporaryRef aRunnable, - bool aForceDispatch = false) - : mRunnable(aRunnable), mForceDispatch(aForceDispatch) {} - }; - // Queue of tasks to run. - std::queue mTasks; + std::queue> mTasks; // The thread currently running the task queue. We store a reference // to this so that IsCurrentThreadIn() can tell if the current thread @@ -222,6 +209,8 @@ public: nsresult FlushAndDispatch(TemporaryRef aRunnable); void Flush(); + bool IsDispatchReliable() override { return false; } + private: class MOZ_STACK_CLASS AutoSetFlushing From d18b093da38f2e8a8f5b13b99c03d5e3a988d077 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Tue, 14 Apr 2015 18:01:44 -0700 Subject: [PATCH 04/43] Bug 1154782 - Change the canonical internal smart pointer type in MediaTaskQueue to the XPCOM style. r=jww We've been meaning to do this for a while, just haven't gotten around to it. --- dom/media/MediaTaskQueue.cpp | 17 +++++------------ dom/media/MediaTaskQueue.h | 15 +++++++++------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/dom/media/MediaTaskQueue.cpp b/dom/media/MediaTaskQueue.cpp index 6998831b214..4bab409cd8d 100644 --- a/dom/media/MediaTaskQueue.cpp +++ b/dom/media/MediaTaskQueue.cpp @@ -40,14 +40,6 @@ MediaTaskQueue::~MediaTaskQueue() MOZ_COUNT_DTOR(MediaTaskQueue); } -nsresult -MediaTaskQueue::Dispatch(TemporaryRef aRunnable) -{ - AssertInTailDispatchIfNeeded(); // Do this before acquiring the monitor. - MonitorAutoLock mon(mQueueMonitor); - return DispatchLocked(aRunnable, AbortIfFlushing); -} - TaskDispatcher& MediaTaskQueue::TailDispatcher() { @@ -57,17 +49,17 @@ MediaTaskQueue::TailDispatcher() } nsresult -MediaTaskQueue::DispatchLocked(TemporaryRef aRunnable, - DispatchMode aMode) +MediaTaskQueue::DispatchLocked(already_AddRefed aRunnable, DispatchMode aMode) { mQueueMonitor.AssertCurrentThreadOwns(); + nsCOMPtr r = aRunnable; if (mIsFlushing && aMode == AbortIfFlushing) { return NS_ERROR_ABORT; } if (mIsShutdown) { return NS_ERROR_FAILURE; } - mTasks.push(aRunnable); + mTasks.push(r.forget()); if (mIsRunning) { return NS_OK; } @@ -179,7 +171,8 @@ FlushableMediaTaskQueue::FlushAndDispatch(TemporaryRef aRunnable) MonitorAutoLock mon(mQueueMonitor); AutoSetFlushing autoFlush(this); FlushLocked(); - nsresult rv = DispatchLocked(aRunnable, IgnoreFlushing); + nsCOMPtr r = dont_AddRef(aRunnable.take()); + nsresult rv = DispatchLocked(r.forget(), IgnoreFlushing); NS_ENSURE_SUCCESS(rv, rv); AwaitIdleLocked(); return NS_OK; diff --git a/dom/media/MediaTaskQueue.h b/dom/media/MediaTaskQueue.h index f54d5742530..4d0e5cfef06 100644 --- a/dom/media/MediaTaskQueue.h +++ b/dom/media/MediaTaskQueue.h @@ -41,7 +41,12 @@ public: explicit MediaTaskQueue(TemporaryRef aPool, bool aRequireTailDispatch = false); - nsresult Dispatch(TemporaryRef aRunnable); + nsresult Dispatch(TemporaryRef aRunnable) + { + MonitorAutoLock mon(mQueueMonitor); + nsCOMPtr r = dont_AddRef(aRunnable.take()); + return DispatchLocked(r.forget(), AbortIfFlushing); + } // Returns a TaskDispatcher that will dispatch its tasks when the currently- // running tasks pops off the stack. @@ -78,9 +83,8 @@ public: void Dispatch(already_AddRefed aRunnable, DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) override { - RefPtr r(aRunnable); MonitorAutoLock mon(mQueueMonitor); - nsresult rv = DispatchLocked(r, AbortIfFlushing); + nsresult rv = DispatchLocked(Move(aRunnable), AbortIfFlushing); MOZ_DIAGNOSTIC_ASSERT(aFailureHandling == DontAssertDispatchSuccess || NS_SUCCEEDED(rv)); unused << rv; } @@ -121,8 +125,7 @@ protected: enum DispatchMode { AbortIfFlushing, IgnoreFlushing }; - nsresult DispatchLocked(TemporaryRef aRunnable, - DispatchMode aMode); + nsresult DispatchLocked(already_AddRefed aRunnable, DispatchMode aMode); RefPtr mPool; @@ -130,7 +133,7 @@ protected: Monitor mQueueMonitor; // Queue of tasks to run. - std::queue> mTasks; + std::queue> mTasks; // The thread currently running the task queue. We store a reference // to this so that IsCurrentThreadIn() can tell if the current thread From fa335dee6ca649a5212223245b012f26a4fb2faf Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Tue, 14 Apr 2015 18:18:54 -0700 Subject: [PATCH 05/43] Bug 1154782 - Align the failure handling of the TemporaryRef Dispatch overload with its companion. r=jww Most of the callers ignore the return values. The MOZ_DIAGNOSTIC_ASSERTS will tell us if any of these actually fail in practice. --- dom/media/MediaTaskQueue.cpp | 10 ++++------ dom/media/MediaTaskQueue.h | 8 ++++---- dom/media/mediasource/TrackBuffer.cpp | 7 +------ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/dom/media/MediaTaskQueue.cpp b/dom/media/MediaTaskQueue.cpp index 4bab409cd8d..2e2c5fdbfe5 100644 --- a/dom/media/MediaTaskQueue.cpp +++ b/dom/media/MediaTaskQueue.cpp @@ -93,12 +93,11 @@ public: return rv; } - nsresult WaitUntilDone() { + void WaitUntilDone() { MonitorAutoLock mon(mMonitor); while (!mDone) { mon.Wait(); } - return NS_OK; } private: RefPtr mRunnable; @@ -106,13 +105,12 @@ private: bool mDone; }; -nsresult +void MediaTaskQueue::SyncDispatch(TemporaryRef aRunnable) { NS_WARNING("MediaTaskQueue::SyncDispatch is dangerous and deprecated. Stop using this!"); RefPtr task(new MediaTaskQueueSyncRunnable(aRunnable)); - nsresult rv = Dispatch(task); - NS_ENSURE_SUCCESS(rv, rv); - return task->WaitUntilDone(); + Dispatch(task); + task->WaitUntilDone(); } void diff --git a/dom/media/MediaTaskQueue.h b/dom/media/MediaTaskQueue.h index 4d0e5cfef06..48585f77daf 100644 --- a/dom/media/MediaTaskQueue.h +++ b/dom/media/MediaTaskQueue.h @@ -41,11 +41,11 @@ public: explicit MediaTaskQueue(TemporaryRef aPool, bool aRequireTailDispatch = false); - nsresult Dispatch(TemporaryRef aRunnable) + void Dispatch(TemporaryRef aRunnable, + DispatchFailureHandling aFailureHandling = AssertDispatchSuccess) { - MonitorAutoLock mon(mQueueMonitor); nsCOMPtr r = dont_AddRef(aRunnable.take()); - return DispatchLocked(r.forget(), AbortIfFlushing); + return Dispatch(r.forget(), aFailureHandling); } // Returns a TaskDispatcher that will dispatch its tasks when the currently- @@ -91,7 +91,7 @@ public: // DEPRECATED! Do not us, if a flush happens at the same time, this function // can hang and block forever! - nsresult SyncDispatch(TemporaryRef aRunnable); + void SyncDispatch(TemporaryRef 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 diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index a29c11aac52..8d6a2e2667c 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -564,12 +564,7 @@ TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder) NS_NewRunnableMethodWithArg(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; } From 381dec0f8d1997b95877250a9f0d24321047a887 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 14 Apr 2015 11:07:48 +0900 Subject: [PATCH 06/43] Bug 1153154 - Add stdc++compat check for host programs when not cross compiling. r=mshal --- config/rules.mk | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config/rules.mk b/config/rules.mk index 70a6e15044d..2c1e373e6f1 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -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) @@ -798,6 +804,9 @@ endif endif endif endif +ifndef CROSS_COMPILE + $(call CHECK_STDCXX,$@) +endif # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files, # so instead of deleting .o files after repacking them into a dylib, we make From e3995bb3eb0d42352c1107e843791d658a6bcd12 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 14 Apr 2015 13:47:48 +0900 Subject: [PATCH 07/43] Bug 1153154 - Add stdc++-compat hack for std::string::_S_compare and std::runtime_error::runtime_error. r=nfroyd --- build/unix/stdc++compat/stdc++compat.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/build/unix/stdc++compat/stdc++compat.cpp b/build/unix/stdc++compat/stdc++compat.cpp index 504cd6a3ba0..4b1aa61f402 100644 --- a/build/unix/stdc++compat/stdc++compat.cpp +++ b/build/unix/stdc++compat/stdc++compat.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 From 6a81b0cab5ee0c761321ae16e40b6bd266cb08ee Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Wed, 15 Apr 2015 14:26:33 +0900 Subject: [PATCH 08/43] Bug 1154596 - Build elfhack tests with -fno-lto. r=nfroyd LTO changes in GCC5 make it break the assumptions made in the test-ctors source. The simplest and more correct thing to do is to just force the test files never to be built with LTO. They are meant to have a particular layout that LTO might break at any time anyways. --- build/unix/elfhack/moz.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/unix/elfhack/moz.build b/build/unix/elfhack/moz.build index a3cb69c9b18..e5cdae5ecb0 100644 --- a/build/unix/elfhack/moz.build +++ b/build/unix/elfhack/moz.build @@ -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', From eb111be052292fe8dbee709a0d7de7e85fd93bef Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Thu, 16 Apr 2015 14:51:27 +0900 Subject: [PATCH 09/43] Bug 478029 Enable TSF in release builds r=jimm+emk+m_kato --- modules/libpref/init/all.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 57ccc8ee227..2f7347ec40f 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -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); From 90063ec981772c09c11e98388f31e528cd8dc6ac Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Thu, 16 Apr 2015 07:57:00 +0200 Subject: [PATCH 10/43] Bug 1151644 - Don't disallow the basic compositor backend. r=jrmuizel --- widget/nsBaseWidget.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index c4d0b8bed47..0ac5bd961dd 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -1137,17 +1137,6 @@ void nsBaseWidget::CreateCompositor(int aWidth, int aHeight) nsTArray backendHints; GetPreferredCompositorBackends(backendHints); -#if !defined(MOZ_X11) && !defined(XP_WIN) - if (!mRequireOffMainThreadCompositing && - !Preferences::GetBool("layers.offmainthreadcomposition.force-basic", false)) { - for (size_t i = 0; i < backendHints.Length(); ++i) { - if (backendHints[i] == LayersBackend::LAYERS_BASIC) { - backendHints[i] = LayersBackend::LAYERS_NONE; - } - } - } -#endif - bool success = false; if (!backendHints.IsEmpty()) { shadowManager = mCompositorChild->SendPLayerTransactionConstructor( From cf828c45571c2a3d5c14ce31f01a4c8a68642d47 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Thu, 16 Apr 2015 15:29:07 +0900 Subject: [PATCH 11/43] Fixup for bug 1153154 for bustage on a CLOSED TREE. r=me There was no need for a stdc++compat check on host static libraries anyways. --- config/rules.mk | 3 --- 1 file changed, 3 deletions(-) diff --git a/config/rules.mk b/config/rules.mk index 2c1e373e6f1..0097226f938 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -804,9 +804,6 @@ endif endif endif endif -ifndef CROSS_COMPILE - $(call CHECK_STDCXX,$@) -endif # On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files, # so instead of deleting .o files after repacking them into a dylib, we make From eddb13be4c192882b251119934884b49cb81a6cc Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Thu, 2 Apr 2015 21:08:11 -0400 Subject: [PATCH 12/43] Bug 748550 - Remove support for |for (... = ... in/of ...)| now that ES6 has removed it. r=jorendorff --- js/src/frontend/Parser.cpp | 38 +++---------------- .../jit-test/tests/auto-regress/bug590772.js | 2 +- .../jit-test/tests/auto-regress/bug596817.js | 8 +++- js/src/jit-test/tests/closures/bug540136.js | 17 --------- js/src/jit-test/tests/closures/bug540348.js | 3 -- .../tests/ion/recover-lambdas-bug1118911.js | 2 +- js/src/jit-test/tests/jaeger/bug583684.js | 8 ---- js/src/jit-test/tests/jaeger/bug719758.js | 10 ----- js/src/js.msg | 3 +- js/src/tests/js1_5/Regress/regress-252892.js | 31 ++++++++------- .../tests/js1_5/extensions/regress-226078.js | 2 +- js/src/tests/js1_8/regress/regress-459185.js | 2 +- js/src/tests/js1_8/regress/regress-459186.js | 2 +- .../js1_8_1/regress/regress-452498-052.js | 2 +- .../js1_8_1/regress/regress-452498-053.js | 2 +- .../js1_8_1/regress/regress-452498-117.js | 2 +- .../js1_8_1/regress/regress-452498-123.js | 2 +- .../reflect-parse/for-loop-destructuring.js | 16 -------- .../tests/js1_8_5/regress/regress-600137.js | 2 +- .../tests/js1_8_5/regress/regress-672892.js | 2 +- js/src/vm/Xdr.h | 4 +- 21 files changed, 43 insertions(+), 117 deletions(-) delete mode 100644 js/src/jit-test/tests/closures/bug540136.js delete mode 100644 js/src/jit-test/tests/closures/bug540348.js delete mode 100644 js/src/jit-test/tests/jaeger/bug583684.js delete mode 100644 js/src/jit-test/tests/jaeger/bug719758.js diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7626d3c99e0..9a835b14d94 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4807,39 +4807,11 @@ Parser::forStatement() if (isForDecl) { pn2 = pn1->pn_head; if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) { - /* - * Declaration with initializer. - * - * Rewrite 'for ( x = i in o)' where 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. */ diff --git a/js/src/jit-test/tests/auto-regress/bug590772.js b/js/src/jit-test/tests/auto-regress/bug590772.js index 2464863ae75..a5e81091d89 100644 --- a/js/src/jit-test/tests/auto-regress/bug590772.js +++ b/js/src/jit-test/tests/auto-regress/bug590772.js @@ -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 []) { }") diff --git a/js/src/jit-test/tests/auto-regress/bug596817.js b/js/src/jit-test/tests/auto-regress/bug596817.js index b6170d36982..247bb0c2ce6 100644 --- a/js/src/jit-test/tests/auto-regress/bug596817.js +++ b/js/src/jit-test/tests/auto-regress/bug596817.js @@ -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. + []; + } +})(); diff --git a/js/src/jit-test/tests/closures/bug540136.js b/js/src/jit-test/tests/closures/bug540136.js deleted file mode 100644 index 54b57713a7f..00000000000 --- a/js/src/jit-test/tests/closures/bug540136.js +++ /dev/null @@ -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(\"\"))\ - )\ - ([])\ - })\ -"))() diff --git a/js/src/jit-test/tests/closures/bug540348.js b/js/src/jit-test/tests/closures/bug540348.js deleted file mode 100644 index 6a0b6f0b35c..00000000000 --- a/js/src/jit-test/tests/closures/bug540348.js +++ /dev/null @@ -1,3 +0,0 @@ -(function() { - for (var [e] = [] in (eval("for (b = 0; b < 6; ++b) gc()"))) {} -})() diff --git a/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js index bf527351481..7c27d395438 100644 --- a/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js +++ b/js/src/jit-test/tests/ion/recover-lambdas-bug1118911.js @@ -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) {} diff --git a/js/src/jit-test/tests/jaeger/bug583684.js b/js/src/jit-test/tests/jaeger/bug583684.js deleted file mode 100644 index c0343621182..00000000000 --- a/js/src/jit-test/tests/jaeger/bug583684.js +++ /dev/null @@ -1,8 +0,0 @@ -// |jit-test| error: TypeError -(function () { - var b = e - for (var [e] = b in w) c -})() - -/* Don't assert. */ - diff --git a/js/src/jit-test/tests/jaeger/bug719758.js b/js/src/jit-test/tests/jaeger/bug719758.js deleted file mode 100644 index e1569a901af..00000000000 --- a/js/src/jit-test/tests/jaeger/bug719758.js +++ /dev/null @@ -1,10 +0,0 @@ - -function test() { - try { - for (var i = 0 in this) throw p; - } catch (e) { - if (i !== 94) - return "what"; - } -} -test(); diff --git a/js/src/js.msg b/js/src/js.msg index 0c24bb30e4a..39227e3a2b7 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -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") diff --git a/js/src/tests/js1_5/Regress/regress-252892.js b/js/src/tests/js1_5/Regress/regress-252892.js index 363c21e5111..5f799bb18fb 100644 --- a/js/src/tests/js1_5/Regress/regress-252892.js +++ b/js/src/tests/js1_5/Regress/regress-252892.js @@ -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); diff --git a/js/src/tests/js1_5/extensions/regress-226078.js b/js/src/tests/js1_5/extensions/regress-226078.js index 3528d28ec1f..178d0a11f76 100644 --- a/js/src/tests/js1_5/extensions/regress-226078.js +++ b/js/src/tests/js1_5/extensions/regress-226078.js @@ -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'); } diff --git a/js/src/tests/js1_8/regress/regress-459185.js b/js/src/tests/js1_8/regress/regress-459185.js index 6ef6903e266..fd31d462d5c 100644 --- a/js/src/tests/js1_8/regress/regress-459185.js +++ b/js/src/tests/js1_8/regress/regress-459185.js @@ -22,7 +22,7 @@ function test() try { - for (var {a: []} = 2 in []) { } + for (var {a: []} in []) { } } catch(ex) { diff --git a/js/src/tests/js1_8/regress/regress-459186.js b/js/src/tests/js1_8/regress/regress-459186.js index d976cc5a05c..0aeda8ede7c 100644 --- a/js/src/tests/js1_8/regress/regress-459186.js +++ b/js/src/tests/js1_8/regress/regress-459186.js @@ -22,7 +22,7 @@ function test() try { - for (var [,{y}] = 1 in []) {} + for (var [,{y}] in []) {} } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-052.js b/js/src/tests/js1_8_1/regress/regress-452498-052.js index 55ea0dd303f..9986ccd6117 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-052.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-052.js @@ -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 diff --git a/js/src/tests/js1_8_1/regress/regress-452498-053.js b/js/src/tests/js1_8_1/regress/regress-452498-053.js index e36ca1b8be5..6c053a5fdc5 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-053.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-053.js @@ -52,7 +52,7 @@ function test() // try { - for (var [x] = x in y) var x; + for (var [x] in y) var x; } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-117.js b/js/src/tests/js1_8_1/regress/regress-452498-117.js index b52a5f55465..c9b5731341b 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-117.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-117.js @@ -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 // ===== diff --git a/js/src/tests/js1_8_1/regress/regress-452498-123.js b/js/src/tests/js1_8_1/regress/regress-452498-123.js index 7673d800ca3..ec9973fdd53 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-123.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-123.js @@ -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 // ===== diff --git a/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js b/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js index 43106b2f851..d5657f7494c 100644 --- a/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js +++ b/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js @@ -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); diff --git a/js/src/tests/js1_8_5/regress/regress-600137.js b/js/src/tests/js1_8_5/regress/regress-600137.js index 31ac0c6a72d..41fb83a2f29 100644 --- a/js/src/tests/js1_8_5/regress/regress-600137.js +++ b/js/src/tests/js1_8_5/regress/regress-600137.js @@ -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' + diff --git a/js/src/tests/js1_8_5/regress/regress-672892.js b/js/src/tests/js1_8_5/regress/regress-672892.js index 843af1d0efc..57c8732eba4 100644 --- a/js/src/tests/js1_8_5/regress/regress-672892.js +++ b/js/src/tests/js1_8_5/regress/regress-672892.js @@ -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'); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index d9e213d2048..52f17636bb0 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -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 " From 63f01dedf5fd55a879d23dbbe920c8d67ca996d0 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Wed, 15 Apr 2015 10:46:41 -0700 Subject: [PATCH 13/43] Bug 1154480 - Make new Uint8Array().set([], -1) throw a RangeError, not merely an Error. r=till --- js/src/js.msg | 4 +-- .../ecma_6/TypedArray/set-negative-offset.js | 35 +++++++++++++++++++ js/src/vm/TypedArrayCommon.h | 3 +- 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 js/src/tests/ecma_6/TypedArray/set-negative-offset.js diff --git a/js/src/js.msg b/js/src/js.msg index 39227e3a2b7..0b2f190556d 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -455,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 diff --git a/js/src/tests/ecma_6/TypedArray/set-negative-offset.js b/js/src/tests/ecma_6/TypedArray/set-negative-offset.js new file mode 100644 index 00000000000..6d9c45ffb2c --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/set-negative-offset.js @@ -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"); diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index a5857290ccd..3bfb1c7fa06 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -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; } } From e0f8fbab371cd81573a11650b9fa446d873fcafe Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Wed, 15 Apr 2015 10:55:39 -0700 Subject: [PATCH 14/43] Bug 1154532 - Add ThrowRangeError and ThrowTypeError intrinsics to make self-hosted code's behavior clearer -- and also have each assert that error number and requested error type are consistent. (It appears no self-hosted code throws SyntaxError, ReferenceError, or URIError yet, so no adding functions for those yet.) r=till --- js/src/builtin/Array.js | 4 +-- js/src/builtin/Intl.js | 4 +-- js/src/builtin/TypedArray.js | 8 +++--- js/src/jscntxt.h | 2 ++ js/src/vm/SelfHosting.cpp | 50 +++++++++++++++++++++++++++++++----- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index c6a07bfe790..b3f16d5ef38 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -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; diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index 776ecb1a56e..74513209ab4 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -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); diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 1df7b803981..fcf14d5e0d0 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -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; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 1a0916561e7..fae098031bc 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -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); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index beca43e72d6..bda19fdf0e8 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -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(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 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), From 7cf531615892bcb93207f5ffa9a0b94409b7e973 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Wed, 15 Apr 2015 08:04:00 +0200 Subject: [PATCH 15/43] Bug 1154403 - Reduce incremental finalize slice time to 5ms. r=smaug --- xpcom/base/CycleCollectedJSRuntime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 6e3bbda9bda..92a8ce03a32 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -98,7 +98,7 @@ class IncrementalFinalizeRunnable : public nsRunnable uint32_t mFinalizeFunctionToRun; bool mReleasing; - static const PRTime SliceMillis = 10; /* ms */ + static const PRTime SliceMillis = 5; /* ms */ static PLDHashOperator DeferredFinalizerEnumerator(DeferredFinalizeFunction& aFunction, From 931484873c7cf7cfdd2dd8a1747c2eeda6dccbd9 Mon Sep 17 00:00:00 2001 From: Felix Janda Date: Thu, 5 Feb 2015 22:26:24 +0100 Subject: [PATCH 16/43] Bug 1151202 - libstagefright: Fix compilation for systems without . r=cpearce --- media/libstagefright/system/core/include/cutils/properties.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/libstagefright/system/core/include/cutils/properties.h b/media/libstagefright/system/core/include/cutils/properties.h index c380d5d5007..79029f3e60d 100644 --- a/media/libstagefright/system/core/include/cutils/properties.h +++ b/media/libstagefright/system/core/include/cutils/properties.h @@ -17,7 +17,7 @@ #ifndef __CUTILS_PROPERTIES_H #define __CUTILS_PROPERTIES_H -#include +#include #include #ifdef __cplusplus From 9ab602113f79c486e9d1e018423744eae1f16b48 Mon Sep 17 00:00:00 2001 From: Jed Davis Date: Wed, 8 Apr 2015 15:36:00 +0200 Subject: [PATCH 17/43] Bug 1148650 - Strengthen assertion that RemoveScriptBlocker is called on the main thread. r=smaug --- dom/base/nsContentUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 02053a0b854..80607405d0f 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -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; From f53605a367cb4617de617c2c121f1f646537460c Mon Sep 17 00:00:00 2001 From: Morris Tseng Date: Tue, 14 Apr 2015 22:39:00 +0200 Subject: [PATCH 18/43] Bug 1147279 - Get correct translation when layer optimize away. r=roc --- gfx/layers/LayersTypes.h | 15 +++++++++++++++ gfx/src/nsRegion.cpp | 5 +++++ layout/base/FrameLayerBuilder.cpp | 9 +++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/gfx/layers/LayersTypes.h b/gfx/layers/LayersTypes.h index 8b6af63cac8..d79c438987f 100644 --- a/gfx/layers/LayersTypes.h +++ b/gfx/layers/LayersTypes.h @@ -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); diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp index 515cb6e6982..67e6630df80 100644 --- a/gfx/src/nsRegion.cpp +++ b/gfx/src/nsRegion.cpp @@ -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; imDispatchToContentHitRegion)); 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); } From e21dafadf8beff031ae06fcd0e5e9a3c240dd8f5 Mon Sep 17 00:00:00 2001 From: James Graham Date: Wed, 15 Apr 2015 16:00:44 +0100 Subject: [PATCH 19/43] Bug 1155079 - Update web-platform-tests to revision 89b6e2bc460316c7f273712d22f0b2d3a3d0c5be, a =testonly --- testing/web-platform/meta/MANIFEST.json | 113 +++++++----------- testing/web-platform/meta/mozilla-sync | 2 +- .../tests/XMLHttpRequest/send-usp.html | 10 ++ .../tests/XMLHttpRequest/send-usp.js | 39 ++++++ .../tests/XMLHttpRequest/send-usp.worker.js | 4 + .../the-window-object/window-properties.html | 1 + .../tests/html/dom/elements-misc.js | 3 + .../tests/tools/manifest/sourcefile.py | 9 +- .../tests/tools/wptserve/wptserve/server.py | 2 +- .../EventTarget.worker.js | 10 +- 10 files changed, 111 insertions(+), 82 deletions(-) create mode 100644 testing/web-platform/tests/XMLHttpRequest/send-usp.html create mode 100644 testing/web-platform/tests/XMLHttpRequest/send-usp.js create mode 100644 testing/web-platform/tests/XMLHttpRequest/send-usp.worker.js diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index d754b6a60f1..acaecedbe62 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -9627,6 +9627,14 @@ "path": "XMLHttpRequest/send-timeout-events.htm", "url": "/XMLHttpRequest/send-timeout-events.htm" }, + { + "path": "XMLHttpRequest/send-usp.html", + "url": "/XMLHttpRequest/send-usp.html" + }, + { + "path": "XMLHttpRequest/send-usp.worker.js", + "url": "/XMLHttpRequest/send-usp.worker" + }, { "path": "XMLHttpRequest/setrequestheader-after-send.htm", "url": "/XMLHttpRequest/setrequestheader-after-send.htm" @@ -16099,14 +16107,6 @@ "path": "service-workers/cache-storage/serviceworker/cache-delete.https.html", "url": "/service-workers/cache-storage/serviceworker/cache-delete.https.html" }, - { - "path": "service-workers/cache-storage/serviceworker/cache-match.https.html", - "url": "/service-workers/cache-storage/serviceworker/cache-match.https.html" - }, - { - "path": "service-workers/cache-storage/serviceworker/cache-put.https.html", - "url": "/service-workers/cache-storage/serviceworker/cache-put.https.html" - }, { "path": "service-workers/cache-storage/serviceworker/cache-storage-keys.https.html", "url": "/service-workers/cache-storage/serviceworker/cache-storage-keys.https.html" @@ -16127,14 +16127,6 @@ "path": "service-workers/cache-storage/window/cache-delete.https.html", "url": "/service-workers/cache-storage/window/cache-delete.https.html" }, - { - "path": "service-workers/cache-storage/window/cache-match.https.html", - "url": "/service-workers/cache-storage/window/cache-match.https.html" - }, - { - "path": "service-workers/cache-storage/window/cache-put.https.html", - "url": "/service-workers/cache-storage/window/cache-put.https.html" - }, { "path": "service-workers/cache-storage/window/cache-storage-keys.https.html", "url": "/service-workers/cache-storage/window/cache-storage-keys.https.html" @@ -16159,14 +16151,6 @@ "path": "service-workers/cache-storage/worker/cache-delete.https.html", "url": "/service-workers/cache-storage/worker/cache-delete.https.html" }, - { - "path": "service-workers/cache-storage/worker/cache-match.https.html", - "url": "/service-workers/cache-storage/worker/cache-match.https.html" - }, - { - "path": "service-workers/cache-storage/worker/cache-put.https.html", - "url": "/service-workers/cache-storage/worker/cache-put.https.html" - }, { "path": "service-workers/cache-storage/worker/cache-storage-keys.https.html", "url": "/service-workers/cache-storage/worker/cache-storage-keys.https.html" @@ -19514,6 +19498,36 @@ "timeout": "long", "url": "/media-source/mediasource-redundant-seek.html" }, + { + "path": "service-workers/cache-storage/serviceworker/cache-match.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/serviceworker/cache-match.https.html" + }, + { + "path": "service-workers/cache-storage/serviceworker/cache-put.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/serviceworker/cache-put.https.html" + }, + { + "path": "service-workers/cache-storage/window/cache-match.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/window/cache-match.https.html" + }, + { + "path": "service-workers/cache-storage/window/cache-put.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/window/cache-put.https.html" + }, + { + "path": "service-workers/cache-storage/worker/cache-match.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/worker/cache-match.https.html" + }, + { + "path": "service-workers/cache-storage/worker/cache-put.https.html", + "timeout": "long", + "url": "/service-workers/cache-storage/worker/cache-put.https.html" + }, { "path": "websockets/binary/002.html", "timeout": "long", @@ -19646,52 +19660,7 @@ }, "local_changes": { "deleted": [], - "items": { - "testharness": { - "service-workers/cache-storage/serviceworker/cache-match.https.html": [ - { - "path": "service-workers/cache-storage/serviceworker/cache-match.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/serviceworker/cache-match.https.html" - } - ], - "service-workers/cache-storage/serviceworker/cache-put.https.html": [ - { - "path": "service-workers/cache-storage/serviceworker/cache-put.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/serviceworker/cache-put.https.html" - } - ], - "service-workers/cache-storage/window/cache-match.https.html": [ - { - "path": "service-workers/cache-storage/window/cache-match.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/window/cache-match.https.html" - } - ], - "service-workers/cache-storage/window/cache-put.https.html": [ - { - "path": "service-workers/cache-storage/window/cache-put.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/window/cache-put.https.html" - } - ], - "service-workers/cache-storage/worker/cache-match.https.html": [ - { - "path": "service-workers/cache-storage/worker/cache-match.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/worker/cache-match.https.html" - } - ], - "service-workers/cache-storage/worker/cache-put.https.html": [ - { - "path": "service-workers/cache-storage/worker/cache-put.https.html", - "timeout": "long", - "url": "/service-workers/cache-storage/worker/cache-put.https.html" - } - ] - } - }, + "items": {}, "reftest_nodes": {} }, "reftest_nodes": { @@ -24760,7 +24729,7 @@ } ] }, - "rev": "7311aa630534282885b9add15b1c30b2b59316dd", + "rev": "89b6e2bc460316c7f273712d22f0b2d3a3d0c5be", "url_base": "/", "version": 2 -} +} \ No newline at end of file diff --git a/testing/web-platform/meta/mozilla-sync b/testing/web-platform/meta/mozilla-sync index 714b831218a..72a09afb230 100644 --- a/testing/web-platform/meta/mozilla-sync +++ b/testing/web-platform/meta/mozilla-sync @@ -1 +1 @@ -cb3abe0063c59cf9273978c8db7700838bf835ad \ No newline at end of file +ec9e346b36e59c9a4fc6fc1cc5a3687417769191 \ No newline at end of file diff --git a/testing/web-platform/tests/XMLHttpRequest/send-usp.html b/testing/web-platform/tests/XMLHttpRequest/send-usp.html new file mode 100644 index 00000000000..1753e5f3a5b --- /dev/null +++ b/testing/web-platform/tests/XMLHttpRequest/send-usp.html @@ -0,0 +1,10 @@ + + +XMLHttpRequest.send(URLSearchParams) + + + +
+ diff --git a/testing/web-platform/tests/XMLHttpRequest/send-usp.js b/testing/web-platform/tests/XMLHttpRequest/send-usp.js new file mode 100644 index 00000000000..56e9e09640f --- /dev/null +++ b/testing/web-platform/tests/XMLHttpRequest/send-usp.js @@ -0,0 +1,39 @@ +function encode(n) { + if (n === 0x20) { + return "\x2B"; + } + + if (n === 0x2A || n === 0x2D || n === 0x2E || + (0x30 <= n && n <= 0x39) || (0x41 <= n && n <= 0x5A) || + n === 0x5F || (0x61 <= n && n <= 0x7A)) { + return String.fromCharCode(n); + } + + var s = n.toString(16).toUpperCase(); + return "%" + (s.length === 2 ? s : '0' + s); +} + +function do_test(n) { + async_test(function() { + var x = new XMLHttpRequest(); + x.onload = this.step_func_done(function(e) { + assert_equals(x.response, "a=" + encode(n)) + }); + x.onerror = this.unreached_func(); + x.open("POST", "resources/content.py"); + var usp = new URLSearchParams(); + usp.append("a", String.fromCharCode(n)); + x.send(usp) + }, "XMLHttpRequest.send(URLSearchParams) (" + n + ")"); +} + +function run_test() { + var i = 0; + add_result_callback(function() { + if (++i === 128) { + return; + } + do_test(i); + }); + do_test(i); +} diff --git a/testing/web-platform/tests/XMLHttpRequest/send-usp.worker.js b/testing/web-platform/tests/XMLHttpRequest/send-usp.worker.js new file mode 100644 index 00000000000..071b2200047 --- /dev/null +++ b/testing/web-platform/tests/XMLHttpRequest/send-usp.worker.js @@ -0,0 +1,4 @@ +importScripts("/resources/testharness.js"); +importScripts("/resources/testharnessreport.js"); +importScripts("send-usp.js"); +run_test(); diff --git a/testing/web-platform/tests/html/browsers/the-window-object/window-properties.html b/testing/web-platform/tests/html/browsers/the-window-object/window-properties.html index 73ef77b4fe6..3316bf5316f 100644 --- a/testing/web-platform/tests/html/browsers/the-window-object/window-properties.html +++ b/testing/web-platform/tests/html/browsers/the-window-object/window-properties.html @@ -67,6 +67,7 @@ var replaceableAttributes = [ "screenY", "outerWidth", "outerHeight", + "devicePixelRatio", ]; var methods = [ diff --git a/testing/web-platform/tests/html/dom/elements-misc.js b/testing/web-platform/tests/html/dom/elements-misc.js index b495d83d4ce..77cc4cd729a 100644 --- a/testing/web-platform/tests/html/dom/elements-misc.js +++ b/testing/web-platform/tests/html/dom/elements-misc.js @@ -50,6 +50,9 @@ var miscElements = { radiogroup: "string", "default": "boolean", }, + dialog: { + open: "boolean", + }, // Global attributes should exist even on unknown elements undefinedelement: {}, diff --git a/testing/web-platform/tests/tools/manifest/sourcefile.py b/testing/web-platform/tests/tools/manifest/sourcefile.py index 4852c9ddfaa..7c0f976a44b 100644 --- a/testing/web-platform/tests/tools/manifest/sourcefile.py +++ b/testing/web-platform/tests/tools/manifest/sourcefile.py @@ -49,11 +49,12 @@ class SourceFile(object): def __getstate__(self): # Remove computed properties if we pickle this class rv = self.__dict__.copy() - cached_properties = rv.get("__cached_properties__", set()) - for key in rv.keys(): - if key in cached_properties: - del rv[key] + if "__cached_properties__" in rv: + cached_properties = rv["__cached_properties__"] + for key in rv.keys(): + if key in cached_properties: + del rv[key] del rv["__cached_properties__"] return rv diff --git a/testing/web-platform/tests/tools/wptserve/wptserve/server.py b/testing/web-platform/tests/tools/wptserve/wptserve/server.py index 4eeb322cc61..78b982c4970 100644 --- a/testing/web-platform/tests/tools/wptserve/wptserve/server.py +++ b/testing/web-platform/tests/tools/wptserve/wptserve/server.py @@ -285,7 +285,7 @@ class WebTestRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): if response: response.set_error(500, err) response.write() - logger.error(err) + self.logger.error(err) def get_request_line(self): try: diff --git a/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js b/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js index ff0c6fc951c..19045273793 100644 --- a/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js +++ b/testing/web-platform/tests/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js @@ -2,10 +2,12 @@ importScripts("/resources/testharness.js"); test(function() { var i = 0; - addEventListener("message", this.step_func(function listener(evt) { - ++i; - removeEventListener("message", listener, true); - }), true); + addEventListener("message", function listener(evt) { + this.step(function() { + ++i; + removeEventListener("message", listener, true); + }); + }, true); self.dispatchEvent(new Event("message")); self.dispatchEvent(new Event("message")); assert_equals(i, 1); From ade1789a95c8631b69eb96ef24a2d7c68d7d8657 Mon Sep 17 00:00:00 2001 From: James Graham Date: Wed, 15 Apr 2015 16:02:37 +0100 Subject: [PATCH 20/43] Bug 1154823 - Give upstreaming script a simple UI to select commits that should not be upstreamed (e.g. backouts) r=Ms2ger --- testing/web-platform/update/upstream.py | 50 ++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/testing/web-platform/update/upstream.py b/testing/web-platform/update/upstream.py index a36daae23d9..9833a82d678 100644 --- a/testing/web-platform/update/upstream.py +++ b/testing/web-platform/update/upstream.py @@ -133,7 +133,7 @@ class GetBaseCommit(Step): class LoadCommits(Step): """Get a list of commits in the gecko tree that need to be upstreamed""" - provides = ["source_commits"] + provides = ["source_commits", "has_backouts"] def create(self, state): state.source_commits = state.local_tree.log(state.last_sync_commit, @@ -141,23 +141,56 @@ class LoadCommits(Step): update_regexp = re.compile("Bug \d+ - Update web-platform-tests to revision [0-9a-f]{40}") + state.has_backouts = False + for i, commit in enumerate(state.source_commits[:]): if update_regexp.match(commit.message.text): # This is a previous update commit so ignore it state.source_commits.remove(commit) continue - if commit.message.backouts: + elif commit.message.backouts: #TODO: Add support for collapsing backouts - raise NotImplementedError("Need to get the Git->Hg commits for backouts and remove the backed out patch") + state.has_backouts = True - if not commit.message.bug: + elif not commit.message.bug: self.logger.error("Commit %i (%s) doesn't have an associated bug number." % - (i + 1, commit.sha1)) + (i + 1, commit.sha1)) return exit_unclean self.logger.debug("Source commits: %s" % state.source_commits) +class SelectCommits(Step): + """Provide a UI to select which commits to upstream""" + + def create(self, state): + while True: + commits = state.source_commits[:] + for i, commit in enumerate(commits): + print "%i:\t%s" % (i, commit.message.summary) + + remove = raw_input("Provide a space-separated list of any commits numbers to remove from the list to upstream:\n").strip() + remove_idx = set() + for item in remove.split(" "): + try: + item = int(item) + except: + continue + if item < 0 or item >= len(commits): + continue + remove_idx.add(item) + + keep_commits = [(i,cmt) for i,cmt in enumerate(commits) if i not in remove_idx] + #TODO: consider printed removed commits + print "Selected the following commits to keep:" + for i, commit in keep_commits: + print "%i:\t%s" % (i, commit.message.summary) + confirm = raw_input("Keep the above commits? y/n\n").strip().lower() + + if confirm == "y": + state.source_commits = [item[1] for item in keep_commits] + break + class MovePatches(Step): """Convert gecko commits into patches against upstream and commit these to the sync tree.""" @@ -189,9 +222,6 @@ class RebaseCommits(Step): In that case the conflicts can be fixed up locally and the sync process restarted with --continue. """ - - provides = ["rebased_commits"] - def create(self, state): self.logger.info("Rebasing local commits") continue_rebase = False @@ -209,13 +239,14 @@ class RebaseCommits(Step): except subprocess.CalledProcessError: self.logger.info("Rebase failed, fix merge and run %s again with --continue" % sys.argv[0]) raise - state.rebased_commits = state.sync_tree.log(state.base_commit) self.logger.info("Rebase successful") class CheckRebase(Step): """Check if there are any commits remaining after rebase""" + provides = ["rebased_commits"] def create(self, state): + state.rebased_commits = state.sync_tree.log(state.base_commit) if not state.rebased_commits: self.logger.info("Nothing to upstream, exiting") return exit_clean @@ -329,6 +360,7 @@ class SyncToUpstreamRunner(StepRunner): GetLastSyncCommit, GetBaseCommit, LoadCommits, + SelectCommits, MovePatches, RebaseCommits, CheckRebase, From 51a38a7dec7f4bc2a35c989faef7e935ce328b69 Mon Sep 17 00:00:00 2001 From: James Graham Date: Wed, 15 Apr 2015 16:04:49 +0100 Subject: [PATCH 21/43] Bug 1155079 -Update to latest wptrunner, a=testonly --- testing/web-platform/harness/requirements.txt | 3 +- .../harness/wptrunner/browsers/b2g.py | 8 +---- .../harness/wptrunner/browsers/base.py | 12 +++++++ .../harness/wptrunner/browsers/firefox.py | 34 ++++++++----------- .../harness/wptrunner/browsers/servo.py | 11 +++--- .../harness/wptrunner/environment.py | 23 ++++++++++++- .../harness/wptrunner/executors/base.py | 12 +++---- .../wptrunner/executors/executormarionette.py | 14 ++++---- .../wptrunner/executors/executorselenium.py | 8 ++--- .../wptrunner/executors/executorservo.py | 19 ++++++----- .../harness/wptrunner/manifestinclude.py | 2 +- .../harness/wptrunner/testloader.py | 2 -- .../harness/wptrunner/testrunner.py | 13 ++++--- .../harness/wptrunner/wptcommandline.py | 29 ++++++---------- .../harness/wptrunner/wptrunner.py | 3 +- 15 files changed, 105 insertions(+), 88 deletions(-) diff --git a/testing/web-platform/harness/requirements.txt b/testing/web-platform/harness/requirements.txt index d6f528cc3fd..0981fb8fe96 100644 --- a/testing/web-platform/harness/requirements.txt +++ b/testing/web-platform/harness/requirements.txt @@ -1,5 +1,4 @@ html5lib >= 0.99 mozinfo >= 0.7 mozlog >= 2.8 -# Unfortunately, just for gdb flags -mozrunner >= 6.1 +mozdebug >= 0.1 diff --git a/testing/web-platform/harness/wptrunner/browsers/b2g.py b/testing/web-platform/harness/wptrunner/browsers/b2g.py index 1c3c1397cf2..e642897f158 100644 --- a/testing/web-platform/harness/wptrunner/browsers/b2g.py +++ b/testing/web-platform/harness/wptrunner/browsers/b2g.py @@ -20,6 +20,7 @@ from mozprofile import FirefoxProfile, Preferences from .base import get_free_port, BrowserError, Browser, ExecutorBrowser from ..executors.executormarionette import MarionetteTestharnessExecutor from ..hosts import HostsFile, HostsLine +from ..environment import hostnames here = os.path.split(__file__)[0] @@ -115,13 +116,6 @@ class B2GBrowser(Browser): self.logger.debug("Device runner started") def setup_hosts(self): - hostnames = ["web-platform.test", - "www.web-platform.test", - "www1.web-platform.test", - "www2.web-platform.test", - "xn--n8j6ds53lwwkrqhv28a.web-platform.test", - "xn--lve-6lad.web-platform.test"] - host_ip = moznetwork.get_ip() temp_dir = tempfile.mkdtemp() diff --git a/testing/web-platform/harness/wptrunner/browsers/base.py b/testing/web-platform/harness/wptrunner/browsers/base.py index b7d3d0502b8..1d3b3d231c7 100644 --- a/testing/web-platform/harness/wptrunner/browsers/base.py +++ b/testing/web-platform/harness/wptrunner/browsers/base.py @@ -41,6 +41,18 @@ def get_free_port(start_port, exclude=None): finally: s.close() +def browser_command(binary, args, debug_info): + if debug_info: + if debug_info.requiresEscapedArgs: + args = [item.replace("&", "\\&") for item in args] + debug_args = [debug_info.path] + debug_info.args + else: + debug_args = [] + + command = [binary] + args + + return debug_args, command + class BrowserError(Exception): pass diff --git a/testing/web-platform/harness/wptrunner/browsers/firefox.py b/testing/web-platform/harness/wptrunner/browsers/firefox.py index ced31e724be..0f009494e92 100644 --- a/testing/web-platform/harness/wptrunner/browsers/firefox.py +++ b/testing/web-platform/harness/wptrunner/browsers/firefox.py @@ -12,9 +12,10 @@ from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozcrash import mozcrash -from .base import get_free_port, Browser, ExecutorBrowser, require_arg, cmd_arg +from .base import get_free_port, Browser, ExecutorBrowser, require_arg, cmd_arg, browser_command from ..executors import executor_kwargs as base_executor_kwargs from ..executors.executormarionette import MarionetteTestharnessExecutor, MarionetteRefTestExecutor +from ..environment import hostnames here = os.path.join(os.path.split(__file__)[0]) @@ -37,8 +38,7 @@ def check_args(**kwargs): def browser_kwargs(**kwargs): return {"binary": kwargs["binary"], "prefs_root": kwargs["prefs_root"], - "debug_args": kwargs["debug_args"], - "interactive": kwargs["interactive"], + "debug_info": kwargs["debug_info"], "symbols_path": kwargs["symbols_path"], "stackwalk_binary": kwargs["stackwalk_binary"], "certutil_binary": kwargs["certutil_binary"], @@ -57,13 +57,13 @@ def env_options(): "external_host": "web-platform.test", "bind_hostname": "false", "certificate_domain": "web-platform.test", - "encrypt_after_connect": True} + "supports_debugger": True} class FirefoxBrowser(Browser): used_ports = set() - def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None, + def __init__(self, logger, binary, prefs_root, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None): Browser.__init__(self, logger) @@ -72,8 +72,7 @@ class FirefoxBrowser(Browser): self.marionette_port = None self.used_ports.add(self.marionette_port) self.runner = None - self.debug_args = debug_args - self.interactive = interactive + self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary @@ -84,38 +83,35 @@ class FirefoxBrowser(Browser): self.marionette_port = get_free_port(2828, exclude=self.used_ports) env = os.environ.copy() - env["MOZ_CRASHREPORTER"] = "1" - env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" - env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" locations = ServerLocations(filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() - ports = {"http": "8000", - "https": "8443", - "ws": "8888"} - self.profile = FirefoxProfile(locations=locations, - proxy=ports, preferences=preferences) self.profile.set_preferences({"marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, - "dom.disable_open_during_load": False}) + "dom.disable_open_during_load": False, + "network.dns.localDomains": ",".join(hostnames)}) if self.ca_certificate_path is not None: self.setup_ssl() + debug_args, cmd = browser_command(self.binary, [cmd_arg("marionette"), "about:blank"], + self.debug_info) + self.runner = FirefoxRunner(profile=self.profile, - binary=self.binary, - cmdargs=[cmd_arg("marionette"), "about:blank"], + binary=cmd[0], + cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") - self.runner.start(debug_args=self.debug_args, interactive=self.interactive) + + self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): diff --git a/testing/web-platform/harness/wptrunner/browsers/servo.py b/testing/web-platform/harness/wptrunner/browsers/servo.py index 988cb2b5479..cbc21e607c7 100644 --- a/testing/web-platform/harness/wptrunner/browsers/servo.py +++ b/testing/web-platform/harness/wptrunner/browsers/servo.py @@ -26,7 +26,7 @@ def check_args(**kwargs): def browser_kwargs(**kwargs): return {"binary": kwargs["binary"], - "debug_args": kwargs["debug_args"], + "debug_info": kwargs["debug_info"], "interactive": kwargs["interactive"]} @@ -39,17 +39,18 @@ def executor_kwargs(test_type, server_config, cache_manager, **kwargs): def env_options(): return {"host": "localhost", "bind_hostname": "true", - "testharnessreport": "testharnessreport-servo.js"} + "testharnessreport": "testharnessreport-servo.js", + "supports_debugger": True} class ServoBrowser(NullBrowser): - def __init__(self, logger, binary, debug_args=None, interactive=False): + def __init__(self, logger, binary, debug_info=None, interactive=False): NullBrowser.__init__(self, logger) self.binary = binary - self.debug_args = debug_args + self.debug_info = debug_info self.interactive = interactive def executor_browser(self): return ExecutorBrowser, {"binary": self.binary, - "debug_args": self.debug_args, + "debug_info": self.debug_info, "interactive": self.interactive} diff --git a/testing/web-platform/harness/wptrunner/environment.py b/testing/web-platform/harness/wptrunner/environment.py index b650dc5b11c..5d0436cbf5a 100644 --- a/testing/web-platform/harness/wptrunner/environment.py +++ b/testing/web-platform/harness/wptrunner/environment.py @@ -5,6 +5,7 @@ import json import os import multiprocessing +import signal import socket import sys import time @@ -18,6 +19,15 @@ here = os.path.split(__file__)[0] serve = None sslutils = None + +hostnames = ["web-platform.test", + "www.web-platform.test", + "www1.web-platform.test", + "www2.web-platform.test", + "xn--n8j6ds53lwwkrqhv28a.web-platform.test", + "xn--lve-6lad.web-platform.test"] + + def do_delayed_imports(logger, test_paths): global serve, sslutils @@ -90,7 +100,7 @@ class StaticHandler(object): class TestEnvironment(object): - def __init__(self, test_paths, ssl_env, pause_after_test, options): + def __init__(self, test_paths, ssl_env, pause_after_test, debug_info, options): """Context manager that owns the test environment i.e. the http and websockets servers""" self.test_paths = test_paths @@ -100,11 +110,13 @@ class TestEnvironment(object): self.external_config = None self.pause_after_test = pause_after_test self.test_server_port = options.pop("test_server_port", True) + self.debug_info = debug_info self.options = options if options is not None else {} self.cache_manager = multiprocessing.Manager() self.routes = self.get_routes() + def __enter__(self): self.ssl_env.__enter__() self.cache_manager.__enter__() @@ -113,9 +125,12 @@ class TestEnvironment(object): serve.set_computed_defaults(self.config) self.external_config, self.servers = serve.start(self.config, self.ssl_env, self.routes) + if self.options.get("supports_debugger") and self.debug_info and self.debug_info.interactive: + self.ignore_interrupts() return self def __exit__(self, exc_type, exc_val, exc_tb): + self.process_interrupts() self.cache_manager.__exit__(exc_type, exc_val, exc_tb) self.ssl_env.__exit__(exc_type, exc_val, exc_tb) @@ -123,6 +138,12 @@ class TestEnvironment(object): for port, server in servers: server.kill() + def ignore_interrupts(self): + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def process_interrupts(self): + signal.signal(signal.SIGINT, signal.SIG_DFL) + def load_config(self): default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json") local_config_path = os.path.join(here, "config.json") diff --git a/testing/web-platform/harness/wptrunner/executors/base.py b/testing/web-platform/harness/wptrunner/executors/base.py index 50bba933087..4aff10f0a77 100644 --- a/testing/web-platform/harness/wptrunner/executors/base.py +++ b/testing/web-platform/harness/wptrunner/executors/base.py @@ -21,7 +21,7 @@ def executor_kwargs(test_type, server_config, cache_manager, **kwargs): executor_kwargs = {"server_config": server_config, "timeout_multiplier": timeout_multiplier, - "debug_args": kwargs["debug_args"]} + "debug_info": kwargs["debug_info"]} if test_type == "reftest": executor_kwargs["screenshot_cache"] = cache_manager.dict() @@ -81,7 +81,7 @@ class TestExecutor(object): convert_result = None def __init__(self, browser, server_config, timeout_multiplier=1, - debug_args=None): + debug_info=None): """Abstract Base class for object that actually executes the tests in a specific browser. Typically there will be a different TestExecutor subclass for each test type and method of executing tests. @@ -97,7 +97,7 @@ class TestExecutor(object): self.browser = browser self.server_config = server_config self.timeout_multiplier = timeout_multiplier - self.debug_args = debug_args + self.debug_info = debug_info self.last_environment = {"protocol": "http", "prefs": []} self.protocol = None # This must be set in subclasses @@ -153,7 +153,7 @@ class TestExecutor(object): @abstractmethod def do_test(self, test): - """Test-type and protocol specific implmentation of running a + """Test-type and protocol specific implementation of running a specific test. :param test: The test to run.""" @@ -182,10 +182,10 @@ class RefTestExecutor(TestExecutor): convert_result = reftest_result_converter def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None, - debug_args=None): + debug_info=None): TestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.screenshot_cache = screenshot_cache diff --git a/testing/web-platform/harness/wptrunner/executors/executormarionette.py b/testing/web-platform/harness/wptrunner/executors/executormarionette.py index 0382823c359..c48b5e623c5 100644 --- a/testing/web-platform/harness/wptrunner/executors/executormarionette.py +++ b/testing/web-platform/harness/wptrunner/executors/executormarionette.py @@ -62,7 +62,7 @@ class MarionetteProtocol(Protocol): while True: success = self.marionette.wait_for_port(60) #When running in a debugger wait indefinitely for firefox to start - if success or self.executor.debug_args is None: + if success or self.executor.debug_info is None: break session_started = False @@ -271,11 +271,11 @@ class MarionetteRun(object): class MarionetteTestharnessExecutor(TestharnessExecutor): def __init__(self, browser, server_config, timeout_multiplier=1, close_after_done=True, - debug_args=None): + debug_info=None): """Marionette-based executor for testharness.js tests""" TestharnessExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.protocol = MarionetteProtocol(self, browser) self.script = open(os.path.join(here, "testharness_marionette.js")).read() @@ -297,7 +297,7 @@ class MarionetteTestharnessExecutor(TestharnessExecutor): self.protocol.load_runner(new_environment["protocol"]) def do_test(self, test): - timeout = (test.timeout * self.timeout_multiplier if self.debug_args is None + timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None else None) success, data = MarionetteRun(self.logger, @@ -331,14 +331,14 @@ class MarionetteTestharnessExecutor(TestharnessExecutor): class MarionetteRefTestExecutor(RefTestExecutor): def __init__(self, browser, server_config, timeout_multiplier=1, - screenshot_cache=None, close_after_done=True, debug_args=None): + screenshot_cache=None, close_after_done=True, debug_info=None): """Marionette-based executor for reftests""" RefTestExecutor.__init__(self, browser, server_config, screenshot_cache=screenshot_cache, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.protocol = MarionetteProtocol(self, browser) self.implementation = RefTestImplementation(self) self.close_after_done = close_after_done @@ -373,7 +373,7 @@ class MarionetteRefTestExecutor(RefTestExecutor): return self.convert_result(test, result) def screenshot(self, test): - timeout = self.timeout_multiplier * test.timeout if self.debug_args is None else None + timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None test_url = self.test_url(test) diff --git a/testing/web-platform/harness/wptrunner/executors/executorselenium.py b/testing/web-platform/harness/wptrunner/executors/executorselenium.py index 4f26e4e2724..6298893503b 100644 --- a/testing/web-platform/harness/wptrunner/executors/executorselenium.py +++ b/testing/web-platform/harness/wptrunner/executors/executorselenium.py @@ -165,11 +165,11 @@ class SeleniumRun(object): class SeleniumTestharnessExecutor(TestharnessExecutor): def __init__(self, browser, server_config, timeout_multiplier=1, - close_after_done=True, capabilities=None, debug_args=None): + close_after_done=True, capabilities=None, debug_info=None): """Selenium-based executor for testharness.js tests""" TestharnessExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.protocol = SeleniumProtocol(self, browser, capabilities) with open(os.path.join(here, "testharness_webdriver.js")) as f: self.script = f.read() @@ -206,14 +206,14 @@ class SeleniumTestharnessExecutor(TestharnessExecutor): class SeleniumRefTestExecutor(RefTestExecutor): def __init__(self, browser, server_config, timeout_multiplier=1, screenshot_cache=None, close_after_done=True, - debug_args=None, capabilities=None): + debug_info=None, capabilities=None): """Selenium WebDriver-based executor for reftests""" RefTestExecutor.__init__(self, browser, server_config, screenshot_cache=screenshot_cache, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.protocol = SeleniumProtocol(self, browser, capabilities=capabilities) self.implementation = RefTestImplementation(self) diff --git a/testing/web-platform/harness/wptrunner/executors/executorservo.py b/testing/web-platform/harness/wptrunner/executors/executorservo.py index 37ba19f7a54..e7b2cd415bc 100644 --- a/testing/web-platform/harness/wptrunner/executors/executorservo.py +++ b/testing/web-platform/harness/wptrunner/executors/executorservo.py @@ -21,6 +21,7 @@ from .base import (ExecutorException, testharness_result_converter, reftest_result_converter) from .process import ProcessTestExecutor +from ..executors.base import browser_command hosts_text = """127.0.0.1 web-platform.test 127.0.0.1 www.web-platform.test @@ -39,11 +40,11 @@ def make_hosts_file(): class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter - def __init__(self, browser, server_config, timeout_multiplier=1, debug_args=None, + def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None @@ -61,13 +62,15 @@ class ServoTestharnessExecutor(ProcessTestExecutor): self.result_data = None self.result_flag = threading.Event() - self.command = [self.binary, "--cpu", "--hard-fail", "-z", self.test_url(test)] + debug_args, command = browser_command(self.binary, ["--cpu", "--hard-fail", "-z", self.test_url(test)], + self.debug_info) + + self.command = command if self.pause_after_test: self.command.remove("-z") - if self.debug_args: - self.command = list(self.debug_args) + self.command + self.command = debug_args + self.command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path @@ -83,7 +86,7 @@ class ServoTestharnessExecutor(ProcessTestExecutor): timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout - if self.debug_args is None and not self.pause_after_test: + if self.debug_info is None and not self.pause_after_test: wait_timeout = timeout + 5 else: wait_timeout = None @@ -150,13 +153,13 @@ class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, browser, server_config, binary=None, timeout_multiplier=1, - screenshot_cache=None, debug_args=None, pause_after_test=False): + screenshot_cache=None, debug_info=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, - debug_args=debug_args) + debug_info=debug_info) self.protocol = Protocol(self, browser) self.screenshot_cache = screenshot_cache diff --git a/testing/web-platform/harness/wptrunner/manifestinclude.py b/testing/web-platform/harness/wptrunner/manifestinclude.py index 5f49c18d6a3..8a11605169c 100644 --- a/testing/web-platform/harness/wptrunner/manifestinclude.py +++ b/testing/web-platform/harness/wptrunner/manifestinclude.py @@ -79,7 +79,7 @@ class IncludeManifest(ManifestItem): return rv def _add_rule(self, test_manifests, url, direction): - maybe_path = os.path.abspath(os.path.join(os.curdir, url)[1:]) + maybe_path = os.path.join(os.path.abspath(os.curdir), url) rest, last = os.path.split(maybe_path) variant = "" if "#" in last: diff --git a/testing/web-platform/harness/wptrunner/testloader.py b/testing/web-platform/harness/wptrunner/testloader.py index d48e25001fd..9270bec2cc1 100644 --- a/testing/web-platform/harness/wptrunner/testloader.py +++ b/testing/web-platform/harness/wptrunner/testloader.py @@ -190,8 +190,6 @@ class EqualTimeChunker(TestChunker): class TestFilter(object): def __init__(self, test_manifests, include=None, exclude=None, manifest_path=None): - test_manifests = test_manifests - if manifest_path is not None and include is None: self.manifest = manifestinclude.get_manifest(manifest_path) else: diff --git a/testing/web-platform/harness/wptrunner/testrunner.py b/testing/web-platform/harness/wptrunner/testrunner.py index 95095906b38..063fa7d159e 100644 --- a/testing/web-platform/harness/wptrunner/testrunner.py +++ b/testing/web-platform/harness/wptrunner/testrunner.py @@ -168,7 +168,7 @@ class TestRunnerManager(threading.Thread): def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs, executor_cls, executor_kwargs, stop_flag, pause_after_test=False, - pause_on_unexpected=False, debug_args=None): + pause_on_unexpected=False, debug_info=None): """Thread that owns a single TestRunner process and any processes required by the TestRunner (e.g. the Firefox binary). @@ -206,7 +206,7 @@ class TestRunnerManager(threading.Thread): self.pause_after_test = pause_after_test self.pause_on_unexpected = pause_on_unexpected - self.debug_args = debug_args + self.debug_info = debug_info self.manager_number = next_manager_number() @@ -333,7 +333,7 @@ class TestRunnerManager(threading.Thread): with self.init_lock: # Guard against problems initialising the browser or the browser # remote control method - if self.debug_args is None: + if self.debug_info is None: self.init_timer = threading.Timer(self.browser.init_timeout, init_failed) test_queue = self.test_source.get_queue() @@ -560,7 +560,6 @@ class TestQueue(object): self.test_type = test_type self.tests = tests self.kwargs = kwargs - self.queue = None def __enter__(self): if not self.tests[self.test_type]: @@ -590,7 +589,7 @@ class ManagerGroup(object): executor_cls, executor_kwargs, pause_after_test=False, pause_on_unexpected=False, - debug_args=None): + debug_info=None): """Main thread object that owns all the TestManager threads.""" self.suite_name = suite_name self.size = size @@ -602,7 +601,7 @@ class ManagerGroup(object): self.executor_kwargs = executor_kwargs self.pause_after_test = pause_after_test self.pause_on_unexpected = pause_on_unexpected - self.debug_args = debug_args + self.debug_info = debug_info self.pool = set() # Event that is polled by threads so that they can gracefully exit in the face @@ -640,7 +639,7 @@ class ManagerGroup(object): self.stop_flag, self.pause_after_test, self.pause_on_unexpected, - self.debug_args) + self.debug_info) manager.start() self.pool.add(manager) self.wait() diff --git a/testing/web-platform/harness/wptrunner/wptcommandline.py b/testing/web-platform/harness/wptrunner/wptcommandline.py index 1622f87db99..18a5367434d 100644 --- a/testing/web-platform/harness/wptrunner/wptcommandline.py +++ b/testing/web-platform/harness/wptrunner/wptcommandline.py @@ -25,12 +25,6 @@ def url_or_path(path): else: return abs_path(path) -def slash_prefixed(url): - if not url.startswith("/"): - url = "/" + url - return url - - def require_arg(kwargs, name, value_func=None): if value_func is None: value_func = lambda x: x is not None @@ -97,15 +91,15 @@ def create_parser(product_choices=None): nargs="*", default=["testharness", "reftest"], choices=["testharness", "reftest"], help="Test types to run") - test_selection_group.add_argument("--include", action="append", type=slash_prefixed, + test_selection_group.add_argument("--include", action="append", help="URL prefix to include") - test_selection_group.add_argument("--exclude", action="append", type=slash_prefixed, + test_selection_group.add_argument("--exclude", action="append", help="URL prefix to exclude") test_selection_group.add_argument("--include-manifest", type=abs_path, help="Path to manifest listing tests to include") debugging_group = parser.add_argument_group("Debugging") - debugging_group.add_argument('--debugger', + debugging_group.add_argument('--debugger', const="__default__", nargs="?", help="run under a debugger, e.g. gdb or valgrind") debugging_group.add_argument('--debugger-args', help="arguments to the debugger") @@ -233,8 +227,6 @@ def exe_path(name): def check_args(kwargs): - from mozrunner import debugger_arguments - set_from_config(kwargs) for test_paths in kwargs["test_paths"].itervalues(): @@ -278,16 +270,17 @@ def check_args(kwargs): kwargs["processes"] = 1 if kwargs["debugger"] is not None: - debug_args, interactive = debugger_arguments(kwargs["debugger"], - kwargs["debugger_args"]) - if interactive: + import mozdebug + if kwargs["debugger"] == "__default__": + kwargs["debugger"] = mozdebug.get_default_debugger_name() + debug_info = mozdebug.get_debugger_info(kwargs["debugger"], + kwargs["debugger_args"]) + if debug_info.interactive: require_arg(kwargs, "processes", lambda x: x == 1) kwargs["no_capture_stdio"] = True - kwargs["interactive"] = interactive - kwargs["debug_args"] = debug_args + kwargs["debug_info"] = debug_info else: - kwargs["interactive"] = False - kwargs["debug_args"] = None + kwargs["debug_info"] = None if kwargs["binary"] is not None: if not os.path.exists(kwargs["binary"]): diff --git a/testing/web-platform/harness/wptrunner/wptrunner.py b/testing/web-platform/harness/wptrunner/wptrunner.py index cef210ee0b3..075f07cdc0d 100644 --- a/testing/web-platform/harness/wptrunner/wptrunner.py +++ b/testing/web-platform/harness/wptrunner/wptrunner.py @@ -134,6 +134,7 @@ def run_tests(config, test_paths, product, **kwargs): with env.TestEnvironment(test_paths, ssl_env, kwargs["pause_after_test"], + kwargs["debug_info"], env_options) as test_environment: try: test_environment.ensure_started() @@ -180,7 +181,7 @@ def run_tests(config, test_paths, product, **kwargs): executor_kwargs, kwargs["pause_after_test"], kwargs["pause_on_unexpected"], - kwargs["debug_args"]) as manager_group: + kwargs["debug_info"]) as manager_group: try: manager_group.run(test_type, test_loader.tests) except KeyboardInterrupt: From 671fd9d1f39dfd2888028900916a19c5fc80f740 Mon Sep 17 00:00:00 2001 From: James Graham Date: Wed, 15 Apr 2015 19:59:04 +0100 Subject: [PATCH 22/43] Bug 1155079 -Update web-platform-tests expected data to revision 89b6e2bc460316c7f273712d22f0b2d3a3d0c5be, a=testonly --- .../meta/XMLHttpRequest/send-usp.html.ini | 5 + .../XMLHttpRequest/send-usp.worker.js.ini | 3 + .../meta/html/dom/reflection-misc.html.ini | 114 ++++++++++++++++++ .../test_navigation_type_reload.html.ini | 4 + .../animation-timeline/idlharness.html.ini | 1 + .../EventTarget.worker.js.ini | 1 + 6 files changed, 128 insertions(+) create mode 100644 testing/web-platform/meta/XMLHttpRequest/send-usp.html.ini create mode 100644 testing/web-platform/meta/XMLHttpRequest/send-usp.worker.js.ini create mode 100644 testing/web-platform/meta/navigation-timing/test_navigation_type_reload.html.ini diff --git a/testing/web-platform/meta/XMLHttpRequest/send-usp.html.ini b/testing/web-platform/meta/XMLHttpRequest/send-usp.html.ini new file mode 100644 index 00000000000..b6b1b0a98d3 --- /dev/null +++ b/testing/web-platform/meta/XMLHttpRequest/send-usp.html.ini @@ -0,0 +1,5 @@ +[send-usp.html] + type: testharness + [XMLHttpRequest.send(URLSearchParams) (0)] + expected: FAIL + diff --git a/testing/web-platform/meta/XMLHttpRequest/send-usp.worker.js.ini b/testing/web-platform/meta/XMLHttpRequest/send-usp.worker.js.ini new file mode 100644 index 00000000000..975ae20841e --- /dev/null +++ b/testing/web-platform/meta/XMLHttpRequest/send-usp.worker.js.ini @@ -0,0 +1,3 @@ +[send-usp.worker] + type: testharness + expected: ERROR diff --git a/testing/web-platform/meta/html/dom/reflection-misc.html.ini b/testing/web-platform/meta/html/dom/reflection-misc.html.ini index 46f4a949f69..e46dd1e303a 100644 --- a/testing/web-platform/meta/html/dom/reflection-misc.html.ini +++ b/testing/web-platform/meta/html/dom/reflection-misc.html.ini @@ -666,3 +666,117 @@ [undefinedelement.tabIndex: IDL set to -2147483648 followed by IDL get] expected: FAIL + [dialog.tabIndex: setAttribute() to -2147483648 followed by IDL get] + expected: FAIL + + [dialog.tabIndex: IDL set to -2147483648 followed by IDL get] + expected: FAIL + + [dialog.open: typeof IDL attribute] + expected: FAIL + + [dialog.open: IDL get with DOM attribute unset] + expected: FAIL + + [dialog.open: setAttribute() to "" followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to " foo " followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to undefined followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to null followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to 7 followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to 1.5 followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to true followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to false followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to object "[object Object\]" followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to NaN followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to Infinity followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to -Infinity followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to "\\0" followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to object "test-toString" followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to object "test-valueOf" followed by IDL get] + expected: FAIL + + [dialog.open: setAttribute() to "open" followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to "" followed by hasAttribute()] + expected: FAIL + + [dialog.open: IDL set to "" followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to " foo " followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to undefined followed by hasAttribute()] + expected: FAIL + + [dialog.open: IDL set to undefined followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to null followed by hasAttribute()] + expected: FAIL + + [dialog.open: IDL set to null followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to 7 followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to 1.5 followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to false followed by hasAttribute()] + expected: FAIL + + [dialog.open: IDL set to object "[object Object\]" followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to NaN followed by hasAttribute()] + expected: FAIL + + [dialog.open: IDL set to NaN followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to Infinity followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to -Infinity followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to "\\0" followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to object "test-toString" followed by IDL get] + expected: FAIL + + [dialog.open: IDL set to object "test-valueOf" followed by IDL get] + expected: FAIL + diff --git a/testing/web-platform/meta/navigation-timing/test_navigation_type_reload.html.ini b/testing/web-platform/meta/navigation-timing/test_navigation_type_reload.html.ini new file mode 100644 index 00000000000..5fc794734bc --- /dev/null +++ b/testing/web-platform/meta/navigation-timing/test_navigation_type_reload.html.ini @@ -0,0 +1,4 @@ +[test_navigation_type_reload.html] + type: testharness + expected: + if not debug and (os == "mac") and (version == "OS X 10.8") and (processor == "x86_64") and (bits == 64): TIMEOUT diff --git a/testing/web-platform/meta/web-animations/animation-timeline/idlharness.html.ini b/testing/web-platform/meta/web-animations/animation-timeline/idlharness.html.ini index 3091878efa7..61cc3303dd2 100644 --- a/testing/web-platform/meta/web-animations/animation-timeline/idlharness.html.ini +++ b/testing/web-platform/meta/web-animations/animation-timeline/idlharness.html.ini @@ -5,3 +5,4 @@ [Stringification of document.timeline] expected: FAIL + diff --git a/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js.ini b/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js.ini index fc8a5d52bc8..eb38f319e05 100644 --- a/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js.ini +++ b/testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js.ini @@ -1,5 +1,6 @@ [EventTarget.worker] type: testharness + expected: ERROR [removeEventListener] expected: FAIL From 8a4db48565f0d2382eb4e0b05ce36e4dff3d9e60 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Thu, 16 Apr 2015 09:04:19 +0000 Subject: [PATCH 23/43] Bug 1153478 part 1 - Add nsInlineFrame::StealFrame and make it deal with being called on the wrong parent for aChild (due to lazy reparenting). r=roc --- layout/generic/nsContainerFrame.cpp | 7 +++--- layout/generic/nsInlineFrame.cpp | 39 +++++++++++++++++++++++++++++ layout/generic/nsInlineFrame.h | 1 + 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index d455cd219e7..20b1b7a2739 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -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; diff --git a/layout/generic/nsInlineFrame.cpp b/layout/generic/nsInlineFrame.cpp index 3fe53097a3c..f9ea77c464b 100644 --- a/layout/generic/nsInlineFrame.cpp +++ b/layout/generic/nsInlineFrame.cpp @@ -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(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, diff --git a/layout/generic/nsInlineFrame.h b/layout/generic/nsInlineFrame.h index f58fdc49ffa..7c46666c428 100644 --- a/layout/generic/nsInlineFrame.h +++ b/layout/generic/nsInlineFrame.h @@ -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, From 8e82f87fcb78068d0841a41e2aa43d3d4df999cc Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Thu, 16 Apr 2015 09:04:19 +0000 Subject: [PATCH 24/43] Bug 1153478 part 2 - Remove useless assertions. r=roc --- layout/generic/nsContainerFrame.cpp | 1 - layout/generic/nsInlineFrame.cpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp index 20b1b7a2739..912f05b9460 100644 --- a/layout/generic/nsContainerFrame.cpp +++ b/layout/generic/nsContainerFrame.cpp @@ -1620,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; } diff --git a/layout/generic/nsInlineFrame.cpp b/layout/generic/nsInlineFrame.cpp index f9ea77c464b..c77122e8cbd 100644 --- a/layout/generic/nsInlineFrame.cpp +++ b/layout/generic/nsInlineFrame.cpp @@ -508,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. @@ -1200,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); From c0daab199ac8294e6ad28e82f9d5f69693550996 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Thu, 16 Apr 2015 11:08:03 +0100 Subject: [PATCH 25/43] Bug 1155064 - Add localization comments to aboutServiceWorkers.properties, r=flod --- .../locales/en-US/chrome/global/aboutServiceWorkers.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolkit/locales/en-US/chrome/global/aboutServiceWorkers.properties b/toolkit/locales/en-US/chrome/global/aboutServiceWorkers.properties index 29d40edc2ab..4b8998de8df 100644 --- a/toolkit/locales/en-US/chrome/global/aboutServiceWorkers.properties +++ b/toolkit/locales/en-US/chrome/global/aboutServiceWorkers.properties @@ -22,11 +22,12 @@ true = true false = false +# LOCALIZATION NOTE this term is used as a button label (verb, not noun). update = Update unregister = Unregister -waiting = Waiting... +waiting = Waiting… # LOCALIZATION NODE the term "Service Worker" should not translated. unregisterError = Failed to unregister this Service Worker. From ceb18d3d018b374f181851b4cd915ed0f02c360f Mon Sep 17 00:00:00 2001 From: Andreas Tolfsen Date: Wed, 15 Apr 2015 13:38:01 +0100 Subject: [PATCH 26/43] Bug 1154691: Align Marionette with WebDriver errors Adds `invalid selector' and `invalid session id' errors to the server, and aligns the exceptions in the Python client with those in the server. Some of the exceptions are not in use yet and consequently do not carry a `code` property. This is fine because it makes us future-proof when the server starts using them. r=dburns --- .../client/marionette/b2g_update_test.py | 18 ++-- .../client/marionette/marionette_test.py | 4 +- .../driver/marionette_driver/errors.py | 86 +++++++++++-------- testing/marionette/error.js | 20 +++++ 4 files changed, 77 insertions(+), 51 deletions(-) diff --git a/testing/marionette/client/marionette/b2g_update_test.py b/testing/marionette/client/marionette/b2g_update_test.py index 5c832922e46..3f7c8cfe132 100644 --- a/testing/marionette/client/marionette/b2g_update_test.py +++ b/testing/marionette/client/marionette/b2g_update_test.py @@ -11,11 +11,11 @@ import time import types import weakref -from b2ginstance import B2GInstance -from marionette_driver.errors import InvalidResponseException from marionette_driver.marionette import Marionette from marionette_test import MarionetteTestCase from marionette_transport import MarionetteTransport + +from b2ginstance import B2GInstance from runtests import MarionetteTestRunner, cli class B2GUpdateMarionetteClient(MarionetteTransport): @@ -237,16 +237,10 @@ class B2GUpdateTestCase(MarionetteTestCase): self.print_status(status, os.path.basename(path)) - try: - results = self.marionette.execute_async_script(data, - script_args=[self.testvars], - special_powers=True) - self.handle_results(path, stage, results) - except InvalidResponseException, e: - # If the update test causes a restart, we will get an invalid - # response from the socket here. - if not will_restart: - raise e + results = self.marionette.execute_async_script(data, + script_args=[self.testvars], + special_powers=True) + self.handle_results(path, stage, results) def handle_results(self, path, stage, results): passed = results['passed'] diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/client/marionette/marionette_test.py index a1c56370d4d..bf6ed1f5f2a 100644 --- a/testing/marionette/client/marionette/marionette_test.py +++ b/testing/marionette/client/marionette/marionette_test.py @@ -16,8 +16,8 @@ import warnings from marionette_driver.errors import ( - MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException, - JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException, + MarionetteException, TimeoutException, + JavascriptException, NoSuchElementException, NoSuchWindowException, StaleElementException, ScriptTimeoutException, ElementNotVisibleException, NoSuchFrameException, InvalidElementStateException, NoAlertPresentException, InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException, diff --git a/testing/marionette/driver/marionette_driver/errors.py b/testing/marionette/driver/marionette_driver/errors.py index 34473cf7d44..d8256dca927 100644 --- a/testing/marionette/driver/marionette_driver/errors.py +++ b/testing/marionette/driver/marionette_driver/errors.py @@ -49,8 +49,16 @@ class MarionetteException(Exception): return "".join(traceback.format_exception(self.__class__, msg, tb)) -class InstallGeckoError(MarionetteException): - pass +class ElementNotSelectableException(MarionetteException): + status = "element not selectable" + + +class InvalidArgumentException(MarionetteException): + status = "invalid argument" + + +class InvalidSessionIdException(MarionetteException): + status = "invalid session id" class TimeoutException(MarionetteException): @@ -58,11 +66,6 @@ class TimeoutException(MarionetteException): status = "timeout" -class InvalidResponseException(MarionetteException): - code = (53,) - status = "invalid response" - - class JavascriptException(MarionetteException): code = (17,) status = "javascript error" @@ -73,11 +76,6 @@ class NoSuchElementException(MarionetteException): status = "no such element" -class XPathLookupException(MarionetteException): - code = (19,) - status = "invalid xpath selector" - - class NoSuchWindowException(MarionetteException): code = (23,) status = "no such window" @@ -159,11 +157,6 @@ class FrameSendFailureError(MarionetteException): status = "frame send failure" -class UnsupportedOperationException(MarionetteException): - code = (405,) - status = "unsupported operation" - - class SessionNotCreatedException(MarionetteException): code = (33, 71) status = "session not created" @@ -173,31 +166,50 @@ class UnexpectedAlertOpen(MarionetteException): code = (26,) status = "unexpected alert open" + +class UnknownCommandException(MarionetteException): + code = (9,) + status = "unknown command" + + +class UnknownException(MarionetteException): + code = (13,) + status = "unknown error" + + +class UnsupportedOperationException(MarionetteException): + code = (405,) + status = "unsupported operation" + + excs = [ - MarionetteException, - TimeoutException, - InvalidResponseException, - JavascriptException, - NoSuchElementException, - XPathLookupException, - NoSuchWindowException, - StaleElementException, - ScriptTimeoutException, - ElementNotVisibleException, ElementNotAccessibleException, - NoSuchFrameException, - InvalidElementStateException, - NoAlertPresentException, - InvalidCookieDomainException, - UnableToSetCookieException, - InvalidElementCoordinates, - InvalidSelectorException, - MoveTargetOutOfBoundsException, - FrameSendNotInitializedError, + ElementNotSelectableException, + ElementNotVisibleException, FrameSendFailureError, - UnsupportedOperationException, + FrameSendNotInitializedError, + InvalidArgumentException, + InvalidCookieDomainException, + InvalidElementCoordinates, + InvalidElementStateException, + InvalidSelectorException, + InvalidSessionIdException, + JavascriptException, + MarionetteException, + MoveTargetOutOfBoundsException, + NoAlertPresentException, + NoSuchElementException, + NoSuchFrameException, + NoSuchWindowException, + ScriptTimeoutException, SessionNotCreatedException, + StaleElementException, + TimeoutException, + UnableToSetCookieException, UnexpectedAlertOpen, + UnknownCommandException, + UnknownException, + UnsupportedOperationException, ] diff --git a/testing/marionette/error.js b/testing/marionette/error.js index 9f664fa3726..cddb80a8104 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -12,6 +12,8 @@ const errors = [ "FrameSendNotInitializedError", "IllegalArgumentError", "InvalidElementStateError", + "InvalidSelectorError", + "InvalidSessionIdError", "JavaScriptError", "NoAlertOpenError", "NoSuchElementError", @@ -176,6 +178,22 @@ this.InvalidElementStateError = function(msg) { }; InvalidElementStateError.prototype = Object.create(WebDriverError.prototype); +this.InvalidSelectorError = function(msg) { + WebDriverError.call(this, msg); + this.name = "InvalidSelectorError"; + this.status = "invalid selector"; + this.code = 32; +}; +InvalidSelectorError.prototype = Object.create(WebDriverError.prototype); + +this.InvalidSessionIdError = function(msg) { + WebDriverError.call(this, msg); + this.name = "InvalidSessionIdError"; + this.status = "invalid session id"; + this.code = 13; +}; +InvalidSessionIdError.prototype = Object.create(WebDriverError.prototype); + /** * Creates an error message for a JavaScript error thrown during * executeScript or executeAsyncScript. @@ -311,6 +329,8 @@ const errorObjs = [ this.FrameSendNotInitializedError, this.IllegalArgumentError, this.InvalidElementStateError, + this.InvalidSelectorError, + this.InvalidSessionIdError, this.JavaScriptError, this.NoAlertOpenError, this.NoSuchElementError, From eecf391129d35ede3cbd22573dbfeb95f94113f0 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Thu, 16 Apr 2015 12:25:42 +0100 Subject: [PATCH 27/43] Bug 1152169 - DataStoreService should check if the first revision exists, r=bent --- dom/datastore/DataStoreCursorImpl.jsm | 6 ++ dom/datastore/DataStoreService.cpp | 117 +++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/dom/datastore/DataStoreCursorImpl.jsm b/dom/datastore/DataStoreCursorImpl.jsm index 9b70901129d..0efa74f44d9 100644 --- a/dom/datastore/DataStoreCursorImpl.jsm +++ b/dom/datastore/DataStoreCursorImpl.jsm @@ -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; diff --git a/dom/datastore/DataStoreService.cpp b/dom/datastore/DataStoreService.cpp index e7ffc96246c..9d275fa620e 100644 --- a/dom/datastore/DataStoreService.cpp +++ b/dom/datastore/DataStoreService.cpp @@ -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 service = DataStoreService::Get(); - MOZ_ASSERT(service); + ErrorResult error; - nsresult rv = service->EnableDataStore(mAppId, mName, mManifestURL); + if (aStatus == Success) { + mTxn = aDb->Transaction(); + + nsRefPtr 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 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 request; + request.swap(mRequest); + + nsRefPtr 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 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 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 mRequest; + nsRefPtr 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'. From c710827c1600c73566bfe3c514fb519ee069f10b Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 15 Apr 2015 14:21:03 -0400 Subject: [PATCH 28/43] Bug 1154831 - Remove the gcc-4.6-warning-silencing code in ErrorResult::ErrorResult; r=bzbarsky --- dom/bindings/ErrorResult.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h index 0fd98d8b707..a469b8de1f0 100644 --- a/dom/bindings/ErrorResult.h +++ b/dom/bindings/ErrorResult.h @@ -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 From d0876f309d8c78d358e0fe1e085ec85cc6d1ccc5 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Thu, 16 Apr 2015 13:36:33 +0200 Subject: [PATCH 29/43] Bug 1155092 - Disable the test browser_PopupNotification on Linux asan. r=roc --- browser/base/content/test/popupNotifications/browser.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/browser/base/content/test/popupNotifications/browser.ini b/browser/base/content/test/popupNotifications/browser.ini index dfef1ee2e0f..50281408d98 100644 --- a/browser/base/content/test/popupNotifications/browser.ini +++ b/browser/base/content/test/popupNotifications/browser.ini @@ -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 From f2866652e08c35d27f7f21a87ea84896d1d93f94 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Thu, 12 Mar 2015 14:01:23 +0100 Subject: [PATCH 30/43] Bug 994541 - Enable BasicCompositor OMTC on linux. r=Bas --- gfx/thebes/gfxPlatform.cpp | 6 ------ modules/libpref/init/all.js | 16 +--------------- toolkit/xre/nsAppRunner.cpp | 20 ++------------------ 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 8218fb114b7..1d3baf4e3b1 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -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; } diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 2f7347ec40f..2690f42fd02 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4013,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) @@ -4025,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); diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 827806b2bce..6e4e03fd9dd 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -3610,25 +3610,9 @@ XREMain::XRE_mainStartup(bool* aExitFlag) } #endif /* MOZ_WIDGET_GTK */ #ifdef MOZ_X11 - // Init X11 in thread-safe mode. Must be called prior to the first call to XOpenDisplay + // Init X11 in thread-safe mode. Must be called prior to the first call to XOpenDisplay // (called inside gdk_display_open). This is a requirement for off main tread compositing. - // This is done only on X11 platforms if the environment variable MOZ_USE_OMTC is set so - // as to avoid overhead when omtc is not used. - // - // On nightly builds, we call this by default to enable OMTC for Electrolysis testing. On - // aurora, beta, and release builds, there is a small tpaint regression from enabling this - // call, so it sits behind an environment variable. - // - // An environment variable is used instead of a pref on X11 platforms because we start having - // access to prefs long after the first call to XOpenDisplay which is hard to change due to - // interdependencies in the initialization. -# ifndef NIGHTLY_BUILD - if (PR_GetEnv("MOZ_USE_OMTC") || - PR_GetEnv("MOZ_OMTC_ENABLED")) -# endif - { - XInitThreads(); - } + XInitThreads(); #endif #if defined(MOZ_WIDGET_GTK) { From a39954a55114edc8b8f2382274efe534920362bb Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Thu, 16 Apr 2015 13:47:51 +0200 Subject: [PATCH 31/43] Backed out changeset cc8eb386f147 (bug 1154691) for b2g test bustage on a CLOSED TREE --- .../client/marionette/b2g_update_test.py | 18 ++-- .../client/marionette/marionette_test.py | 4 +- .../driver/marionette_driver/errors.py | 86 ++++++++----------- testing/marionette/error.js | 20 ----- 4 files changed, 51 insertions(+), 77 deletions(-) diff --git a/testing/marionette/client/marionette/b2g_update_test.py b/testing/marionette/client/marionette/b2g_update_test.py index 3f7c8cfe132..5c832922e46 100644 --- a/testing/marionette/client/marionette/b2g_update_test.py +++ b/testing/marionette/client/marionette/b2g_update_test.py @@ -11,11 +11,11 @@ import time import types import weakref +from b2ginstance import B2GInstance +from marionette_driver.errors import InvalidResponseException from marionette_driver.marionette import Marionette from marionette_test import MarionetteTestCase from marionette_transport import MarionetteTransport - -from b2ginstance import B2GInstance from runtests import MarionetteTestRunner, cli class B2GUpdateMarionetteClient(MarionetteTransport): @@ -237,10 +237,16 @@ class B2GUpdateTestCase(MarionetteTestCase): self.print_status(status, os.path.basename(path)) - results = self.marionette.execute_async_script(data, - script_args=[self.testvars], - special_powers=True) - self.handle_results(path, stage, results) + try: + results = self.marionette.execute_async_script(data, + script_args=[self.testvars], + special_powers=True) + self.handle_results(path, stage, results) + except InvalidResponseException, e: + # If the update test causes a restart, we will get an invalid + # response from the socket here. + if not will_restart: + raise e def handle_results(self, path, stage, results): passed = results['passed'] diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/client/marionette/marionette_test.py index bf6ed1f5f2a..a1c56370d4d 100644 --- a/testing/marionette/client/marionette/marionette_test.py +++ b/testing/marionette/client/marionette/marionette_test.py @@ -16,8 +16,8 @@ import warnings from marionette_driver.errors import ( - MarionetteException, TimeoutException, - JavascriptException, NoSuchElementException, NoSuchWindowException, + MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException, + JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException, StaleElementException, ScriptTimeoutException, ElementNotVisibleException, NoSuchFrameException, InvalidElementStateException, NoAlertPresentException, InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException, diff --git a/testing/marionette/driver/marionette_driver/errors.py b/testing/marionette/driver/marionette_driver/errors.py index d8256dca927..34473cf7d44 100644 --- a/testing/marionette/driver/marionette_driver/errors.py +++ b/testing/marionette/driver/marionette_driver/errors.py @@ -49,16 +49,8 @@ class MarionetteException(Exception): return "".join(traceback.format_exception(self.__class__, msg, tb)) -class ElementNotSelectableException(MarionetteException): - status = "element not selectable" - - -class InvalidArgumentException(MarionetteException): - status = "invalid argument" - - -class InvalidSessionIdException(MarionetteException): - status = "invalid session id" +class InstallGeckoError(MarionetteException): + pass class TimeoutException(MarionetteException): @@ -66,6 +58,11 @@ class TimeoutException(MarionetteException): status = "timeout" +class InvalidResponseException(MarionetteException): + code = (53,) + status = "invalid response" + + class JavascriptException(MarionetteException): code = (17,) status = "javascript error" @@ -76,6 +73,11 @@ class NoSuchElementException(MarionetteException): status = "no such element" +class XPathLookupException(MarionetteException): + code = (19,) + status = "invalid xpath selector" + + class NoSuchWindowException(MarionetteException): code = (23,) status = "no such window" @@ -157,6 +159,11 @@ class FrameSendFailureError(MarionetteException): status = "frame send failure" +class UnsupportedOperationException(MarionetteException): + code = (405,) + status = "unsupported operation" + + class SessionNotCreatedException(MarionetteException): code = (33, 71) status = "session not created" @@ -166,50 +173,31 @@ class UnexpectedAlertOpen(MarionetteException): code = (26,) status = "unexpected alert open" - -class UnknownCommandException(MarionetteException): - code = (9,) - status = "unknown command" - - -class UnknownException(MarionetteException): - code = (13,) - status = "unknown error" - - -class UnsupportedOperationException(MarionetteException): - code = (405,) - status = "unsupported operation" - - excs = [ - ElementNotAccessibleException, - ElementNotSelectableException, - ElementNotVisibleException, - FrameSendFailureError, - FrameSendNotInitializedError, - InvalidArgumentException, - InvalidCookieDomainException, - InvalidElementCoordinates, - InvalidElementStateException, - InvalidSelectorException, - InvalidSessionIdException, - JavascriptException, MarionetteException, - MoveTargetOutOfBoundsException, - NoAlertPresentException, - NoSuchElementException, - NoSuchFrameException, - NoSuchWindowException, - ScriptTimeoutException, - SessionNotCreatedException, - StaleElementException, TimeoutException, + InvalidResponseException, + JavascriptException, + NoSuchElementException, + XPathLookupException, + NoSuchWindowException, + StaleElementException, + ScriptTimeoutException, + ElementNotVisibleException, + ElementNotAccessibleException, + NoSuchFrameException, + InvalidElementStateException, + NoAlertPresentException, + InvalidCookieDomainException, UnableToSetCookieException, - UnexpectedAlertOpen, - UnknownCommandException, - UnknownException, + InvalidElementCoordinates, + InvalidSelectorException, + MoveTargetOutOfBoundsException, + FrameSendNotInitializedError, + FrameSendFailureError, UnsupportedOperationException, + SessionNotCreatedException, + UnexpectedAlertOpen, ] diff --git a/testing/marionette/error.js b/testing/marionette/error.js index cddb80a8104..9f664fa3726 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -12,8 +12,6 @@ const errors = [ "FrameSendNotInitializedError", "IllegalArgumentError", "InvalidElementStateError", - "InvalidSelectorError", - "InvalidSessionIdError", "JavaScriptError", "NoAlertOpenError", "NoSuchElementError", @@ -178,22 +176,6 @@ this.InvalidElementStateError = function(msg) { }; InvalidElementStateError.prototype = Object.create(WebDriverError.prototype); -this.InvalidSelectorError = function(msg) { - WebDriverError.call(this, msg); - this.name = "InvalidSelectorError"; - this.status = "invalid selector"; - this.code = 32; -}; -InvalidSelectorError.prototype = Object.create(WebDriverError.prototype); - -this.InvalidSessionIdError = function(msg) { - WebDriverError.call(this, msg); - this.name = "InvalidSessionIdError"; - this.status = "invalid session id"; - this.code = 13; -}; -InvalidSessionIdError.prototype = Object.create(WebDriverError.prototype); - /** * Creates an error message for a JavaScript error thrown during * executeScript or executeAsyncScript. @@ -329,8 +311,6 @@ const errorObjs = [ this.FrameSendNotInitializedError, this.IllegalArgumentError, this.InvalidElementStateError, - this.InvalidSelectorError, - this.InvalidSessionIdError, this.JavaScriptError, this.NoAlertOpenError, this.NoSuchElementError, From cdfde43ff611311808280194d53fcc3ccf24d9c1 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Fri, 27 Feb 2015 18:22:05 +0100 Subject: [PATCH 32/43] Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan - Relevant spec text: - http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise - http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise - http://webaudio.github.io/web-audio-api/#widl-AudioContext-close-Promise - http://webaudio.github.io/web-audio-api/#widl-AudioContext-state - http://webaudio.github.io/web-audio-api/#widl-AudioContext-onstatechange - In a couple words, the behavior we want: - Closed context cannot have new nodes created, but can do decodeAudioData, and create buffers, and such. - OfflineAudioContexts don't support those methods, transitions happen at startRendering and at the end of processing. onstatechange is used to make this observable. - (regular) AudioContexts support those methods. The promises and onstatechange should be resolved/called when the operation has actually completed on the rendering thread. Once a context has been closed, it cannot transition back to "running". An AudioContext switches to "running" when the audio callback start running, this allow authors to know how long the audio stack takes to start running. - MediaStreams that feed in/go out of a suspended graph should respectively not buffer at the graph input, and output silence - suspended context should not be doing much on the CPU, and we should try to pause audio streams if we can (this behaviour is the main reason we need this in the first place, for saving battery on mobile, and CPU on all platforms) - Now, the implementation: - AudioNodeStreams are now tagged with a context id, to be able to operate on all the streams of a given AudioContext on the Graph thread without having to go and lock everytime to touch the AudioContext. This happens in the AudioNodeStream ctor. IDs are of course constant for the lifetime of the node. - When an AudioContext goes into suspended mode, streams for this AudioContext are moved out of the mStreams array to a second array, mSuspendedStreams. Streams in mSuspendedStream are not ordered, and are not processed. - The MSG will automatically switch to a SystemClockDriver when it finds that there are no more AudioNodeStream/Stream with an audio track. This is how pausing the audio subsystem and saving battery works. Subsequently, when the MSG finds that there are only streams in mSuspendedStreams, it will go to sleep (block on a monitor), so we save CPU, but it does not shut itself down. This is mostly not a new behaviour (this is what the MSG does since the refactoring), but is important to note. - Promises are gripped (addref-ed) on the main thread, and then shepherd down other threads and to the GraphDriver, if needed (sometimes we can resolve them right away). They move between threads as void* to prevent calling methods on them, as they are not thread safe. Then, the driver executes the operation, and when it's done (initializing and closing audio streams can take some time), we send the promise back to the main thread, and resolve it, casting back to Promise* after asserting we're back on the main thread. This way, we can send them back on the main thread once an operation has complete (suspending an audio stream, starting it again on resume(), etc.), without having to do bookkeeping between suspend calls and their result. Promises are not thread safe, so we can't move them around AddRef-ed. - The stream destruction logic now takes into account that a stream can be destroyed while not being in mStreams. - A graph can now switch GraphDriver twice or more per iteration, for example if an author goes suspend()/resume()/suspend() in the same script. - Some operation have to be done on suspended stream, so we now use double for-loop around mSuspendedStreams and mStreams in some places in MediaStreamGraph.cpp. - A tricky part was making sure everything worked at AudioContext boundaries. TrackUnionStream that have one of their input stream suspended append null ticks instead. - The graph ordering algorithm had to be altered to not include suspended streams. - There are some edge cases (adding a stream on a suspended graph, calling suspend/resume when a graph has just been close()d). --- dom/base/nsGlobalWindow.cpp | 6 +- dom/media/GraphDriver.cpp | 120 ++-- dom/media/GraphDriver.h | 31 +- dom/media/MediaStreamGraph.cpp | 543 +++++++++++++++--- dom/media/MediaStreamGraph.h | 35 ++ dom/media/MediaStreamGraphImpl.h | 55 +- dom/media/TrackUnionStream.cpp | 18 +- .../tests/mochitest/identity/mochitest.ini | 2 +- dom/media/tests/mochitest/mochitest.ini | 160 +++--- dom/media/webaudio/AudioContext.cpp | 349 ++++++++++- dom/media/webaudio/AudioContext.h | 87 ++- dom/media/webaudio/AudioDestinationNode.cpp | 11 +- .../webaudio/AudioNodeExternalInputStream.cpp | 4 +- .../webaudio/AudioNodeExternalInputStream.h | 2 +- dom/media/webaudio/AudioNodeStream.cpp | 4 +- dom/media/webaudio/AudioNodeStream.h | 10 +- .../webaudio/MediaStreamAudioSourceNode.h | 1 + dom/media/webaudio/moz.build | 1 + dom/media/webaudio/test/mochitest.ini | 2 + .../test_audioContextSuspendResumeClose.html | 400 +++++++++++++ dom/media/webaudio/test/webaudio.js | 12 + dom/webidl/AudioContext.webidl | 34 +- 22 files changed, 1612 insertions(+), 275 deletions(-) create mode 100644 dom/media/webaudio/test/test_audioContextSuspendResumeClose.html diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 63c85fee34b..2d968e0d821 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -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 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 d = mAudioContexts[i]->Resume(dummy); } // Thaw all of the workers for this window. diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp index 0228edabea2..c9fab58cb31 100644 --- a/dom/media/GraphDriver.cpp +++ b/dom/media/GraphDriver.cpp @@ -23,7 +23,7 @@ extern PRLogModuleInfo* gMediaStreamGraphLog; #ifdef ENABLE_LIFECYCLE_LOG #ifdef ANDROID #include "android/log.h" -#define LIFECYCLE_LOG(args...) __android_log_print(ANDROID_LOG_INFO, "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 releaseEvent = new AsyncCubebTask(mDriver->AsAudioCallbackDriver(), - AsyncCubebTask::SHUTDOWN); + AsyncCubebOperation::SHUTDOWN); mDriver = nullptr; releaseEvent->Dispatch(); } else { @@ -163,7 +160,7 @@ void GraphDriver::Shutdown() if (AsAudioCallbackDriver()) { LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n"); nsRefPtr releaseEvent = - new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); + new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); releaseEvent->Dispatch(); } else { Stop(); @@ -204,7 +201,7 @@ public: // because the osx audio stack is currently switching output device. if (!mDriver->mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) { nsRefPtr releaseEvent = - new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); + new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); mDriver->mPreviousDriver = nullptr; releaseEvent->Dispatch(); } @@ -505,36 +502,21 @@ AsyncCubebTask::Run() MOZ_ASSERT(mDriver); switch(mOperation) { - case AsyncCubebOperation::INIT: + case AsyncCubebOperation::INIT: { LIFECYCLE_LOG("AsyncCubebOperation::INIT\n"); mDriver->Init(); + mDriver->CompleteAudioContextOperations(mOperation); break; - case AsyncCubebOperation::SHUTDOWN: + } + case AsyncCubebOperation::SHUTDOWN: { LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n"); mDriver->Stop(); + + mDriver->CompleteAudioContextOperations(mOperation); + mDriver = nullptr; mShutdownGrip = nullptr; break; - case AsyncCubebOperation::SLEEP: { - { - LIFECYCLE_LOG("AsyncCubebOperation::SLEEP\n"); - MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); - // We might just have been awoken - if (mDriver->mGraphImpl->mNeedAnotherIteration) { - mDriver->mPauseRequested = false; - mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_RUNNING; - mDriver->mGraphImpl->mGraphDriverAsleep = false ; // atomic - break; - } - mDriver->Stop(); - mDriver->mGraphImpl->mGraphDriverAsleep = true; // atomic - mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_WAITING_INDEFINITELY; - mDriver->mPauseRequested = false; - mDriver->mGraphImpl->GetMonitor().Wait(PR_INTERVAL_NO_TIMEOUT); - } - STREAM_LOG(PR_LOG_DEBUG, ("Restarting audio stream from sleep.")); - mDriver->StartStream(); - break; } default: MOZ_CRASH("Operation not implemented."); @@ -546,6 +528,16 @@ AsyncCubebTask::Run() return NS_OK; } +StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation) + : mStream(aStream) + , mPromise(aPromise) + , mOperation(aOperation) +{ + // MOZ_ASSERT(aPromise); +} + AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl, dom::AudioChannel aChannel) : GraphDriver(aGraphImpl) , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS) @@ -561,7 +553,9 @@ AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl, dom:: } AudioCallbackDriver::~AudioCallbackDriver() -{} +{ + MOZ_ASSERT(mPromisesForOperation.IsEmpty()); +} void AudioCallbackDriver::Init() @@ -651,12 +645,18 @@ AudioCallbackDriver::Start() if (NS_IsMainThread()) { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); nsRefPtr initEvent = - new AsyncCubebTask(this, AsyncCubebTask::INIT); + new AsyncCubebTask(this, AsyncCubebOperation::INIT); initEvent->Dispatch(); } else { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from the previous driver's thread", mGraphImpl)); Init(); + // Check if we need to resolve promises because the driver just got switched + // because of a resuming AudioContext + if (!mPromisesForOperation.IsEmpty()) { + CompleteAudioContextOperations(AsyncCubebOperation::INIT); + } + if (mPreviousDriver) { nsCOMPtr event = new MediaStreamGraphShutdownThreadRunnable(mPreviousDriver); @@ -704,7 +704,7 @@ AudioCallbackDriver::Revive() } else { STREAM_LOG(PR_LOG_DEBUG, ("Starting audio threads for MediaStreamGraph %p from a new thread.", mGraphImpl)); nsRefPtr initEvent = - new AsyncCubebTask(this, AsyncCubebTask::INIT); + new AsyncCubebTask(this, AsyncCubebOperation::INIT); initEvent->Dispatch(); } } @@ -729,20 +729,6 @@ AudioCallbackDriver::GetCurrentTime() void AudioCallbackDriver::WaitForNextIteration() { -#if 0 - mGraphImpl->GetMonitor().AssertCurrentThreadOwns(); - - // We can't block on the monitor in the audio callback, so we kick off a new - // thread that will pause the audio stream, and restart it when unblocked. - // We don't want to sleep when we haven't started the driver yet. - if (!mGraphImpl->mNeedAnotherIteration && mAudioStream && mGraphImpl->Running()) { - STREAM_LOG(PR_LOG_DEBUG+1, ("AudioCallbackDriver going to sleep")); - mPauseRequested = true; - nsRefPtr sleepEvent = - new AsyncCubebTask(this, AsyncCubebTask::SLEEP); - sleepEvent->Dispatch(); - } -#endif } void @@ -1074,5 +1060,47 @@ AudioCallbackDriver::IsStarted() { return mStarted; } +void +AudioCallbackDriver::EnqueueStreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation) +{ + MonitorAutoLock mon(mGraphImpl->GetMonitor()); + mPromisesForOperation.AppendElement(StreamAndPromiseForOperation(aStream, + aPromise, + aOperation)); +} + +void AudioCallbackDriver::CompleteAudioContextOperations(AsyncCubebOperation aOperation) +{ + nsAutoTArray array; + + // We can't lock for the whole function because AudioContextOperationCompleted + // will grab the monitor + { + MonitorAutoLock mon(GraphImpl()->GetMonitor()); + array.SwapElements(mPromisesForOperation); + } + + for (int32_t i = array.Length() - 1; i >= 0; i--) { + StreamAndPromiseForOperation& s = array[i]; + if ((aOperation == AsyncCubebOperation::INIT && + s.mOperation == 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 diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h index de387ec1762..5bc64866c24 100644 --- a/dom/media/GraphDriver.h +++ b/dom/media/GraphDriver.h @@ -13,6 +13,7 @@ #include "AudioSegment.h" #include "SelfRef.h" #include "mozilla/Atomics.h" +#include "AudioContext.h" struct cubeb_stream; @@ -321,6 +322,21 @@ private: GraphTime mSlice; }; +struct StreamAndPromiseForOperation +{ + StreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + nsRefPtr mStream; + void* mPromise; + dom::AudioContextOperation mOperation; +}; + +enum AsyncCubebOperation { + INIT, + SHUTDOWN +}; + /** * This is a graph driver that is based on callback functions called by the * audio api. This ensures minimal audio latency, because it means there is no @@ -392,6 +408,12 @@ public: return this; } + /* Enqueue a promise that is going to be resolved when a specific operation + * occurs on the cubeb stream. */ + void EnqueueStreamAndPromiseForOperation(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + bool IsSwitchingDevice() { #ifdef XP_MACOSX return mSelfReference; @@ -414,6 +436,8 @@ public: /* Tell the driver whether this process is using a microphone or not. This is * thread safe. */ void SetMicrophoneActive(bool aActive); + + void CompleteAudioContextOperations(AsyncCubebOperation aOperation); private: /** * On certain MacBookPro, the microphone is located near the left speaker. @@ -471,6 +495,7 @@ private: /* Thread for off-main-thread initialization and * shutdown of the audio stream. */ nsCOMPtr mInitShutdownThread; + nsAutoTArray mPromisesForOperation; dom::AudioChannel mAudioChannel; Atomic mInCallback; /* A thread has been created to be able to pause and restart the audio thread, @@ -498,12 +523,6 @@ private: class AsyncCubebTask : public nsRunnable { public: - enum AsyncCubebOperation { - INIT, - SHUTDOWN, - SLEEP - }; - AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation); diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index 9804e92890b..d6e0e38986e 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -24,6 +24,7 @@ #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "AudioNodeExternalInputStream.h" +#include "mozilla/dom/AudioContextBinding.h" #include #include "DOMMediaStream.h" #include "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)); @@ -380,49 +402,64 @@ MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime, Gr { nsTArray streamsReadyToFinish; nsAutoTArray streamHasOutput; + + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + streamHasOutput.SetLength(mStreams.Length()); - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - MediaStream* stream = mStreams[i]; - // Calculate blocked time and fire Blocked/Unblocked events - GraphTime blockedTime = 0; - GraphTime t = aPrevCurrentTime; - // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called - // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime == stream end time| - while (t <= aNextCurrentTime) { - GraphTime end; - bool blocked = stream->mBlocked.GetAt(t, &end); - if (blocked) { - blockedTime += std::min(end, aNextCurrentTime) - t; - } - if (blocked != stream->mNotifiedBlocked) { - for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { - MediaStreamListener* l = stream->mListeners[j]; - l->NotifyBlockingChanged(this, - blocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED); + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + + // Calculate blocked time and fire Blocked/Unblocked events + GraphTime blockedTime = 0; + GraphTime t = aPrevCurrentTime; + // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called + // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime == + // stream end time| + while (t <= aNextCurrentTime) { + GraphTime end; + bool blocked = stream->mBlocked.GetAt(t, &end); + if (blocked) { + blockedTime += std::min(end, aNextCurrentTime) - t; } - stream->mNotifiedBlocked = blocked; + if (blocked != stream->mNotifiedBlocked) { + for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { + MediaStreamListener* l = stream->mListeners[j]; + l->NotifyBlockingChanged(this, blocked + ? MediaStreamListener::BLOCKED + : MediaStreamListener::UNBLOCKED); + } + stream->mNotifiedBlocked = blocked; + } + t = end; } - t = end; + + stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime, + blockedTime); + // Advance mBlocked last so that implementations of + // AdvanceTimeVaryingValuesToCurrentTime can rely on the value of + // mBlocked. + stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime); + + if (runningAndSuspendedPair[array] == &mStreams) { + streamHasOutput[i] = blockedTime < aNextCurrentTime - aPrevCurrentTime; + // Make this an assertion when bug 957832 is fixed. + NS_WARN_IF_FALSE( + !streamHasOutput[i] || !stream->mNotifiedFinished, + "Shouldn't have already notified of finish *and* have output!"); + + if (stream->mFinished && !stream->mNotifiedFinished) { + streamsReadyToFinish.AppendElement(stream); + } + } + STREAM_LOG(PR_LOG_DEBUG + 1, + ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream, + MediaTimeToSeconds(stream->mBufferStartTime), + MediaTimeToSeconds(blockedTime))); } - - - stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime, blockedTime); - // Advance mBlocked last so that implementations of - // AdvanceTimeVaryingValuesToCurrentTime can rely on the value of mBlocked. - stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime); - - streamHasOutput[i] = blockedTime < aNextCurrentTime - aPrevCurrentTime; - // Make this an assertion when bug 957832 is fixed. - NS_WARN_IF_FALSE(!streamHasOutput[i] || !stream->mNotifiedFinished, - "Shouldn't have already notified of finish *and* have output!"); - - if (stream->mFinished && !stream->mNotifiedFinished) { - streamsReadyToFinish.AppendElement(stream); - } - STREAM_LOG(PR_LOG_DEBUG+1, ("MediaStream %p bufferStartTime=%f blockedTime=%f", - stream, MediaTimeToSeconds(stream->mBufferStartTime), - MediaTimeToSeconds(blockedTime))); } @@ -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 streamSet; - AddBlockingRelatedStreamsToSet(&streamSet, stream); + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; - GraphTime end; - for (GraphTime t = CurrentDriver()->StateComputedTime(); - t < aEndBlockingDecisions; t = end) { - end = GRAPH_TIME_MAX; - RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end); - if (end < GRAPH_TIME_MAX) { - blockingDecisionsWillChange = true; + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < (*runningAndSuspendedPair[array]).Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + if (!stream->mInBlockingSet) { + // Compute a partition of the streams containing 'stream' such that we + // can + // compute the blocking status of each subset independently. + nsAutoTArray streamSet; + AddBlockingRelatedStreamsToSet(&streamSet, stream); + + GraphTime end; + for (GraphTime t = CurrentDriver()->StateComputedTime(); + t < aEndBlockingDecisions; t = end) { + end = GRAPH_TIME_MAX; + RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end); + if (end < GRAPH_TIME_MAX) { + blockingDecisionsWillChange = true; + } } } - } - GraphTime end; - stream->mBlocked.GetAt(IterationEnd(), &end); - if (end < GRAPH_TIME_MAX) { - blockingDecisionsWillChange = true; + GraphTime end; + stream->mBlocked.GetAt(IterationEnd(), &end); + if (end < GRAPH_TIME_MAX) { + blockingDecisionsWillChange = true; + } } } STREAM_LOG(PR_LOG_DEBUG+1, ("Media graph %p computed blocking for interval %f to %f", @@ -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 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(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(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 mStream; + MediaStreamGraph* mGraph; +}; + +void +MediaStreamGraph::NotifyWhenGraphStarted(AudioNodeStream* aStream) +{ + class GraphStartedNotificationControlMessage : public ControlMessage + { + public: + explicit GraphStartedNotificationControlMessage(AudioNodeStream* aStream) + : ControlMessage(aStream) + { + } + virtual void Run() + { + // This runs on the graph thread, so when this runs, and the current + // driver is an AudioCallbackDriver, we know the audio hardware is + // started. If not, we are going to switch soon, keep reposting this + // ControlMessage. + MediaStreamGraphImpl* graphImpl = mStream->GraphImpl(); + if (graphImpl->CurrentDriver()->AsAudioCallbackDriver()) { + nsCOMPtr event = new dom::StateChangeTask( + mStream->AsAudioNodeStream(), nullptr, AudioContextState::Running); + NS_DispatchToMainThread(event); + } else { + nsCOMPtr event = new GraphStartedRunnable( + mStream->AsAudioNodeStream(), mStream->Graph()); + NS_DispatchToMainThread(event); + } + } + virtual void RunDuringShutdown() + { + MOZ_ASSERT(false, "We should be reviving the graph?"); + } + }; + + MediaStreamGraphImpl* graphImpl = static_cast(this); + graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream)); +} + +void +MediaStreamGraphImpl::ResetVisitedStreamState() +{ + // Reset the visited/consumed/blocked state of the streams. + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + ProcessedMediaStream* ps = + (*runningAndSuspendedPair[array])[i]->AsProcessedStream(); + if (ps) { + ps->mCycleMarker = NOT_VISITED; + ps->mIsConsumed = false; + ps->mInBlockingSet = false; + } + } + } +} + +void +MediaStreamGraphImpl::StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId, + mozilla::LinkedList& aStreamSet) +{ + nsTArray* runningAndSuspendedPair[2]; + runningAndSuspendedPair[0] = &mStreams; + runningAndSuspendedPair[1] = &mSuspendedStreams; + + for (uint32_t array = 0; array < 2; array++) { + for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) { + MediaStream* stream = (*runningAndSuspendedPair[array])[i]; + if (aAudioContextId == stream->AudioContextId()) { + aStreamSet.insertFront(stream); + } + } + } +} + +void +MediaStreamGraphImpl::MoveStreams(AudioContextOperation aAudioContextOperation, + mozilla::LinkedList& aStreamSet) +{ + // For our purpose, Suspend and Close are equivalent: we want to remove the + // streams from the set of streams that are going to be processed. + nsTArray& from = + aAudioContextOperation == AudioContextOperation::Resume ? mSuspendedStreams + : mStreams; + nsTArray& to = + aAudioContextOperation == AudioContextOperation::Resume ? mStreams + : mSuspendedStreams; + + MediaStream* stream; + while ((stream = aStreamSet.getFirst())) { + // It is posible to not find the stream here, if there has been two + // suspend/resume/close calls in a row. + auto i = from.IndexOf(stream); + if (i != from.NoIndex) { + from.RemoveElementAt(i); + to.AppendElement(stream); + } + + // If streams got added during a period where an AudioContext was suspended, + // set their buffer start time to the appropriate value now: + if (aAudioContextOperation == AudioContextOperation::Resume && + stream->mBufferStartTime == START_TIME_DELAYED) { + stream->mBufferStartTime = IterationEnd(); + } + + stream->remove(); + } + STREAM_LOG(PR_LOG_DEBUG, ("Moving streams between suspended and running" + "state: mStreams: %d, mSuspendedStreams: %d\n", mStreams.Length(), + mSuspendedStreams.Length())); +#ifdef DEBUG + // The intersection of the two arrays should be null. + for (uint32_t i = 0; i < mStreams.Length(); i++) { + for (uint32_t j = 0; j < mSuspendedStreams.Length(); j++) { + MOZ_ASSERT( + mStreams[i] != mSuspendedStreams[j], + "The suspended stream set and running stream set are not disjoint."); + } + } +#endif +} + +void +MediaStreamGraphImpl::AudioContextOperationCompleted(MediaStream* aStream, + void* aPromise, + AudioContextOperation aOperation) +{ + // This can be called from the thread created to do cubeb operation, or the + // MSG thread. The pointers passed back here are refcounted, so are still + // alive. + MonitorAutoLock lock(mMonitor); + + AudioContextState state; + switch (aOperation) { + case Suspend: state = AudioContextState::Suspended; break; + case Resume: state = AudioContextState::Running; break; + case Close: state = AudioContextState::Closed; break; + default: MOZ_CRASH("Not handled."); + } + + nsCOMPtr event = new dom::StateChangeTask( + aStream->AsAudioNodeStream(), aPromise, state); + NS_DispatchToMainThread(event); +} + +void +MediaStreamGraphImpl::ApplyAudioContextOperationImpl(AudioNodeStream* aStream, + AudioContextOperation aOperation, + void* aPromise) +{ + MOZ_ASSERT(CurrentDriver()->OnThread()); + mozilla::LinkedList streamSet; + + SetStreamOrderDirty(); + + ResetVisitedStreamState(); + + StreamSetForAudioContext(aStream->AudioContextId(), streamSet); + + MoveStreams(aOperation, streamSet); + MOZ_ASSERT(!streamSet.getFirst(), + "Streams should be removed from the list after having been moved."); + + // If we have suspended the last AudioContext, and we don't have other + // streams that have audio, this graph will automatically switch to a + // SystemCallbackDriver, because it can't find a MediaStream that has an audio + // track. When resuming, force switching to an AudioCallbackDriver. It would + // have happened at the next iteration anyways, but doing this now save + // some time. + if (aOperation == AudioContextOperation::Resume) { + if (!CurrentDriver()->AsAudioCallbackDriver()) { + AudioCallbackDriver* driver = new AudioCallbackDriver(this); + driver->EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation); + mMixer.AddCallback(driver); + CurrentDriver()->SwitchAtNextIteration(driver); + } else { + // We are resuming a context, but we are already using an + // AudioCallbackDriver, we can resolve the promise now. + AudioContextOperationCompleted(aStream, aPromise, aOperation); + } + } + // Close, suspend: check if we are going to switch to a + // SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver + // if that's the case, so it can notify the content. + // This is the same logic as in UpdateStreamOrder, but it's simpler to have it + // here as well so we don't have to store the Promise(s) on the Graph. + if (aOperation != AudioContextOperation::Resume) { + bool audioTrackPresent = false; + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + MediaStream* stream = mStreams[i]; + if (stream->AsAudioNodeStream()) { + audioTrackPresent = true; + } + for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO); + !tracks.IsEnded(); tracks.Next()) { + audioTrackPresent = true; + } + } + if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) { + CurrentDriver()->AsAudioCallbackDriver()-> + EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation); + + SystemClockDriver* driver = new SystemClockDriver(this); + CurrentDriver()->SwitchAtNextIteration(driver); + } else { + // We are closing or suspending an AudioContext, but something else is + // using the audio stream, we can resolve the promise now. + AudioContextOperationCompleted(aStream, aPromise, aOperation); + } + } +} + +void +MediaStreamGraph::ApplyAudioContextOperation(AudioNodeStream* aNodeStream, + AudioContextOperation aOperation, + void* aPromise) +{ + class AudioContextOperationControlMessage : public ControlMessage + { + public: + AudioContextOperationControlMessage(AudioNodeStream* aStream, + AudioContextOperation aOperation, + void* aPromise) + : ControlMessage(aStream) + , mAudioContextOperation(aOperation) + , mPromise(aPromise) + { + } + virtual void Run() + { + mStream->GraphImpl()->ApplyAudioContextOperationImpl( + mStream->AsAudioNodeStream(), mAudioContextOperation, mPromise); + } + virtual void RunDuringShutdown() + { + MOZ_ASSERT(false, "We should be reviving the graph?"); + } + + private: + AudioContextOperation mAudioContextOperation; + void* mPromise; + }; + + MediaStreamGraphImpl* graphImpl = static_cast(this); + graphImpl->AppendMessage( + new AudioContextOperationControlMessage(aNodeStream, aOperation, aPromise)); +} + bool MediaStreamGraph::IsNonRealtime() const { diff --git a/dom/media/MediaStreamGraph.h b/dom/media/MediaStreamGraph.h index efd07611e7c..43a0cd0aaca 100644 --- a/dom/media/MediaStreamGraph.h +++ b/dom/media/MediaStreamGraph.h @@ -22,6 +22,7 @@ #include #include "mozilla/dom/AudioChannelBinding.h" #include "DOMMediaStream.h" +#include "AudioContext.h" class nsIRunnable; @@ -318,6 +319,7 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream) explicit MediaStream(DOMMediaStream* aWrapper); + virtual dom::AudioContext::AudioContextId AudioContextId() const { return 0; } protected: // Protected destructor, to discourage deletion outside of Release(): @@ -364,6 +366,8 @@ public: // Explicitly block. Useful for example if a media element is pausing // and we need to stop its stream emitting its buffered data. virtual void ChangeExplicitBlockerCount(int32_t aDelta); + void BlockStreamIfNeeded(); + void UnblockStreamIfNeeded(); // Events will be dispatched by calling methods of aListener. virtual void AddListener(MediaStreamListener* aListener); virtual void RemoveListener(MediaStreamListener* aListener); @@ -465,6 +469,22 @@ public: { mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta); } + void BlockStreamIfNeededImpl(GraphTime aTime) + { + bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0; + if (blocked) { + return; + } + ChangeExplicitBlockerCountImpl(aTime, 1); + } + void UnblockStreamIfNeededImpl(GraphTime aTime) + { + bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0; + if (!blocked) { + return; + } + ChangeExplicitBlockerCountImpl(aTime, -1); + } void AddListenerImpl(already_AddRefed aListener); void RemoveListenerImpl(MediaStreamListener* aListener); void RemoveAllListenersImpl(); @@ -1227,6 +1247,21 @@ public: CreateAudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate = 0); + /* From the main thread, ask the MSG to send back an event when the graph + * thread is running, and audio is being processed. */ + void NotifyWhenGraphStarted(AudioNodeStream* aNodeStream); + /* From the main thread, suspend, resume or close an AudioContext. + * aNodeStream is the stream of the DestinationNode of the AudioContext. + * + * This can possibly pause the graph thread, releasing system resources, if + * all streams have been suspended/closed. + * + * When the operation is complete, aPromise is resolved. + */ + void ApplyAudioContextOperation(AudioNodeStream* aNodeStream, + dom::AudioContextOperation aState, + void * aPromise); + bool IsNonRealtime() const; /** * Start processing non-realtime for a specific number of ticks. diff --git a/dom/media/MediaStreamGraphImpl.h b/dom/media/MediaStreamGraphImpl.h index a4b57b7d165..5e25740ca85 100644 --- a/dom/media/MediaStreamGraphImpl.h +++ b/dom/media/MediaStreamGraphImpl.h @@ -248,6 +248,49 @@ public: * Mark aStream and all its inputs (recursively) as consumed. */ static void MarkConsumed(MediaStream* aStream); + + /** + * Given the Id of an AudioContext, return the set of all MediaStreams that + * are part of this context. + */ + void StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId, + mozilla::LinkedList& aStreamSet); + + /** + * Called when a suspend/resume/close operation has been completed, on the + * graph thread. + */ + void AudioContextOperationCompleted(MediaStream* aStream, + void* aPromise, + dom::AudioContextOperation aOperation); + + /** + * Apply and AudioContext operation (suspend/resume/closed), on the graph + * thread. + */ + void ApplyAudioContextOperationImpl(AudioNodeStream* aStream, + dom::AudioContextOperation aOperation, + void* aPromise); + + /* + * Move streams from the mStreams to mSuspendedStream if suspending/closing an + * AudioContext, or the inverse when resuming an AudioContext. + */ + void MoveStreams(dom::AudioContextOperation aAudioContextOperation, + mozilla::LinkedList& aStreamSet); + + /* + * Reset some state about the streams before suspending them, or resuming + * them. + */ + void ResetVisitedStreamState(); + + /* + * True if a stream is suspended, that is, is not in mStreams, but in + * mSuspendedStream. + */ + bool StreamSuspended(MediaStream* aStream); + /** * Sort mStreams so that every stream not in a cycle is after any streams * it depends on, and every stream in a cycle is marked as being in a cycle. @@ -368,7 +411,10 @@ public: /** * Returns true when there are no active streams. */ - bool IsEmpty() { return mStreams.IsEmpty() && mPortCount == 0; } + bool IsEmpty() + { + return mStreams.IsEmpty() && mSuspendedStreams.IsEmpty() && mPortCount == 0; + } // For use by control messages, on graph thread only. /** @@ -487,6 +533,13 @@ public: * unnecessary thread-safe refcount changes. */ nsTArray mStreams; + /** + * This stores MediaStreams that are part of suspended AudioContexts. + * mStreams and mSuspendStream are disjoint sets: a stream is either suspended + * or not suspended. Suspended streams are not ordered in UpdateStreamOrder, + * and are therefore not doing any processing. + */ + nsTArray mSuspendedStreams; /** * Streams from mFirstCycleBreaker to the end of mStreams produce output * before they receive input. They correspond to DelayNodes that are in diff --git a/dom/media/TrackUnionStream.cpp b/dom/media/TrackUnionStream.cpp index bb59bf9867b..fddc04ad05d 100644 --- a/dom/media/TrackUnionStream.cpp +++ b/dom/media/TrackUnionStream.cpp @@ -24,10 +24,10 @@ #include "AudioNodeEngine.h" #include "AudioNodeStream.h" #include "AudioNodeExternalInputStream.h" +#include "webaudio/MediaStreamAudioDestinationNode.h" #include #include "DOMMediaStream.h" #include "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) { diff --git a/dom/media/tests/mochitest/identity/mochitest.ini b/dom/media/tests/mochitest/identity/mochitest.ini index f93b6262353..6eb017db894 100644 --- a/dom/media/tests/mochitest/identity/mochitest.ini +++ b/dom/media/tests/mochitest/identity/mochitest.ini @@ -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 diff --git a/dom/media/tests/mochitest/mochitest.ini b/dom/media/tests/mochitest/mochitest.ini index d0e09056759..d855ddf8af1 100644 --- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -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] diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp index 81033d1cec0..7779d197d64 100644 --- a/dom/media/webaudio/AudioContext.cpp +++ b/dom/media/webaudio/AudioContext.cpp @@ -9,8 +9,8 @@ #include "nsPIDOMWindow.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/AnalyserNode.h" -#include "mozilla/dom/AudioContextBinding.h" #include "mozilla/dom/HTMLMediaElement.h" +#include "mozilla/dom/AudioContextBinding.h" #include "mozilla/dom/OfflineAudioContextBinding.h" #include "mozilla/dom/OwningNonNull.h" #include "MediaStreamGraph.h" @@ -42,6 +42,10 @@ namespace mozilla { namespace dom { +// 0 is a special value that MediaStreams use to denote they are not part of a +// AudioContext. +static dom::AudioContext::AudioContextId gAudioContextId = 1; + NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioContext) @@ -85,12 +89,15 @@ AudioContext::AudioContext(nsPIDOMWindow* aWindow, uint32_t aLength, float aSampleRate) : DOMEventTargetHelper(aWindow) + , mId(gAudioContextId++) , mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate)) + , mAudioContextState(AudioContextState::Suspended) , mNumberOfChannels(aNumberOfChannels) , mNodeCount(0) , mIsOffline(aIsOffline) , mIsStarted(!aIsOffline) , mIsShutDown(false) + , mCloseCalled(false) { aWindow->AddAudioContext(this); @@ -197,9 +204,22 @@ AudioContext::Constructor(const GlobalObject& aGlobal, return object.forget(); } -already_AddRefed -AudioContext::CreateBufferSource() +bool AudioContext::CheckClosed(ErrorResult& aRv) { + if (mAudioContextState == AudioContextState::Closed) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return true; + } + return false; +} + +already_AddRefed +AudioContext::CreateBufferSource(ErrorResult& aRv) +{ + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr bufferNode = new AudioBufferSourceNode(this); return bufferNode.forget(); @@ -247,6 +267,10 @@ AudioContext::CreateMediaStreamDestination(ErrorResult& aRv) return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr node = new MediaStreamAudioDestinationNode(this); return node.forget(); @@ -266,6 +290,10 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize, return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr scriptProcessor = new ScriptProcessorNode(this, aBufferSize, aNumberOfInputChannels, aNumberOfOutputChannels); @@ -273,15 +301,23 @@ AudioContext::CreateScriptProcessor(uint32_t aBufferSize, } already_AddRefed -AudioContext::CreateAnalyser() +AudioContext::CreateAnalyser(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr analyserNode = new AnalyserNode(this); return analyserNode.forget(); } already_AddRefed -AudioContext::CreateStereoPanner() +AudioContext::CreateStereoPanner(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr stereoPannerNode = new StereoPannerNode(this); return stereoPannerNode.forget(); } @@ -300,6 +336,11 @@ AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement, return nullptr; } #endif + + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr 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 = new MediaStreamAudioSourceNode(this, &aMediaStream); return mediaStreamAudioSourceNode.forget(); } already_AddRefed -AudioContext::CreateGain() +AudioContext::CreateGain(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr gainNode = new GainNode(this); return gainNode.forget(); } already_AddRefed -AudioContext::CreateWaveShaper() +AudioContext::CreateWaveShaper(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr waveShaperNode = new WaveShaperNode(this); return waveShaperNode.forget(); } @@ -340,25 +394,38 @@ AudioContext::CreateWaveShaper() already_AddRefed AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + if (aMaxDelayTime > 0. && aMaxDelayTime < 180.) { nsRefPtr delayNode = new DelayNode(this, aMaxDelayTime); return delayNode.forget(); } + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } already_AddRefed -AudioContext::CreatePanner() +AudioContext::CreatePanner(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr pannerNode = new PannerNode(this); mPannerNodes.PutEntry(pannerNode); return pannerNode.forget(); } already_AddRefed -AudioContext::CreateConvolver() +AudioContext::CreateConvolver(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr convolverNode = new ConvolverNode(this); return convolverNode.forget(); } @@ -372,6 +439,10 @@ AudioContext::CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv) return nullptr; } + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr 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 mergerNode = new ChannelMergerNode(this, aNumberOfInputs); return mergerNode.forget(); } already_AddRefed -AudioContext::CreateDynamicsCompressor() +AudioContext::CreateDynamicsCompressor(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr compressorNode = new DynamicsCompressorNode(this); return compressorNode.forget(); } already_AddRefed -AudioContext::CreateBiquadFilter() +AudioContext::CreateBiquadFilter(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr filterNode = new BiquadFilterNode(this); return filterNode.forget(); } already_AddRefed -AudioContext::CreateOscillator() +AudioContext::CreateOscillator(ErrorResult& aRv) { + if (CheckClosed(aRv)) { + return nullptr; + } + nsRefPtr oscillatorNode = new OscillatorNode(this); return oscillatorNode.forget(); @@ -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 parent = do_QueryInterface(mAudioContext->GetParentObject()); + if (!parent) { + return NS_ERROR_FAILURE; + } + + nsIDocument* doc = parent->GetExtantDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + return nsContentUtils::DispatchTrustedEvent(doc, + static_cast(mAudioContext), + NS_LITERAL_STRING("statechange"), + false, false); + } + +private: + nsRefPtr mAudioContext; +}; + + + +void +AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState) +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT((mAudioContextState == AudioContextState::Suspended && + aNewState == AudioContextState::Running) || + (mAudioContextState == AudioContextState::Running && + aNewState == AudioContextState::Suspended) || + (mAudioContextState == AudioContextState::Running && + aNewState == AudioContextState::Closed) || + (mAudioContextState == AudioContextState::Suspended && + aNewState == AudioContextState::Closed) || + (mAudioContextState == aNewState), + "Invalid AudioContextState transition"); + + MOZ_ASSERT( + mIsOffline || aPromise || aNewState == AudioContextState::Running, + "We should have a promise here if this is a real-time AudioContext." + "Or this is the first time we switch to \"running\"."); + + if (aPromise) { + Promise* promise = reinterpret_cast(aPromise); + promise->MaybeResolve(JS::UndefinedHandleValue); + DebugOnly rv = mPromiseGripArray.RemoveElement(promise); + MOZ_ASSERT(rv, "Promise wasn't in the grip array?"); + } + + if (mAudioContextState != aNewState) { + nsRefPtr onStateChangeTask = + new OnStateChangeTask(this); + NS_DispatchToMainThread(onStateChangeTask); + } + + mAudioContextState = aNewState; +} + +already_AddRefed +AudioContext::Suspend(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed || + mCloseCalled) { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Suspended) { + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); + } + MediaStream* ds = DestinationStream(); if (ds) { - ds->ChangeExplicitBlockerCount(-1); + ds->BlockStreamIfNeeded(); } + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Suspend, promise); + + return promise.forget(); +} + +already_AddRefed +AudioContext::Resume(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed || + mCloseCalled) { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Running) { + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); + } + + MediaStream* ds = DestinationStream(); + if (ds) { + ds->UnblockStreamIfNeeded(); + } + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Resume, promise); + + return promise.forget(); +} + +already_AddRefed +AudioContext::Close(ErrorResult& aRv) +{ + nsCOMPtr parentObject = do_QueryInterface(GetParentObject()); + nsRefPtr promise; + promise = Promise::Create(parentObject, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (mIsOffline) { + promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return promise.forget(); + } + + if (mAudioContextState == AudioContextState::Closed) { + promise->MaybeResolve(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + mCloseCalled = true; + + mPromiseGripArray.AppendElement(promise); + Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(), + AudioContextOperation::Close, promise); + + MediaStream* ds = DestinationStream(); + if (ds) { + ds->BlockStreamIfNeeded(); + } + + return promise.forget(); } void @@ -653,6 +957,9 @@ AudioContext::StartRendering(ErrorResult& aRv) mIsStarted = true; nsRefPtr promise = Promise::Create(parentObject, aRv); mDestination->StartRendering(promise); + + OnStateChanged(nullptr, AudioContextState::Running); + return promise.forget(); } diff --git a/dom/media/webaudio/AudioContext.h b/dom/media/webaudio/AudioContext.h index ed9a7fe55bd..20e7f5b6482 100644 --- a/dom/media/webaudio/AudioContext.h +++ b/dom/media/webaudio/AudioContext.h @@ -35,9 +35,12 @@ class DOMMediaStream; class ErrorResult; class MediaStream; class MediaStreamGraph; +class AudioNodeEngine; +class AudioNodeStream; namespace dom { +enum class AudioContextState : uint32_t; class AnalyserNode; class AudioBuffer; class AudioBufferSourceNode; @@ -64,6 +67,30 @@ class WaveShaperNode; class PeriodicWave; class Promise; +/* This runnable allows the MSG to notify the main thread when audio is actually + * flowing */ +class StateChangeTask final : public nsRunnable +{ +public: + /* This constructor should be used when this event is sent from the main + * thread. */ + StateChangeTask(AudioContext* aAudioContext, void* aPromise, AudioContextState aNewState); + + /* This constructor should be used when this event is sent from the audio + * thread. */ + StateChangeTask(AudioNodeStream* aStream, void* aPromise, AudioContextState aNewState); + + NS_IMETHOD Run() override; + +private: + nsRefPtr mAudioContext; + void* mPromise; + nsRefPtr mAudioNodeStream; + AudioContextState mNewState; +}; + +enum AudioContextOperation { Suspend, Resume, Close }; + class AudioContext final : public DOMEventTargetHelper, public nsIMemoryReporter { @@ -76,6 +103,8 @@ class AudioContext final : public DOMEventTargetHelper, ~AudioContext(); public: + typedef uint64_t AudioContextId; + NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioContext, DOMEventTargetHelper) @@ -87,8 +116,6 @@ public: } void Shutdown(); // idempotent - void Suspend(); - void Resume(); virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -124,11 +151,31 @@ public: return mSampleRate; } + AudioContextId Id() const + { + return mId; + } + double CurrentTime() const; AudioListener* Listener(); - already_AddRefed CreateBufferSource(); + AudioContextState State() const; + // Those three methods return a promise to content, that is resolved when an + // (possibly long) operation is completed on the MSG (and possibly other) + // thread(s). To avoid having to match the calls and asychronous result when + // the operation is completed, we keep a reference to the promises on the main + // thread, and then send the promises pointers down the MSG thread, as a void* + // (to make it very clear that the pointer is to merely be treated as an ID). + // When back on the main thread, we can resolve or reject the promise, by + // casting it back to a `Promise*` while asserting we're back on the main + // thread and removing the reference we added. + already_AddRefed Suspend(ErrorResult& aRv); + already_AddRefed Resume(ErrorResult& aRv); + already_AddRefed Close(ErrorResult& aRv); + IMPL_EVENT_HANDLER(statechange) + + already_AddRefed CreateBufferSource(ErrorResult& aRv); already_AddRefed CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, @@ -145,16 +192,16 @@ public: ErrorResult& aRv); already_AddRefed - CreateStereoPanner(); + CreateStereoPanner(ErrorResult& aRv); already_AddRefed - CreateAnalyser(); + CreateAnalyser(ErrorResult& aRv); already_AddRefed - CreateGain(); + CreateGain(ErrorResult& aRv); already_AddRefed - CreateWaveShaper(); + CreateWaveShaper(ErrorResult& aRv); already_AddRefed CreateMediaElementSource(HTMLMediaElement& aMediaElement, ErrorResult& aRv); @@ -165,10 +212,10 @@ public: CreateDelay(double aMaxDelayTime, ErrorResult& aRv); already_AddRefed - CreatePanner(); + CreatePanner(ErrorResult& aRv); already_AddRefed - CreateConvolver(); + CreateConvolver(ErrorResult& aRv); already_AddRefed CreateChannelSplitter(uint32_t aNumberOfOutputs, ErrorResult& aRv); @@ -177,13 +224,13 @@ public: CreateChannelMerger(uint32_t aNumberOfInputs, ErrorResult& aRv); already_AddRefed - CreateDynamicsCompressor(); + CreateDynamicsCompressor(ErrorResult& aRv); already_AddRefed - CreateBiquadFilter(); + CreateBiquadFilter(ErrorResult& aRv); already_AddRefed - CreateOscillator(); + CreateOscillator(ErrorResult& aRv); already_AddRefed CreatePeriodicWave(const Float32Array& aRealData, const Float32Array& aImagData, @@ -244,6 +291,8 @@ public: return aTime + ExtraCurrentTime(); } + void OnStateChanged(void* aPromise, AudioContextState aNewState); + IMPL_EVENT_HANDLER(mozinterruptbegin) IMPL_EVENT_HANDLER(mozinterruptend) @@ -266,13 +315,23 @@ private: friend struct ::mozilla::WebAudioDecodeJob; + bool CheckClosed(ErrorResult& aRv); + private: + // Each AudioContext has an id, that is passed down the MediaStreams that + // back the AudioNodes, so we can easily compute the set of all the + // MediaStreams for a given context, on the MediasStreamGraph side. + const AudioContextId mId; // Note that it's important for mSampleRate to be initialized before // mDestination, as mDestination's constructor needs to access it! const float mSampleRate; + AudioContextState mAudioContextState; nsRefPtr mDestination; nsRefPtr mListener; nsTArray > mDecodeJobs; + // This array is used to keep the suspend/resume/close promises alive until + // they are resolved, so we can safely pass them accross threads. + nsTArray> mPromiseGripArray; // See RegisterActiveNode. These will keep the AudioContext alive while it // is rendering and the window remains alive. nsTHashtable > mActiveNodes; @@ -286,8 +345,12 @@ private: bool mIsOffline; bool mIsStarted; bool mIsShutDown; + // Close has been called, reject suspend and resume call. + bool mCloseCalled; }; +static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0; + } } diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp index 08030c0f72e..172bbd7c858 100644 --- a/dom/media/webaudio/AudioDestinationNode.cpp +++ b/dom/media/webaudio/AudioDestinationNode.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioDestinationNode.h" +#include "AudioContext.h" #include "mozilla/dom/AudioDestinationNodeBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" @@ -176,9 +177,11 @@ public: aNode->ResolvePromise(renderedBuffer); - nsRefPtr task = + nsRefPtr onCompleteTask = new OnCompleteTask(context, renderedBuffer); - NS_DispatchToMainThread(task); + NS_DispatchToMainThread(onCompleteTask); + + context->OnStateChanged(nullptr, AudioContextState::Closed); } virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override @@ -367,6 +370,10 @@ AudioDestinationNode::AudioDestinationNode(AudioContext* aContext, mStream->AddMainThreadListener(this); mStream->AddAudioOutput(&gWebAudioOutputKey); + if (!aIsOffline) { + graph->NotifyWhenGraphStarted(mStream->AsAudioNodeStream()); + } + if (aChannel != AudioChannel::Normal) { ErrorResult rv; SetMozAudioChannelType(aChannel, rv); diff --git a/dom/media/webaudio/AudioNodeExternalInputStream.cpp b/dom/media/webaudio/AudioNodeExternalInputStream.cpp index 15442c27f43..b165af71d0c 100644 --- a/dom/media/webaudio/AudioNodeExternalInputStream.cpp +++ b/dom/media/webaudio/AudioNodeExternalInputStream.cpp @@ -12,8 +12,8 @@ using namespace mozilla::dom; namespace mozilla { -AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate) - : AudioNodeStream(aEngine, MediaStreamGraph::INTERNAL_STREAM, aSampleRate) +AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId) + : AudioNodeStream(aEngine, MediaStreamGraph::INTERNAL_STREAM, aSampleRate, aContextId) { MOZ_COUNT_CTOR(AudioNodeExternalInputStream); } diff --git a/dom/media/webaudio/AudioNodeExternalInputStream.h b/dom/media/webaudio/AudioNodeExternalInputStream.h index 92b9b782d33..d2248a8232e 100644 --- a/dom/media/webaudio/AudioNodeExternalInputStream.h +++ b/dom/media/webaudio/AudioNodeExternalInputStream.h @@ -20,7 +20,7 @@ namespace mozilla { */ class AudioNodeExternalInputStream : public AudioNodeStream { public: - AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate); + AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId); protected: ~AudioNodeExternalInputStream(); diff --git a/dom/media/webaudio/AudioNodeStream.cpp b/dom/media/webaudio/AudioNodeStream.cpp index 2fcf4ab8edf..8e23ec16085 100644 --- a/dom/media/webaudio/AudioNodeStream.cpp +++ b/dom/media/webaudio/AudioNodeStream.cpp @@ -27,10 +27,12 @@ namespace mozilla { AudioNodeStream::AudioNodeStream(AudioNodeEngine* aEngine, MediaStreamGraph::AudioNodeStreamKind aKind, - TrackRate aSampleRate) + TrackRate aSampleRate, + AudioContext::AudioContextId aContextId) : ProcessedMediaStream(nullptr), mEngine(aEngine), mSampleRate(aSampleRate), + mAudioContextId(aContextId), mKind(aKind), mNumberOfInputChannels(2), mMarkAsFinishedAfterThisBlock(false), diff --git a/dom/media/webaudio/AudioNodeStream.h b/dom/media/webaudio/AudioNodeStream.h index 0956478873f..52711abefba 100644 --- a/dom/media/webaudio/AudioNodeStream.h +++ b/dom/media/webaudio/AudioNodeStream.h @@ -47,7 +47,8 @@ public: */ AudioNodeStream(AudioNodeEngine* aEngine, MediaStreamGraph::AudioNodeStreamKind aKind, - TrackRate aSampleRate); + TrackRate aSampleRate, + AudioContext::AudioContextId aContextId); protected: ~AudioNodeStream(); @@ -121,6 +122,7 @@ public: // Any thread AudioNodeEngine* Engine() { return mEngine; } TrackRate SampleRate() const { return mSampleRate; } + AudioContext::AudioContextId AudioContextId() const override { return mAudioContextId; } /** * Convert a time in seconds on the destination stream to ticks @@ -147,6 +149,7 @@ public: void SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf, AudioNodeSizes& aUsage) const; + protected: void AdvanceOutputSegment(); void FinishOutput(); @@ -166,8 +169,11 @@ protected: OutputChunks mLastChunks; // The stream's sampling rate const TrackRate mSampleRate; + // This is necessary to be able to find all the nodes for a given + // AudioContext. It is set on the main thread, in the constructor. + const AudioContext::AudioContextId mAudioContextId; // Whether this is an internal or external stream - MediaStreamGraph::AudioNodeStreamKind mKind; + const MediaStreamGraph::AudioNodeStreamKind mKind; // The number of input channels that this stream requires. 0 means don't care. uint32_t mNumberOfInputChannels; // The mixing modes diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.h b/dom/media/webaudio/MediaStreamAudioSourceNode.h index a121c4a6111..1fcfff1f3ea 100644 --- a/dom/media/webaudio/MediaStreamAudioSourceNode.h +++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h @@ -35,6 +35,7 @@ public: NS_ERROR("MediaStreamAudioSourceNodeEngine bad parameter index"); } } + private: bool mEnabled; }; diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build index ff6e320b117..4deee515eb1 100644 --- a/dom/media/webaudio/moz.build +++ b/dom/media/webaudio/moz.build @@ -34,6 +34,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'FFTBlock.h', + 'MediaStreamAudioDestinationNode.h', ] EXPORTS.mozilla.dom += [ diff --git a/dom/media/webaudio/test/mochitest.ini b/dom/media/webaudio/test/mochitest.ini index d325710d51b..95c7ed37d5d 100644 --- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -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] diff --git a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html new file mode 100644 index 00000000000..3007699d401 --- /dev/null +++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html @@ -0,0 +1,400 @@ + + + + Test suspend, resume and close method of the AudioContext + + + + + +
+
+
+ + diff --git a/dom/media/webaudio/test/webaudio.js b/dom/media/webaudio/test/webaudio.js index 105a4f9eab1..4cf68a8f92b 100644 --- a/dom/media/webaudio/test/webaudio.js +++ b/dom/media/webaudio/test/webaudio.js @@ -33,6 +33,18 @@ function expectTypeError(func) { ok(threw, "The exception was thrown"); } +function expectRejectedPromise(that, func, exceptionName) { + var promise = that[func](); + + ok(promise instanceof Promise, "Expect a Promise"); + + promise.then(function(res) { + ok(false, "Promise resolved when it should have been rejected."); + }).catch(function(err) { + is(err.name, exceptionName, "Promise correctly reject with " + exceptionName); + }); +} + function fuzzyCompare(a, b) { return Math.abs(a - b) < 9e-3; } diff --git a/dom/webidl/AudioContext.webidl b/dom/webidl/AudioContext.webidl index 94e1ac158d3..791c3568af9 100644 --- a/dom/webidl/AudioContext.webidl +++ b/dom/webidl/AudioContext.webidl @@ -13,6 +13,12 @@ callback DecodeSuccessCallback = void (AudioBuffer decodedData); callback DecodeErrorCallback = void (); +enum AudioContextState { + "suspended", + "running", + "closed" +}; + [Constructor, Constructor(AudioChannel audioChannelType)] interface AudioContext : EventTarget { @@ -21,6 +27,14 @@ interface AudioContext : EventTarget { readonly attribute float sampleRate; readonly attribute double currentTime; readonly attribute AudioListener listener; + readonly attribute AudioContextState state; + [Throws] + Promise suspend(); + [Throws] + Promise resume(); + [Throws] + Promise close(); + attribute EventHandler onstatechange; [NewObject, Throws] AudioBuffer createBuffer(unsigned long numberOfChannels, unsigned long length, float sampleRate); @@ -31,7 +45,7 @@ interface AudioContext : EventTarget { optional DecodeErrorCallback errorCallback); // AudioNode creation - [NewObject] + [NewObject, Throws] AudioBufferSourceNode createBufferSource(); [NewObject, Throws] @@ -42,25 +56,25 @@ interface AudioContext : EventTarget { optional unsigned long numberOfInputChannels = 2, optional unsigned long numberOfOutputChannels = 2); - [NewObject] + [NewObject, Throws] StereoPannerNode createStereoPanner(); - [NewObject] + [NewObject, Throws] AnalyserNode createAnalyser(); [NewObject, Throws, UnsafeInPrerendering] MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement); [NewObject, Throws, UnsafeInPrerendering] MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream); - [NewObject] + [NewObject, Throws] GainNode createGain(); [NewObject, Throws] DelayNode createDelay(optional double maxDelayTime = 1); - [NewObject] + [NewObject, Throws] BiquadFilterNode createBiquadFilter(); - [NewObject] + [NewObject, Throws] WaveShaperNode createWaveShaper(); - [NewObject] + [NewObject, Throws] PannerNode createPanner(); - [NewObject] + [NewObject, Throws] ConvolverNode createConvolver(); [NewObject, Throws] @@ -68,10 +82,10 @@ interface AudioContext : EventTarget { [NewObject, Throws] ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6); - [NewObject] + [NewObject, Throws] DynamicsCompressorNode createDynamicsCompressor(); - [NewObject] + [NewObject, Throws] OscillatorNode createOscillator(); [NewObject, Throws] PeriodicWave createPeriodicWave(Float32Array real, Float32Array imag); From bcc9883477478a85b4f5c9ee68d946456ab20d71 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Thu, 16 Apr 2015 21:55:57 +0900 Subject: [PATCH 33/43] Bug 1155048 - Remove redundant return statement in CustomizableUI.jsm. r=Gijs --- browser/components/customizableui/CustomizableUI.jsm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm index 246983f802c..41c92df2db0 100644 --- a/browser/components/customizableui/CustomizableUI.jsm +++ b/browser/components/customizableui/CustomizableUI.jsm @@ -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 From 77c9a07ff5e6a1ab34d285e4bbe04df78af6c449 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Thu, 16 Apr 2015 21:55:59 +0900 Subject: [PATCH 34/43] Bug 1154234 - Remove unnecessary return in BezierCanvas.prototype.plot in CubicBezierWidget.js. r=pbrosset --- browser/devtools/shared/widgets/CubicBezierWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/devtools/shared/widgets/CubicBezierWidget.js b/browser/devtools/shared/widgets/CubicBezierWidget.js index 45078f03e3f..8b7dcb08c80 100644 --- a/browser/devtools/shared/widgets/CubicBezierWidget.js +++ b/browser/devtools/shared/widgets/CubicBezierWidget.js @@ -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(); }; From 3c8061eb0fe10a0b04ee9283d319c6cdef544646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Pag=C3=A8s?= Date: Fri, 27 Feb 2015 15:11:14 -0500 Subject: [PATCH 35/43] Bug 1124695 - [mozrunner] and/or [mozprocess] should send SIGTERM before sending SIGKILL by default; r=ahal --- .../mozprocess/mozprocess/processhandler.py | 35 ++++++++++++++----- .../mozbase/mozprocess/tests/infinite_loop.py | 18 ++++++++++ .../mozprocess/tests/test_mozprocess_kill.py | 22 ++++++++++++ 3 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 testing/mozbase/mozprocess/tests/infinite_loop.py diff --git a/testing/mozbase/mozprocess/mozprocess/processhandler.py b/testing/mozbase/mozprocess/mozprocess/processhandler.py index a6d2befa12d..8268e67208d 100644 --- a/testing/mozbase/mozprocess/mozprocess/processhandler.py +++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py @@ -65,6 +65,7 @@ class ProcessHandlerMixin(object): MAX_IOCOMPLETION_PORT_NOTIFICATION_DELAY = 180 MAX_PROCESS_KILL_DELAY = 30 + TIMEOUT_BEFORE_SIGKILL = 1.0 def __init__(self, args, @@ -144,16 +145,32 @@ class ProcessHandlerMixin(object): if err is not None: raise OSError(err) else: - sig = sig or signal.SIGKILL - if not self._ignore_children: - try: - os.killpg(self.pid, sig) - except BaseException, e: - if getattr(e, "errno", None) != 3: - # Error 3 is "no such process", which is ok - print >> sys.stdout, "Could not kill process, could not find pid: %s, assuming it's already dead" % self.pid + def send_sig(sig): + if not self._ignore_children: + try: + os.killpg(self.pid, sig) + except BaseException, e: + if getattr(e, "errno", None) != 3: + # Error 3 is "no such process", which is ok + print >> sys.stdout, "Could not kill process, could not find pid: %s, assuming it's already dead" % self.pid + else: + os.kill(self.pid, sig) + + if sig is None and isPosix: + # ask the process for termination and wait a bit + send_sig(signal.SIGTERM) + limit = time.time() + self.TIMEOUT_BEFORE_SIGKILL + while time.time() <= limit: + if self.poll() is not None: + # process terminated nicely + break + time.sleep(0.02) + else: + # process did not terminate - send SIGKILL to force + send_sig(signal.SIGKILL) else: - os.kill(self.pid, sig) + # a signal was explicitly set or not posix + send_sig(sig or signal.SIGKILL) self.returncode = self.wait() self._cleanup() diff --git a/testing/mozbase/mozprocess/tests/infinite_loop.py b/testing/mozbase/mozprocess/tests/infinite_loop.py new file mode 100644 index 00000000000..e38e425e054 --- /dev/null +++ b/testing/mozbase/mozprocess/tests/infinite_loop.py @@ -0,0 +1,18 @@ +import threading +import time +import sys +import signal + +if 'deadlock' in sys.argv: + lock = threading.Lock() + + def trap(sig, frame): + lock.acquire() + + # get the lock once + lock.acquire() + # and take it again on SIGTERM signal: deadlock. + signal.signal(signal.SIGTERM, trap) + +while 1: + time.sleep(1) diff --git a/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py b/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py index 19859f4a989..08261951d60 100644 --- a/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py +++ b/testing/mozbase/mozprocess/tests/test_mozprocess_kill.py @@ -4,6 +4,7 @@ import os import time import unittest import proctest +import signal from mozprocess import processhandler here = os.path.dirname(os.path.abspath(__file__)) @@ -80,5 +81,26 @@ class ProcTestKill(proctest.ProcTest): p.didTimeout, expectedfail=('returncode',)) + @unittest.skipUnless(processhandler.isPosix, "posix only") + def test_process_kill_with_sigterm(self): + script = os.path.join(here, 'infinite_loop.py') + p = processhandler.ProcessHandler([self.python, script]) + + p.run() + p.kill() + + self.assertEquals(p.proc.returncode, -signal.SIGTERM) + + @unittest.skipUnless(processhandler.isPosix, "posix only") + def test_process_kill_with_sigint_if_needed(self): + script = os.path.join(here, 'infinite_loop.py') + p = processhandler.ProcessHandler([self.python, script, 'deadlock']) + + p.run() + time.sleep(1) + p.kill() + + self.assertEquals(p.proc.returncode, -signal.SIGKILL) + if __name__ == '__main__': unittest.main() From 480c7ce21e9e24fca16e5af7cd22b5066c95d5c5 Mon Sep 17 00:00:00 2001 From: EKR Date: Thu, 16 Apr 2015 06:33:49 -0700 Subject: [PATCH 36/43] Bug 1151080: Rewrite NR_async_timer_set(0) to use direct dispatch. r=mt --- media/mtransport/nr_timer.cpp | 144 ++++++++++++++++---- media/mtransport/test/nrappkit_unittest.cpp | 27 +++- 2 files changed, 141 insertions(+), 30 deletions(-) diff --git a/media/mtransport/nr_timer.cpp b/media/mtransport/nr_timer.cpp index ea495b1c41d..2172cddfd8f 100644 --- a/media/mtransport/nr_timer.cpp +++ b/media/mtransport/nr_timer.cpp @@ -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 GetSTSThread() { nsresult rv; nsCOMPtr 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 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(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(handle); - - timer->Cancel(); - // Allow the timer to go away. - timer->Release(); + nrappkitCallback* callback = static_cast(handle); + callback->Cancel(); return 0; } diff --git a/media/mtransport/test/nrappkit_unittest.cpp b/media/mtransport/test/nrappkit_unittest.cpp index 55f459a001b..005c90922ab 100644 --- a/media/mtransport/test/nrappkit_unittest.cpp +++ b/media/mtransport/test/nrappkit_unittest.cpp @@ -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); From 312251baddb1ce410840271de6f93f443366aa66 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 16 Apr 2015 15:35:19 +0200 Subject: [PATCH 37/43] Bug 1148354 - Deprecate the doppler effect from the PannerNode. r=ehsan --- dom/base/nsDeprecatedOperationList.h | 1 + dom/locales/en-US/chrome/dom/dom.properties | 2 ++ dom/webidl/AudioListener.webidl | 9 ++++++--- dom/webidl/PannerNode.webidl | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h index 87bf6a2f059..669dfbd41d8 100644 --- a/dom/base/nsDeprecatedOperationList.h +++ b/dom/base/nsDeprecatedOperationList.h @@ -40,3 +40,4 @@ DEPRECATED_OPERATION(SyncXMLHttpRequest) DEPRECATED_OPERATION(DataContainerEvent) DEPRECATED_OPERATION(Window_Controllers) DEPRECATED_OPERATION(ImportXULIntoContent) +DEPRECATED_OPERATION(PannerNodeDoppler) diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index aa5bca20315..969ba82f9f4 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -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 diff --git a/dom/webidl/AudioListener.webidl b/dom/webidl/AudioListener.webidl index 91e8cdb354a..e3470168b20 100644 --- a/dom/webidl/AudioListener.webidl +++ b/dom/webidl/AudioListener.webidl @@ -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); }; diff --git a/dom/webidl/PannerNode.webidl b/dom/webidl/PannerNode.webidl index f346198a9f9..e0bd4387cf7 100644 --- a/dom/webidl/PannerNode.webidl +++ b/dom/webidl/PannerNode.webidl @@ -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 From e44bd1f136d79d841bac2e25d8978cb6b50635ab Mon Sep 17 00:00:00 2001 From: Sotaro Ikeda Date: Thu, 16 Apr 2015 06:46:01 -0700 Subject: [PATCH 38/43] Bug 1153344 - Suppress DispatchOnAudioSinkComplete() r=cpearce --- dom/media/AudioSink.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dom/media/AudioSink.cpp b/dom/media/AudioSink.cpp index a4f57df1292..b1465d20ff3 100644 --- a/dom/media/AudioSink.cpp +++ b/dom/media/AudioSink.cpp @@ -256,7 +256,11 @@ AudioSink::Cleanup() AssertCurrentThreadInMonitor(); nsRefPtr 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(); From a73b52ebf9d0dc65ff686986def9e8095fb0d2ef Mon Sep 17 00:00:00 2001 From: Andreas Tolfsen Date: Wed, 15 Apr 2015 12:18:00 +0100 Subject: [PATCH 39/43] Bug 1153832: New dispatch style framework in Marionette listener Takes advantage of the new dispatching technique introduced in bug 1107706 on the content side. The patch introduces the framework to write simpler command handlers in content space, but does not convert all commands in listener.js to use this. This can be done gradually, as both techniques are still compatible. r=dburns --- testing/marionette/driver.js | 17 +- testing/marionette/elements.js | 47 ++- testing/marionette/error.js | 55 ++-- testing/marionette/listener.js | 505 ++++++++++++++++----------------- testing/marionette/sendkeys.js | 10 +- 5 files changed, 315 insertions(+), 319 deletions(-) diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index 00292b52625..0067e7fef8a 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -155,8 +155,7 @@ ListenerProxy.prototype.__noSuchMethod__ = function*(name, args) { let okListener = () => resolve(); let valListener = msg => resolve(msg.json.value); - let errListener = msg => reject( - "error" in msg.objects ? msg.objects.error : msg.json); + let errListener = msg => reject(msg.objects.error); let handleDialog = function(subject, topic) { listeners.remove(); @@ -2062,7 +2061,7 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) { // listen for it and then just send an error back. The person making the // call should be aware something isnt right and handle accordingly this.addFrameCloseListener("click"); - yield this.listener.clickElement({id: id}); + yield this.listener.clickElement(id); break; } }; @@ -2086,7 +2085,7 @@ GeckoDriver.prototype.getElementAttribute = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementAttribute({id: id, name: name}); + resp.value = yield this.listener.getElementAttribute(id, name); break; } }; @@ -2112,7 +2111,7 @@ GeckoDriver.prototype.getElementText = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementText({id: id}); + resp.value = yield this.listener.getElementText(id); break; } }; @@ -2134,7 +2133,7 @@ GeckoDriver.prototype.getElementTagName = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementTagName({id: id}); + resp.value = yield this.listener.getElementTagName(id); break; } }; @@ -2224,7 +2223,7 @@ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.isElementEnabled({id: id}); + resp.value = yield this.listener.isElementEnabled(id); break; } }, @@ -2270,7 +2269,7 @@ GeckoDriver.prototype.getElementSize = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementSize({id: id}); + resp.value = yield this.listener.getElementSize(id); break; } }; @@ -2292,7 +2291,7 @@ GeckoDriver.prototype.getElementRect = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementRect({id: id}); + resp.value = yield this.listener.getElementRect(id); break; } }; diff --git a/testing/marionette/elements.js b/testing/marionette/elements.js index 884a0f8de1a..53f13559bba 100644 --- a/testing/marionette/elements.js +++ b/testing/marionette/elements.js @@ -1,8 +1,11 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +let {utils: Cu} = Components; + +Cu.import("chrome://marionette/content/error.js"); + /** * The ElementManager manages DOM references and interactions with elements. * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the @@ -28,8 +31,8 @@ this.EXPORTED_SYMBOLS = [ const DOCUMENT_POSITION_DISCONNECTED = 1; -let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"] - .getService(Components.interfaces.nsIUUIDGenerator); +const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"] + .getService(Components.interfaces.nsIUUIDGenerator); this.CLASS_NAME = "class name"; this.SELECTOR = "css selector"; @@ -42,12 +45,6 @@ this.XPATH = "xpath"; this.ANON= "anon"; this.ANON_ATTRIBUTE = "anon attribute"; -function ElementException(msg, num, stack) { - this.message = msg; - this.code = num; - this.stack = stack; -} - this.Accessibility = function Accessibility() { // A flag indicating whether the accessibility issue should be logged or cause // an exception. Default: log to stdout. @@ -185,7 +182,7 @@ Accessibility.prototype = { return; } if (this.strict) { - throw new ElementException(message, 56, null); + throw new ElementNotAccessibleError(message); } dump(Date.now() + " Marionette: " + message); } @@ -224,19 +221,17 @@ ElementManager.prototype = { let foundEl = null; try { foundEl = this.seenItems[i].get(); - } - catch(e) {} + } catch (e) {} if (foundEl) { if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(element)) { return i; } - } - else { - //cleanup reference to GC'd element + } else { + // cleanup reference to GC'd element delete this.seenItems[i]; } } - var id = uuidGen.generateUUID().toString(); + let id = uuidGen.generateUUID().toString(); this.seenItems[id] = Components.utils.getWeakReference(element); return id; }, @@ -255,7 +250,7 @@ ElementManager.prototype = { getKnownElement: function EM_getKnownElement(id, win) { let el = this.seenItems[id]; if (!el) { - throw new ElementException("Element has not been seen before. Id given was " + id, 17, null); + throw new JavaScriptError("Element has not been seen before. Id given was " + id); } try { el = el.get(); @@ -270,8 +265,9 @@ ElementManager.prototype = { !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) || (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) & DOCUMENT_POSITION_DISCONNECTED)) { - throw new ElementException("The element reference is stale. Either the element " + - "is no longer attached to the DOM or the page has been refreshed.", 10, null); + throw new StaleElementReferenceError( + "The element reference is stale. Either the element " + + "is no longer attached to the DOM or the page has been refreshed."); } return el; }, @@ -369,8 +365,9 @@ ElementManager.prototype = { args.hasOwnProperty(this.w3cElementKey))) { let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey]; converted = this.getKnownElement(elementUniqueIdentifier, win); - if (converted == null) - throw new ElementException("Unknown element: " + elementUniqueIdentifier, 500, null); + if (converted == null) { + throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`); + } } else { converted = {}; @@ -443,7 +440,7 @@ ElementManager.prototype = { let startNode = (values.element != undefined) ? this.getKnownElement(values.element, win) : win.document; if (this.elementStrategies.indexOf(values.using) < 0) { - throw new ElementException("No such strategy.", 32, null); + throw new InvalidSelectorError(`No such strategy: ${values.using}`); } let found = all ? this.findElements(values.using, values.value, win.document, startNode) : this.findElement(values.using, values.value, win.document, startNode); @@ -461,7 +458,7 @@ ElementManager.prototype = { } else if (values.using == ANON_ATTRIBUTE) { message = "Unable to locate anonymous element: " + JSON.stringify(values.value); } - on_error({message: message, code: 7}, command_id); + on_error(new NoSuchElementError(message), command_id); } } else { values.time = startTime; @@ -594,7 +591,7 @@ ElementManager.prototype = { element = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]); break; default: - throw new ElementException("No such strategy", 500, null); + throw new WebDriverError("No such strategy"); } return element; }, @@ -661,7 +658,7 @@ ElementManager.prototype = { } break; default: - throw new ElementException("No such strategy", 500, null); + throw new WebDriverError("No such strategy"); } return elements; }, diff --git a/testing/marionette/error.js b/testing/marionette/error.js index 9f664fa3726..86bcd4e8b41 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -7,11 +7,13 @@ const {utils: Cu} = Components; const errors = [ + "ElementNotAccessibleError", "ElementNotVisibleError", "FrameSendFailureError", "FrameSendNotInitializedError", "IllegalArgumentError", "InvalidElementStateError", + "InvalidSelectorError", "JavaScriptError", "NoAlertOpenError", "NoSuchElementError", @@ -19,6 +21,7 @@ const errors = [ "NoSuchWindowError", "ScriptTimeoutError", "SessionNotCreatedError", + "StaleElementReferenceError", "TimeoutError", "UnknownCommandError", "UnknownError", @@ -38,11 +41,6 @@ error.toJSON = function(err) { }; }; -/** - * Gets WebDriver error by its Selenium status code number. - */ -error.byCode = n => lookup.get(n); - /** * Determines if the given status code is successful. */ @@ -130,6 +128,14 @@ this.WebDriverError = function(msg) { }; WebDriverError.prototype = Object.create(Error.prototype); +this.ElementNotAccessibleError = function(msg) { + WebDriverError.call(this, msg); + this.name = "ElementNotAccessibleError"; + this.status = "element not accessible"; + this.code = 56; +}; +ElementNotAccessibleError.prototype = Object.create(WebDriverError.prototype); + this.ElementNotVisibleError = function(msg) { WebDriverError.call(this, msg); this.name = "ElementNotVisibleError"; @@ -176,6 +182,14 @@ this.InvalidElementStateError = function(msg) { }; InvalidElementStateError.prototype = Object.create(WebDriverError.prototype); +this.InvalidSelectorError = function(msg) { + WebDriverError.call(this, msg); + this.name = "InvalidSelectorError"; + this.status = "invalid selector"; + this.code = 32; +}; +InvalidSelectorError.prototype = Object.create(WebDriverError.prototype); + /** * Creates an error message for a JavaScript error thrown during * executeScript or executeAsyncScript. @@ -270,9 +284,17 @@ this.SessionNotCreatedError = function(msg) { this.status = "session not created"; // should be 33 to match Selenium this.code = 71; -} +}; SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype); +this.StaleElementReferenceError = function(msg) { + WebDriverError.call(this, msg); + this.name = "StaleElementReferenceError"; + this.status = "stale element reference"; + this.code = 10; +}; +StaleElementReferenceError.prototype = Object.create(WebDriverError.prototype); + this.TimeoutError = function(msg) { WebDriverError.call(this, msg); this.name = "TimeoutError"; @@ -304,24 +326,3 @@ this.UnsupportedOperationError = function(msg) { this.code = 405; }; UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype); - -const errorObjs = [ - this.ElementNotVisibleError, - this.FrameSendFailureError, - this.FrameSendNotInitializedError, - this.IllegalArgumentError, - this.InvalidElementStateError, - this.JavaScriptError, - this.NoAlertOpenError, - this.NoSuchElementError, - this.NoSuchFrameError, - this.NoSuchWindowError, - this.ScriptTimeoutError, - this.SessionNotCreatedError, - this.TimeoutError, - this.UnknownCommandError, - this.UnknownError, - this.UnsupportedOperationError, - this.WebDriverError, -]; -const lookup = new Map(errorObjs.map(err => [new err().code, err])); diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index 33822d5cd4d..53d3ae5a34c 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -15,6 +15,7 @@ loader.loadSubScript("chrome://marionette/content/simpletest.js"); loader.loadSubScript("chrome://marionette/content/common.js"); loader.loadSubScript("chrome://marionette/content/actions.js"); Cu.import("chrome://marionette/content/elements.js"); +Cu.import("chrome://marionette/content/error.js"); Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -93,7 +94,7 @@ let modalHandler = function() { * If the actor returns an ID, we start the listeners. Otherwise, nothing happens. */ function registerSelf() { - let msg = {value: winUtil.outerWindowID} + let msg = {value: winUtil.outerWindowID}; // register will have the ID and a boolean describing if this is the main process or not let register = sendSyncMessage("Marionette:register", msg); @@ -146,6 +147,28 @@ function emitTouchEventForIFrame(message) { message.force, 90); } +function dispatch(fn) { + return function(msg) { + let id = msg.json.command_id; + try { + let rv; + if (typeof msg.json == "undefined" || msg.json instanceof Array) { + rv = fn.apply(null, msg.json); + } else { + rv = fn(msg.json); + } + + if (typeof rv == "undefined") { + sendOk(id); + } else { + sendResponse({value: rv}, id); + } + } catch (e) { + sendError(e, id); + } + }; +} + /** * Add a message listener that's tied to our listenerId. */ @@ -160,6 +183,15 @@ function removeMessageListenerId(messageName, handler) { removeMessageListener(messageName + listenerId, handler); } +let getElementSizeFn = dispatch(getElementSize); +let getActiveElementFn = dispatch(getActiveElement); +let clickElementFn = dispatch(clickElement); +let getElementAttributeFn = dispatch(getElementAttribute); +let getElementTextFn = dispatch(getElementText); +let getElementTagNameFn = dispatch(getElementTagName); +let getElementRectFn = dispatch(getElementRect); +let isElementEnabledFn = dispatch(isElementEnabled); + /** * Start all message listeners */ @@ -182,17 +214,17 @@ function startListeners() { addMessageListenerId("Marionette:refresh", refresh); addMessageListenerId("Marionette:findElementContent", findElementContent); addMessageListenerId("Marionette:findElementsContent", findElementsContent); - addMessageListenerId("Marionette:getActiveElement", getActiveElement); - addMessageListenerId("Marionette:clickElement", clickElement); - addMessageListenerId("Marionette:getElementAttribute", getElementAttribute); - addMessageListenerId("Marionette:getElementText", getElementText); - addMessageListenerId("Marionette:getElementTagName", getElementTagName); + addMessageListenerId("Marionette:getActiveElement", getActiveElementFn); + addMessageListenerId("Marionette:clickElement", clickElementFn); + addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn); + addMessageListenerId("Marionette:getElementText", getElementTextFn); + addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn); addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); addMessageListenerId("Marionette:submitElement", submitElement); - addMessageListenerId("Marionette:getElementSize", getElementSize); - addMessageListenerId("Marionette:getElementRect", getElementRect); - addMessageListenerId("Marionette:isElementEnabled", isElementEnabled); + addMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated + addMessageListenerId("Marionette:getElementRect", getElementRectFn); + addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); addMessageListenerId("Marionette:isElementSelected", isElementSelected); addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); addMessageListenerId("Marionette:getElementLocation", getElementLocation); //deprecated @@ -287,17 +319,17 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:refresh", refresh); removeMessageListenerId("Marionette:findElementContent", findElementContent); removeMessageListenerId("Marionette:findElementsContent", findElementsContent); - removeMessageListenerId("Marionette:getActiveElement", getActiveElement); - removeMessageListenerId("Marionette:clickElement", clickElement); - removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute); - removeMessageListenerId("Marionette:getElementText", getElementText); - removeMessageListenerId("Marionette:getElementTagName", getElementTagName); + removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn); + removeMessageListenerId("Marionette:clickElement", clickElementFn); + removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn); + removeMessageListenerId("Marionette:getElementText", getElementTextFn); + removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn); removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); removeMessageListenerId("Marionette:submitElement", submitElement); - removeMessageListenerId("Marionette:getElementSize", getElementSize); //deprecated - removeMessageListenerId("Marionette:getElementRect", getElementRect); - removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled); + removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated + removeMessageListenerId("Marionette:getElementRect", getElementRectFn); + removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); removeMessageListenerId("Marionette:isElementSelected", isElementSelected); removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); removeMessageListenerId("Marionette:getElementLocation", getElementLocation); @@ -331,40 +363,42 @@ function deleteSession(msg) { /** * Generic method to send a message to the server */ -function sendToServer(msg, value, command_id) { - if (command_id) { - value.command_id = command_id; +function sendToServer(name, data, objs, id) { + if (!data) { + data = {} } - sendAsyncMessage(msg, value); + if (id) { + data.command_id = id; + } + sendAsyncMessage(name, data, objs); } /** * Send response back to server */ function sendResponse(value, command_id) { - sendToServer("Marionette:done", value, command_id); + sendToServer("Marionette:done", value, null, command_id); } /** * Send ack back to server */ function sendOk(command_id) { - sendToServer("Marionette:ok", {}, command_id); + sendToServer("Marionette:ok", null, null, command_id); } /** * Send log message to server */ function sendLog(msg) { - sendToServer("Marionette:log", { message: msg }); + sendToServer("Marionette:log", {message: msg}); } /** * Send error message to server */ -function sendError(msg, code, stack, cmdId) { - let payload = {message: msg, code: code, stack: stack}; - sendToServer("Marionette:error", payload, cmdId); +function sendError(err, cmdId) { + sendToServer("Marionette:error", null, {error: err}, cmdId); } /** @@ -458,8 +492,8 @@ function createExecuteContentSandbox(aWindow, timeout) { }); } - sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) { - if (commandId == asyncTestCommandId) { + sandbox.asyncComplete = function(obj, id) { + if (id == asyncTestCommandId) { curFrame.removeEventListener("unload", onunload, false); curFrame.clearTimeout(asyncTestTimeoutId); @@ -467,24 +501,19 @@ function createExecuteContentSandbox(aWindow, timeout) { curFrame.clearTimeout(inactivityTimeoutId); } - sendSyncMessage("Marionette:shareData", - {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); + {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); - if (status == 0){ + if (error.isError(obj)) { + sendError(obj, id); + } else { if (Object.keys(_emu_cbs).length) { _emu_cbs = {}; - sendError("Emulator callback still pending when finish() called", - 500, null, commandId); + sendError(new WebDriverError("Emulator callback still pending when finish() called"), id); + } else { + sendResponse({value: elementManager.wrapValue(obj)}, id); } - else { - sendResponse({value: elementManager.wrapValue(value), status: status}, - commandId); - } - } - else { - sendError(value, status, stack, commandId); } asyncTestRunning = false; @@ -495,33 +524,32 @@ function createExecuteContentSandbox(aWindow, timeout) { }; sandbox.finish = function sandbox_finish() { if (asyncTestRunning) { - sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId); + sandbox.asyncComplete(marionette.generate_results(), sandbox.asyncTestCommandId); } else { return marionette.generate_results(); } }; - sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) { - return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId); - }; + sandbox.marionetteScriptFinished = val => + sandbox.asyncComplete(val, sandbox.asyncTestCommandId); return sandbox; } /** * Execute the given script either as a function body (executeScript) - * or directly (for 'mochitest' like JS Marionette tests) + * or directly (for mochitest like JS Marionette tests). */ function executeScript(msg, directInject) { // Set up inactivity timeout. if (msg.json.inactivityTimeout) { let setTimer = function() { - inactivityTimeoutId = curFrame.setTimeout(function() { - sendError('timed out due to inactivity', 28, null, asyncTestCommandId); + inactivityTimeoutId = curFrame.setTimeout(function() { + sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); - heartbeatCallback = function resetInactivityTimeout() { + heartbeatCallback = function() { curFrame.clearTimeout(inactivityTimeoutId); setTimer(); }; @@ -534,11 +562,10 @@ function executeScript(msg, directInject) { sandbox = createExecuteContentSandbox(curFrame, msg.json.timeout); if (!sandbox) { - sendError("Could not create sandbox!", 500, null, asyncTestCommandId); + sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId); return; } - } - else { + } else { sandbox.asyncTestCommandId = asyncTestCommandId; } @@ -558,7 +585,7 @@ function executeScript(msg, directInject) { marionetteLogObj.clearLogs(); if (res == undefined || res.passed == undefined) { - sendError("Marionette.finish() not called", 17, null, asyncTestCommandId); + sendError(new JavaScriptError("Marionette.finish() not called"), asyncTestCommandId); } else { sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); @@ -568,9 +595,8 @@ function executeScript(msg, directInject) { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( msg.json.args, curFrame), sandbox, { wrapReflectors: true }); - } - catch(e) { - sendError(e.message, e.code, e.stack, asyncTestCommandId); + } catch (e) { + sendError(e, asyncTestCommandId); return; } @@ -590,15 +616,14 @@ function executeScript(msg, directInject) { marionetteLogObj.clearLogs(); sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); } - } - catch (e) { - // 17 = JavascriptException - let error = createStackMessage(e, - "execute_script", - msg.json.filename, - msg.json.line, - script); - sendError(error[0], 17, error[1], asyncTestCommandId); + } catch (e) { + let err = new JavaScriptError( + e, + "execute_script", + msg.json.filename, + msg.json.line, + script); + sendError(err, asyncTestCommandId); } } @@ -642,12 +667,12 @@ function executeWithCallback(msg, useFinish) { if (msg.json.inactivityTimeout) { let setTimer = function() { inactivityTimeoutId = curFrame.setTimeout(function() { - sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId); + sandbox.asyncComplete(new ScriptTimeout("timed out due to inactivity"), asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); - heartbeatCallback = function resetInactivityTimeout() { + heartbeatCallback = function() { curFrame.clearTimeout(inactivityTimeoutId); setTimer(); }; @@ -657,7 +682,7 @@ function executeWithCallback(msg, useFinish) { asyncTestCommandId = msg.json.command_id; onunload = function() { - sendError("unload was called", 17, null, asyncTestCommandId); + sendError(new JavaScriptError("unload was called"), asyncTestCommandId); }; curFrame.addEventListener("unload", onunload, false); @@ -665,7 +690,7 @@ function executeWithCallback(msg, useFinish) { sandbox = createExecuteContentSandbox(curFrame, msg.json.timeout); if (!sandbox) { - sendError("Could not create sandbox!", 17, null, asyncTestCommandId); + sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId); return; } } @@ -680,19 +705,19 @@ function executeWithCallback(msg, useFinish) { // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js. // We'll stay compatible with the Selenium code. asyncTestTimeoutId = curFrame.setTimeout(function() { - sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId); + sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId); }, msg.json.timeout); originalOnError = curFrame.onerror; - curFrame.onerror = function errHandler(errMsg, url, line) { - sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId); + curFrame.onerror = function errHandler(msg, url, line) { + sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId); curFrame.onerror = originalOnError; }; let scriptSrc; if (useFinish) { if (msg.json.timeout == null || msg.json.timeout == 0) { - sendError("Please set a timeout", 21, null, asyncTestCommandId); + sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId); } scriptSrc = script; } @@ -700,9 +725,8 @@ function executeWithCallback(msg, useFinish) { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( msg.json.args, curFrame), sandbox, { wrapReflectors: true }); - } - catch(e) { - sendError(e.message, e.code, e.stack, asyncTestCommandId); + } catch (e) { + sendError(e, asyncTestCommandId); return; } @@ -723,13 +747,13 @@ function executeWithCallback(msg, useFinish) { } Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0); } catch (e) { - // 17 = JavascriptException - let error = createStackMessage(e, - "execute_async_script", - msg.json.filename, - msg.json.line, - scriptSrc); - sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId); + let err = new JavaScriptError( + e, + "execute_async_script", + msg.json.filename, + msg.json.line, + scriptSrc); + sandbox.asyncComplete(err, asyncTestCommandId); } } @@ -856,7 +880,7 @@ function singleTap(msg) { let visible = checkVisible(el, msg.json.corx, msg.json.cory); checkVisibleAccessibility(acc, visible); if (!visible) { - sendError("Element is not currently visible and may not be manipulated", 11, null, command_id); + sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id); return; } checkActionableAccessibility(acc); @@ -871,10 +895,9 @@ function singleTap(msg) { emitTouchEvent('touchend', touch); } actions.mouseTap(el.ownerDocument, c.x, c.y); - sendOk(msg.json.command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, msg.json.command_id); + sendOk(command_id); + } catch (e) { + sendError(e, command_id); } } @@ -959,12 +982,8 @@ function actionChain(msg) { let touchId = msg.json.nextId; let callbacks = {}; - callbacks.onSuccess = (value) => { - sendResponse(value, command_id); - }; - callbacks.onError = (message, code, trace) => { - sendError(message, code, trace, msg.json.command_id); - }; + callbacks.onSuccess = value => sendResponse(value, command_id); + callbacks.onError = err => sendError(err, command_id); let touchProvider = {}; touchProvider.createATouch = createATouch; @@ -979,7 +998,7 @@ function actionChain(msg) { callbacks, touchProvider); } catch (e) { - sendError(e.message, e.code, e.stack, command_id); + sendError(e, command_id); } } @@ -1144,9 +1163,8 @@ function multiAction(msg) { // pendingTouches keeps track of current touches that's on the screen let pendingTouches = []; setDispatch(concurrentEvent, pendingTouches, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, msg.json.command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1178,7 +1196,7 @@ function pollForReadyState(msg, start, callback) { !curFrame.document.baseURI.startsWith(url)) { // We have reached an error url without requesting it. callback(); - sendError("Error loading page", 13, null, command_id); + sendError(new UnknownError("Error loading page"), command_id); } else if (curFrame.document.readyState == "interactive" && curFrame.document.baseURI.startsWith("about:")) { callback(); @@ -1186,11 +1204,9 @@ function pollForReadyState(msg, start, callback) { } else { navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); } - } - else { + } else { callback(); - sendError("Error loading page, timed out (checkLoad)", 21, null, - command_id); + sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id); } } checkLoad(); @@ -1220,8 +1236,7 @@ function get(msg) { function timerFunc() { removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); - sendError("Error loading page, timed out (onDOMContentLoaded)", 21, - null, msg.json.command_id); + sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id); } if (msg.json.pageTimeout != null) { navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT); @@ -1306,13 +1321,12 @@ function refresh(msg) { function findElementContent(msg) { let command_id = msg.json.command_id; try { - let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) }; - let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); }; + let onSuccess = (el, id) => sendResponse({value: el}, id); + let onError = (err, id) => sendError(err, id); elementManager.find(curFrame, msg.json, msg.json.searchTimeout, - false /* all */, on_success, on_error, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + false /* all */, onSuccess, onError, command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1322,97 +1336,90 @@ function findElementContent(msg) { function findElementsContent(msg) { let command_id = msg.json.command_id; try { - let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); }; - let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); }; + let onSuccess = (els, id) => sendResponse({value: els}, id); + let onError = (err, id) => sendError(err, id); elementManager.find(curFrame, msg.json, msg.json.searchTimeout, - true /* all */, on_success, on_error, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + true /* all */, onSuccess, onError, command_id); + } catch (e) { + sendError(e, command_id); } } /** - * Find and return the active element on the page + * Find and return the active element on the page. + * + * @return {WebElement} + * Reference to web element. */ -function getActiveElement(msg) { - let command_id = msg.json.command_id; - var element = curFrame.document.activeElement; - var id = elementManager.addToKnownElements(element); - sendResponse({value: id}, command_id); +function getActiveElement() { + let el = curFrame.document.activeElement; + return elementManager.addToKnownElements(el); } /** - * Send click event to element + * Send click event to element. + * + * @param {WebElement} id + * Reference to the web element to click. */ -function clickElement(msg) { - let command_id = msg.json.command_id; - let el; - try { - el = elementManager.getKnownElement(msg.json.id, curFrame); - let acc = accessibility.getAccessibleObject(el, true); - let visible = checkVisible(el); - checkVisibleAccessibility(acc, visible); - if (visible) { - checkActionableAccessibility(acc); - if (utils.isElementEnabled(el)) { - utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView) - } - else { - sendError("Element is not Enabled", 12, null, command_id) - } - } - else { - sendError("Element is not visible", 11, null, command_id) - } - sendOk(command_id); +function clickElement(id) { + let el = elementManager.getKnownElement(id, curFrame); + let acc = accessibility.getAccessibleObject(el, true); + let visible = checkVisible(el); + checkVisibleAccessibility(acc, visible); + if (!visible) { + throw new ElementNotVisibleError("Element is not visible"); } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + checkActionableAccessibility(acc); + if (utils.isElementEnabled(el)) { + utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView); + } else { + throw new InvalidElementStateError("Element is not Enabled"); } } /** - * Get a given attribute of an element + * Get a given attribute of an element. + * + * @param {WebElement} id + * Reference to the web element to get the attribute of. + * @param {string} name + * Name of the attribute. + * + * @return {string} + * The value of the attribute. */ -function getElementAttribute(msg) { - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - sendResponse({value: utils.getElementAttribute(el, msg.json.name)}, - command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function getElementAttribute(id, name) { + let el = elementManager.getKnownElement(id, curFrame); + return utils.getElementAttribute(el, name); } /** * Get the text of this element. This includes text from child elements. + * + * @param {WebElement} id + * Reference to web element. + * + * @return {string} + * Text of element. */ -function getElementText(msg) { - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - sendResponse({value: utils.getElementText(el)}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function getElementText(id) { + let el = elementManager.getKnownElement(id, curFrame); + return utils.getElementText(el); } /** * Get the tag name of an element. + * + * @param {WebElement} id + * Reference to web element. + * + * @return {string} + * Tag name of element. */ -function getElementTagName(msg) { - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - sendResponse({value: el.tagName.toLowerCase()}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function getElementTagName(id) { + let el = elementManager.getKnownElement(id, curFrame); + return el.tagName.toLowerCase(); } /** @@ -1425,9 +1432,8 @@ function isElementDisplayed(msg) { let displayed = utils.isElementDisplayed(el); checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed); sendResponse({value: displayed}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1446,9 +1452,8 @@ function getElementValueOfCssProperty(msg){ let el = elementManager.getKnownElement(msg.json.id, curFrame); sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1467,67 +1472,63 @@ function submitElement (msg) { if (el.tagName && el.tagName.toLowerCase() == 'form') { el.submit(); sendOk(command_id); + } else { + sendError(new NoSuchElementError("Element is not a form element or in a form"), command_id); } - else { - sendError("Element is not a form element or in a form", 7, null, command_id); - } - - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } } /** - * Get the size of the element and return it + * Get the size of the element. + * + * @param {WebElement} id + * Web element reference. + * + * @return {Object.} + * The width/height dimensions of th element. */ -function getElementSize(msg){ - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - let clientRect = el.getBoundingClientRect(); - sendResponse({value: {width: clientRect.width, height: clientRect.height}}, - command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function getElementSize(id) { + let el = elementManager.getKnownElement(id, curFrame); + let clientRect = el.getBoundingClientRect(); + return {width: clientRect.width, height: clientRect.height}; } /** - * Get the size of the element and return it + * Get the size of the element. + * + * @param {WebElement} id + * Reference to web element. + * + * @return {Object.} + * The x, y, width, and height properties of the element. */ -function getElementRect(msg){ - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - let clientRect = el.getBoundingClientRect(); - sendResponse({value: {x: clientRect.x + curFrame.pageXOffset, - y: clientRect.y + curFrame.pageYOffset, - width: clientRect.width, - height: clientRect.height}}, - command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function getElementRect(id) { + let el = elementManager.getKnownElement(id, curFrame); + let clientRect = el.getBoundingClientRect(); + return { + x: clientRect.x + curFrame.pageXOffset, + y: clientRect.y + curFrame.pageYOffset, + width: clientRect.width, + height: clientRect.height + }; } /** - * Check if element is enabled + * Check if element is enabled. + * + * @param {WebElement} id + * Reference to web element. + * + * @return {boolean} + * True if enabled, false otherwise. */ -function isElementEnabled(msg) { - let command_id = msg.json.command_id; - try { - let el = elementManager.getKnownElement(msg.json.id, curFrame); - let enabled = utils.isElementEnabled(el); - checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), - enabled); - sendResponse({value: enabled}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); - } +function isElementEnabled(id) { + let el = elementManager.getKnownElement(id, curFrame); + let enabled = utils.isElementEnabled(el); + checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), enabled); + return enabled; } /** @@ -1538,9 +1539,8 @@ function isElementSelected(msg) { try { let el = elementManager.getKnownElement(msg.json.id, curFrame); sendResponse({value: utils.isElementSelected(el)}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1567,7 +1567,7 @@ function sendKeysToElement(msg) { file = new File(p); } catch (e) { let err = new IllegalArgumentError(`File not found: ${val}`); - sendError(err.message, err.code, err.stack, command_id); + sendError(err, command_id); return; } fs.push(file); @@ -1598,9 +1598,8 @@ function getElementLocation(msg) { location.y = rect.top; sendResponse({value: location}, command_id); - } - catch (e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } } @@ -1618,7 +1617,7 @@ function clearElement(msg) { } sendOk(command_id); } catch (e) { - sendError(e.message, e.code, e.stack, command_id); + sendError(e, command_id); } } @@ -1633,9 +1632,9 @@ function switchToFrame(msg) { if (curFrame.document.readyState == "complete") { sendOk(command_id); return; - } - else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) { - sendError("Error loading page", 13, null, command_id); + } else if (curFrame.document.readyState == "interactive" && + errorRegex.exec(curFrame.document.baseURI)) { + sendError(new UnknownError("Error loading page"), command_id); return; } checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); @@ -1675,9 +1674,8 @@ function switchToFrame(msg) { let wantedFrame; try { wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //Frame Element - } - catch(e) { - sendError(e.message, e.code, e.stack, command_id); + } catch (e) { + sendError(e, command_id); } if (frames.length > 0) { @@ -1735,8 +1733,9 @@ function switchToFrame(msg) { } } } + if (foundFrame === null) { - sendError("Unable to locate frame: " + (msg.json.id || msg.json.element), 8, null, command_id); + sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id); return true; } @@ -1777,12 +1776,11 @@ function addCookie(msg) { if (!cookie.domain) { var location = curFrame.document.location; cookie.domain = location.hostname; - } - else { + } else { var currLocation = curFrame.location; var currDomain = currLocation.host; if (currDomain.indexOf(cookie.domain) == -1) { - sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id); + sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id); } } @@ -1795,12 +1793,12 @@ function addCookie(msg) { var document = curFrame.document; if (!document || !document.contentType.match(/html/i)) { - sendError('You may only set cookies on html documents', 25, null, msg.json.command_id); + sendError(new UnableToSetCookie("You may only set cookies on html documents"), msg.json.command_id); } let added = sendSyncMessage("Marionette:addCookie", {value: cookie}); if (added[0] !== true) { - sendError("Error setting cookie", 13, null, msg.json.command_id); + sendError(new UnknownError("Error setting cookie"), msg.json.command_id); return; } sendOk(msg.json.command_id); @@ -1841,7 +1839,7 @@ function deleteCookie(msg) { if (cookie.name == toDelete) { let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie}); if (deleted[0] !== true) { - sendError("Could not delete cookie: " + msg.json.name, 13, null, msg.json.command_id); + sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id); return; } } @@ -1858,7 +1856,7 @@ function deleteAllCookies(msg) { for (let cookie of cookies) { let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie}); if (!deleted[0]) { - sendError("Could not delete cookie: " + JSON.stringify(cookie), 13, null, msg.json.command_id); + sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id); return; } } @@ -1912,9 +1910,8 @@ function emulatorCmdResult(msg) { } try { cb(message.result); - } - catch(e) { - sendError(e.message, e.code, e.stack, -1); + } catch (e) { + sendError(e, -1); return; } } diff --git a/testing/marionette/sendkeys.js b/testing/marionette/sendkeys.js index 096330afc0a..eb2b8e050f1 100644 --- a/testing/marionette/sendkeys.js +++ b/testing/marionette/sendkeys.js @@ -17,8 +17,11 @@ */ let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("chrome://marionette/content/error.js"); + let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader); + .getService(Ci.mozIJSSubScriptLoader); let utils = {}; loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils); @@ -138,8 +141,7 @@ function sendKeysToElement (document, element, keysToSend, successCallback, erro sendSingleKey(c, modifiers, document); } successCallback(command_id); - } - else { - errorCallback("Element is not visible", 11, null, command_id); + } else { + errorCallback(new ElementNotVisibleError("Element is not visible"), command_id); } }; From 4ac32b4479cf28c3e5be0366ffe943fb2bad406c Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 15 Apr 2015 16:04:49 -0400 Subject: [PATCH 40/43] Bug 870891 - move DIST_FILES to moz.build; r=mshal --- build/Makefile.in | 8 ------- build/moz.build | 5 +++++ dom/indexedDB/test/extensions/Makefile.in | 5 ----- dom/indexedDB/test/extensions/moz.build | 5 +++++ .../test/extensions/bootstrap/Makefile.in | 6 ----- .../test/extensions/bootstrap/moz.build | 6 +++++ .../test/extensions/traditional/Makefile.in | 5 ----- .../test/extensions/traditional/moz.build | 5 +++++ layout/tools/reftest/Makefile.in | 6 ----- layout/tools/reftest/moz.build | 5 +++++ mobile/android/base/Makefile.in | 4 ---- mobile/android/base/moz.build | 2 ++ .../mozbuild/backend/recursivemake.py | 10 +++++++++ python/mozbuild/mozbuild/frontend/context.py | 6 +++++ python/mozbuild/mozbuild/frontend/data.py | 16 ++++++++++++++ python/mozbuild/mozbuild/frontend/emitter.py | 11 ++++++++++ .../test/backend/data/dist-files/install.rdf | 0 .../test/backend/data/dist-files/main.js | 0 .../test/backend/data/dist-files/moz.build | 8 +++++++ .../test/backend/test_recursivemake.py | 15 +++++++++++++ .../data/dist-files-missing/install.rdf | 0 .../data/dist-files-missing/moz.build | 8 +++++++ .../test/frontend/data/dist-files/install.rdf | 0 .../test/frontend/data/dist-files/main.js | 0 .../test/frontend/data/dist-files/moz.build | 8 +++++++ .../mozbuild/test/frontend/test_emitter.py | 22 +++++++++++++++++++ testing/mochitest/Makefile.in | 1 - testing/mochitest/moz.build | 2 ++ testing/specialpowers/Makefile.in | 3 --- testing/specialpowers/moz.build | 4 +++- tools/quitter/Makefile.in | 5 ----- tools/quitter/moz.build | 7 +++++- xulrunner/examples/simple/Makefile.in | 2 -- xulrunner/examples/simple/moz.build | 3 +++ 34 files changed, 146 insertions(+), 47 deletions(-) create mode 100644 python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf create mode 100644 python/mozbuild/mozbuild/test/backend/data/dist-files/main.js create mode 100644 python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build create mode 100644 python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf create mode 100644 python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build create mode 100644 python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf create mode 100644 python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js create mode 100644 python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build diff --git a/build/Makefile.in b/build/Makefile.in index 2339e362439..5c6b151edca 100644 --- a/build/Makefile.in +++ b/build/Makefile.in @@ -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 diff --git a/build/moz.build b/build/moz.build index ed45228e8af..e314db4eca0 100644 --- a/build/moz.build +++ b/build/moz.build @@ -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'] diff --git a/dom/indexedDB/test/extensions/Makefile.in b/dom/indexedDB/test/extensions/Makefile.in index 92cd1e96590..fa75f500708 100644 --- a/dom/indexedDB/test/extensions/Makefile.in +++ b/dom/indexedDB/test/extensions/Makefile.in @@ -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) diff --git a/dom/indexedDB/test/extensions/moz.build b/dom/indexedDB/test/extensions/moz.build index a0b239cef4c..8c9c09d11fd 100644 --- a/dom/indexedDB/test/extensions/moz.build +++ b/dom/indexedDB/test/extensions/moz.build @@ -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', +] diff --git a/dom/workers/test/extensions/bootstrap/Makefile.in b/dom/workers/test/extensions/bootstrap/Makefile.in index fc120e8fe39..fa75f500708 100644 --- a/dom/workers/test/extensions/bootstrap/Makefile.in +++ b/dom/workers/test/extensions/bootstrap/Makefile.in @@ -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) diff --git a/dom/workers/test/extensions/bootstrap/moz.build b/dom/workers/test/extensions/bootstrap/moz.build index b77d4f0f255..275d6bce150 100644 --- a/dom/workers/test/extensions/bootstrap/moz.build +++ b/dom/workers/test/extensions/bootstrap/moz.build @@ -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', +] diff --git a/dom/workers/test/extensions/traditional/Makefile.in b/dom/workers/test/extensions/traditional/Makefile.in index 5a8411e016d..fa75f500708 100644 --- a/dom/workers/test/extensions/traditional/Makefile.in +++ b/dom/workers/test/extensions/traditional/Makefile.in @@ -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) diff --git a/dom/workers/test/extensions/traditional/moz.build b/dom/workers/test/extensions/traditional/moz.build index 195b079e6b9..7f43a80fcb6 100644 --- a/dom/workers/test/extensions/traditional/moz.build +++ b/dom/workers/test/extensions/traditional/moz.build @@ -16,3 +16,8 @@ EXTRA_COMPONENTS += [ ] XPI_NAME = 'worker' + +DIST_FILES += [ + 'install.rdf', + 'worker.js', +] diff --git a/layout/tools/reftest/Makefile.in b/layout/tools/reftest/Makefile.in index 0400a6502eb..8c649b94d7a 100644 --- a/layout/tools/reftest/Makefile.in +++ b/layout/tools/reftest/Makefile.in @@ -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 diff --git a/layout/tools/reftest/moz.build b/layout/tools/reftest/moz.build index 41d0bc95306..f6209bd9465 100644 --- a/layout/tools/reftest/moz.build +++ b/layout/tools/reftest/moz.build @@ -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'] diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index ac5ac598005..c3d5d4aaf3b 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -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 diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 86981dbde9a..2dcc7ab60ca 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -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'] diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index 901a9b9c5a3..05ce9e798f9 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -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() diff --git a/python/mozbuild/mozbuild/frontend/context.py b/python/mozbuild/mozbuild/frontend/context.py index f909b408f7e..4fde1823c9c 100644 --- a/python/mozbuild/mozbuild/frontend/context.py +++ b/python/mozbuild/mozbuild/frontend/context.py @@ -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. diff --git a/python/mozbuild/mozbuild/frontend/data.py b/python/mozbuild/mozbuild/frontend/data.py index abe4e8902a0..f80a15d4ec9 100644 --- a/python/mozbuild/mozbuild/frontend/data.py +++ b/python/mozbuild/mozbuild/frontend/data.py @@ -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.""" diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index 3180d3009b9..5afb73cab76 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -28,6 +28,7 @@ from .data import ( ConfigFileSubstitution, ContextWrapped, Defines, + DistFiles, DirectoryTraversal, Exports, FinalTargetFiles, @@ -641,6 +642,16 @@ class TreeMetadataEmitter(LoggingMixin): if final_target_files: yield FinalTargetFiles(context, final_target_files, context['FINAL_TARGET']) + dist_files = context.get('DIST_FILES') + if dist_files: + for f in dist_files: + path = os.path.join(context.srcdir, f) + if not os.path.exists(path): + raise SandboxValidationError('File listed in DIST_FILES ' + 'does not exist: %s' % f, context) + + yield DistFiles(context, dist_files, context['FINAL_TARGET']) + self._handle_libraries(context) for obj in self._process_test_manifests(context): diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf b/python/mozbuild/mozbuild/test/backend/data/dist-files/install.rdf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/main.js b/python/mozbuild/mozbuild/test/backend/data/dist-files/main.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build b/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build new file mode 100644 index 00000000000..51e196fcd4e --- /dev/null +++ b/python/mozbuild/mozbuild/test/backend/data/dist-files/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +DIST_FILES += [ + 'install.rdf', + 'main.js', +] diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py index c9ee468fbd1..cfebc8af0ad 100644 --- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py +++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py @@ -627,6 +627,21 @@ class TestRecursiveMakeBackend(BackendTester): str.startswith('DIST_SUBDIR')] self.assertEqual(found, expected_rules) + def test_dist_files(self): + """Test that DIST_FILES is written to backend.mk correctly.""" + env = self._consume('dist-files', RecursiveMakeBackend) + + backend_path = mozpath.join(env.topobjdir, 'backend.mk') + lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]] + + expected = [ + 'DIST_FILES += install.rdf', + 'DIST_FILES += main.js', + ] + + found = [str for str in lines if str.startswith('DIST_FILES')] + self.assertEqual(found, expected) + def test_config(self): """Test that CONFIGURE_SUBST_FILES and CONFIGURE_DEFINE_FILES are properly handled.""" diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/install.rdf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build new file mode 100644 index 00000000000..51e196fcd4e --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files-missing/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +DIST_FILES += [ + 'install.rdf', + 'main.js', +] diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf b/python/mozbuild/mozbuild/test/frontend/data/dist-files/install.rdf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js b/python/mozbuild/mozbuild/test/frontend/data/dist-files/main.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build new file mode 100644 index 00000000000..51e196fcd4e --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/data/dist-files/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +DIST_FILES += [ + 'install.rdf', + 'main.js', +] diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 68244f10c9f..825d11abf93 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -12,6 +12,7 @@ from mozunit import main from mozbuild.frontend.data import ( ConfigFileSubstitution, Defines, + DistFiles, DirectoryTraversal, Exports, GeneratedFile, @@ -811,5 +812,26 @@ class TestEmitterBasic(unittest.TestCase): self.assertEqual(sources.files, files) self.assertFalse(sources.have_unified_mapping) + def test_dist_files(self): + """Test that DIST_FILES works properly.""" + reader = self.reader('dist-files') + objs = self.read_topsrcdir(reader) + + self.assertEqual(len(objs), 1) + self.assertIsInstance(objs[0], DistFiles) + + self.assertEqual(len(objs[0].files), 2) + + expected = {'install.rdf', 'main.js'} + for f in objs[0].files: + self.assertTrue(f in expected) + + def test_missing_dist_files(self): + """Test that DIST_FILES with missing files throws errors.""" + with self.assertRaisesRegexp(SandboxValidationError, 'File listed in ' + 'DIST_FILES does not exist'): + reader = self.reader('dist-files-missing') + self.read_topsrcdir(reader) + if __name__ == '__main__': main() diff --git a/testing/mochitest/Makefile.in b/testing/mochitest/Makefile.in index 3874f8beff9..52d3658a8f3 100644 --- a/testing/mochitest/Makefile.in +++ b/testing/mochitest/Makefile.in @@ -5,7 +5,6 @@ NO_JS_MANIFEST = 1 MOZ_CHROME_FILE_FORMAT = jar -DIST_FILES = install.rdf # Used in install.rdf USE_EXTENSION_MANIFEST = 1 diff --git a/testing/mochitest/moz.build b/testing/mochitest/moz.build index 476c139eb66..c24c07a092c 100644 --- a/testing/mochitest/moz.build +++ b/testing/mochitest/moz.build @@ -18,6 +18,8 @@ XPI_NAME = 'mochijar' JAR_MANIFESTS += ['jar.mn'] +DIST_FILES += ['install.rdf'] + MOCHITEST_MANIFESTS += [ 'dynamic/mochitest.ini', 'MochiKit/mochitest.ini', diff --git a/testing/specialpowers/Makefile.in b/testing/specialpowers/Makefile.in index ec9759b9b3a..dc77bc76c0a 100644 --- a/testing/specialpowers/Makefile.in +++ b/testing/specialpowers/Makefile.in @@ -5,9 +5,6 @@ NO_JS_MANIFEST = 1 MOZ_CHROME_FILE_FORMAT = flat -DIST_FILES = \ - install.rdf \ - $(NULL) # Used in install.rdf USE_EXTENSION_MANIFEST=1 diff --git a/testing/specialpowers/moz.build b/testing/specialpowers/moz.build index 1e4e67014bd..9b2d85a8fe2 100644 --- a/testing/specialpowers/moz.build +++ b/testing/specialpowers/moz.build @@ -10,4 +10,6 @@ EXTRA_COMPONENTS += [ XPI_NAME = 'specialpowers' -JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file +JAR_MANIFESTS += ['jar.mn'] + +DIST_FILES += ['install.rdf'] diff --git a/tools/quitter/Makefile.in b/tools/quitter/Makefile.in index 0fc019d4ee9..f65e3d1e4ca 100644 --- a/tools/quitter/Makefile.in +++ b/tools/quitter/Makefile.in @@ -6,10 +6,5 @@ XPI_PKGNAME = quitter@mozilla.org NO_JS_MANIFEST = 1 -DIST_FILES = \ - install.rdf \ - chrome.manifest \ - $(NULL) - # Used in install.rdf USE_EXTENSION_MANIFEST=1 diff --git a/tools/quitter/moz.build b/tools/quitter/moz.build index 85c202ee684..763a63cb0c9 100644 --- a/tools/quitter/moz.build +++ b/tools/quitter/moz.build @@ -10,4 +10,9 @@ EXTRA_COMPONENTS += [ XPI_NAME = 'quitter' -JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file +JAR_MANIFESTS += ['jar.mn'] + +DIST_FILES += [ + 'chrome.manifest', + 'install.rdf', +] diff --git a/xulrunner/examples/simple/Makefile.in b/xulrunner/examples/simple/Makefile.in index f31c0daee05..cc894585c96 100644 --- a/xulrunner/examples/simple/Makefile.in +++ b/xulrunner/examples/simple/Makefile.in @@ -3,8 +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 = application.ini - ifneq (,$(filter windows,$(MOZ_WIDGET_TOOLKIT))) ICONS = icons/simple.ico endif diff --git a/xulrunner/examples/simple/moz.build b/xulrunner/examples/simple/moz.build index 444532379ce..974a0a25335 100644 --- a/xulrunner/examples/simple/moz.build +++ b/xulrunner/examples/simple/moz.build @@ -14,3 +14,6 @@ JS_PREFERENCE_FILES += [ 'simple-prefs.js', ] +DIST_FILES += [ + 'application.ini', +] From 8c600ca84e32846079fbea33eb462d4df8f8daea Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 16 Mar 2015 18:12:38 +0100 Subject: [PATCH 41/43] Bug 1136360 - Take into account the output device latency in the clock, and be more robust about rounding error accumulation, in cubeb_wasapi.cpp. r=kinetik --- media/libcubeb/src/cubeb_wasapi.cpp | 92 +++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index dfd8315112e..b4771ddc716 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -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; } From 4b4367be07682b7fbcc1ff489b53da1b20febfc7 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Thu, 16 Apr 2015 12:20:06 -0400 Subject: [PATCH 42/43] Bug 994541 - Skip 789933-1.html on Linux debug due to frequent crashes. --- dom/canvas/crashtests/crashtests.list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/canvas/crashtests/crashtests.list b/dom/canvas/crashtests/crashtests.list index eacda7578ff..ba2ff1262d3 100644 --- a/dom/canvas/crashtests/crashtests.list +++ b/dom/canvas/crashtests/crashtests.list @@ -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 From b194af2156208ca0be7ce3aa723267f9b5775af3 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Thu, 16 Apr 2015 12:20:48 -0400 Subject: [PATCH 43/43] Backed out changeset 5c83a36d9eba (bug 1153832) for Gip(a) failures. CLOSED TREE --- testing/marionette/driver.js | 17 +- testing/marionette/elements.js | 47 +-- testing/marionette/error.js | 55 ++-- testing/marionette/listener.js | 505 +++++++++++++++++---------------- testing/marionette/sendkeys.js | 10 +- 5 files changed, 319 insertions(+), 315 deletions(-) diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index 0067e7fef8a..00292b52625 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -155,7 +155,8 @@ ListenerProxy.prototype.__noSuchMethod__ = function*(name, args) { let okListener = () => resolve(); let valListener = msg => resolve(msg.json.value); - let errListener = msg => reject(msg.objects.error); + let errListener = msg => reject( + "error" in msg.objects ? msg.objects.error : msg.json); let handleDialog = function(subject, topic) { listeners.remove(); @@ -2061,7 +2062,7 @@ GeckoDriver.prototype.clickElement = function(cmd, resp) { // listen for it and then just send an error back. The person making the // call should be aware something isnt right and handle accordingly this.addFrameCloseListener("click"); - yield this.listener.clickElement(id); + yield this.listener.clickElement({id: id}); break; } }; @@ -2085,7 +2086,7 @@ GeckoDriver.prototype.getElementAttribute = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementAttribute(id, name); + resp.value = yield this.listener.getElementAttribute({id: id, name: name}); break; } }; @@ -2111,7 +2112,7 @@ GeckoDriver.prototype.getElementText = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementText(id); + resp.value = yield this.listener.getElementText({id: id}); break; } }; @@ -2133,7 +2134,7 @@ GeckoDriver.prototype.getElementTagName = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementTagName(id); + resp.value = yield this.listener.getElementTagName({id: id}); break; } }; @@ -2223,7 +2224,7 @@ GeckoDriver.prototype.isElementEnabled = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.isElementEnabled(id); + resp.value = yield this.listener.isElementEnabled({id: id}); break; } }, @@ -2269,7 +2270,7 @@ GeckoDriver.prototype.getElementSize = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementSize(id); + resp.value = yield this.listener.getElementSize({id: id}); break; } }; @@ -2291,7 +2292,7 @@ GeckoDriver.prototype.getElementRect = function(cmd, resp) { break; case Context.CONTENT: - resp.value = yield this.listener.getElementRect(id); + resp.value = yield this.listener.getElementRect({id: id}); break; } }; diff --git a/testing/marionette/elements.js b/testing/marionette/elements.js index 53f13559bba..884a0f8de1a 100644 --- a/testing/marionette/elements.js +++ b/testing/marionette/elements.js @@ -1,11 +1,8 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -let {utils: Cu} = Components; - -Cu.import("chrome://marionette/content/error.js"); - /** * The ElementManager manages DOM references and interactions with elements. * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the @@ -31,8 +28,8 @@ this.EXPORTED_SYMBOLS = [ const DOCUMENT_POSITION_DISCONNECTED = 1; -const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"] - .getService(Components.interfaces.nsIUUIDGenerator); +let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"] + .getService(Components.interfaces.nsIUUIDGenerator); this.CLASS_NAME = "class name"; this.SELECTOR = "css selector"; @@ -45,6 +42,12 @@ this.XPATH = "xpath"; this.ANON= "anon"; this.ANON_ATTRIBUTE = "anon attribute"; +function ElementException(msg, num, stack) { + this.message = msg; + this.code = num; + this.stack = stack; +} + this.Accessibility = function Accessibility() { // A flag indicating whether the accessibility issue should be logged or cause // an exception. Default: log to stdout. @@ -182,7 +185,7 @@ Accessibility.prototype = { return; } if (this.strict) { - throw new ElementNotAccessibleError(message); + throw new ElementException(message, 56, null); } dump(Date.now() + " Marionette: " + message); } @@ -221,17 +224,19 @@ ElementManager.prototype = { let foundEl = null; try { foundEl = this.seenItems[i].get(); - } catch (e) {} + } + catch(e) {} if (foundEl) { if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(element)) { return i; } - } else { - // cleanup reference to GC'd element + } + else { + //cleanup reference to GC'd element delete this.seenItems[i]; } } - let id = uuidGen.generateUUID().toString(); + var id = uuidGen.generateUUID().toString(); this.seenItems[id] = Components.utils.getWeakReference(element); return id; }, @@ -250,7 +255,7 @@ ElementManager.prototype = { getKnownElement: function EM_getKnownElement(id, win) { let el = this.seenItems[id]; if (!el) { - throw new JavaScriptError("Element has not been seen before. Id given was " + id); + throw new ElementException("Element has not been seen before. Id given was " + id, 17, null); } try { el = el.get(); @@ -265,9 +270,8 @@ ElementManager.prototype = { !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) || (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) & DOCUMENT_POSITION_DISCONNECTED)) { - throw new StaleElementReferenceError( - "The element reference is stale. Either the element " + - "is no longer attached to the DOM or the page has been refreshed."); + throw new ElementException("The element reference is stale. Either the element " + + "is no longer attached to the DOM or the page has been refreshed.", 10, null); } return el; }, @@ -365,9 +369,8 @@ ElementManager.prototype = { args.hasOwnProperty(this.w3cElementKey))) { let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey]; converted = this.getKnownElement(elementUniqueIdentifier, win); - if (converted == null) { - throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`); - } + if (converted == null) + throw new ElementException("Unknown element: " + elementUniqueIdentifier, 500, null); } else { converted = {}; @@ -440,7 +443,7 @@ ElementManager.prototype = { let startNode = (values.element != undefined) ? this.getKnownElement(values.element, win) : win.document; if (this.elementStrategies.indexOf(values.using) < 0) { - throw new InvalidSelectorError(`No such strategy: ${values.using}`); + throw new ElementException("No such strategy.", 32, null); } let found = all ? this.findElements(values.using, values.value, win.document, startNode) : this.findElement(values.using, values.value, win.document, startNode); @@ -458,7 +461,7 @@ ElementManager.prototype = { } else if (values.using == ANON_ATTRIBUTE) { message = "Unable to locate anonymous element: " + JSON.stringify(values.value); } - on_error(new NoSuchElementError(message), command_id); + on_error({message: message, code: 7}, command_id); } } else { values.time = startTime; @@ -591,7 +594,7 @@ ElementManager.prototype = { element = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]); break; default: - throw new WebDriverError("No such strategy"); + throw new ElementException("No such strategy", 500, null); } return element; }, @@ -658,7 +661,7 @@ ElementManager.prototype = { } break; default: - throw new WebDriverError("No such strategy"); + throw new ElementException("No such strategy", 500, null); } return elements; }, diff --git a/testing/marionette/error.js b/testing/marionette/error.js index 86bcd4e8b41..9f664fa3726 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -7,13 +7,11 @@ const {utils: Cu} = Components; const errors = [ - "ElementNotAccessibleError", "ElementNotVisibleError", "FrameSendFailureError", "FrameSendNotInitializedError", "IllegalArgumentError", "InvalidElementStateError", - "InvalidSelectorError", "JavaScriptError", "NoAlertOpenError", "NoSuchElementError", @@ -21,7 +19,6 @@ const errors = [ "NoSuchWindowError", "ScriptTimeoutError", "SessionNotCreatedError", - "StaleElementReferenceError", "TimeoutError", "UnknownCommandError", "UnknownError", @@ -41,6 +38,11 @@ error.toJSON = function(err) { }; }; +/** + * Gets WebDriver error by its Selenium status code number. + */ +error.byCode = n => lookup.get(n); + /** * Determines if the given status code is successful. */ @@ -128,14 +130,6 @@ this.WebDriverError = function(msg) { }; WebDriverError.prototype = Object.create(Error.prototype); -this.ElementNotAccessibleError = function(msg) { - WebDriverError.call(this, msg); - this.name = "ElementNotAccessibleError"; - this.status = "element not accessible"; - this.code = 56; -}; -ElementNotAccessibleError.prototype = Object.create(WebDriverError.prototype); - this.ElementNotVisibleError = function(msg) { WebDriverError.call(this, msg); this.name = "ElementNotVisibleError"; @@ -182,14 +176,6 @@ this.InvalidElementStateError = function(msg) { }; InvalidElementStateError.prototype = Object.create(WebDriverError.prototype); -this.InvalidSelectorError = function(msg) { - WebDriverError.call(this, msg); - this.name = "InvalidSelectorError"; - this.status = "invalid selector"; - this.code = 32; -}; -InvalidSelectorError.prototype = Object.create(WebDriverError.prototype); - /** * Creates an error message for a JavaScript error thrown during * executeScript or executeAsyncScript. @@ -284,17 +270,9 @@ this.SessionNotCreatedError = function(msg) { this.status = "session not created"; // should be 33 to match Selenium this.code = 71; -}; +} SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype); -this.StaleElementReferenceError = function(msg) { - WebDriverError.call(this, msg); - this.name = "StaleElementReferenceError"; - this.status = "stale element reference"; - this.code = 10; -}; -StaleElementReferenceError.prototype = Object.create(WebDriverError.prototype); - this.TimeoutError = function(msg) { WebDriverError.call(this, msg); this.name = "TimeoutError"; @@ -326,3 +304,24 @@ this.UnsupportedOperationError = function(msg) { this.code = 405; }; UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype); + +const errorObjs = [ + this.ElementNotVisibleError, + this.FrameSendFailureError, + this.FrameSendNotInitializedError, + this.IllegalArgumentError, + this.InvalidElementStateError, + this.JavaScriptError, + this.NoAlertOpenError, + this.NoSuchElementError, + this.NoSuchFrameError, + this.NoSuchWindowError, + this.ScriptTimeoutError, + this.SessionNotCreatedError, + this.TimeoutError, + this.UnknownCommandError, + this.UnknownError, + this.UnsupportedOperationError, + this.WebDriverError, +]; +const lookup = new Map(errorObjs.map(err => [new err().code, err])); diff --git a/testing/marionette/listener.js b/testing/marionette/listener.js index 53d3ae5a34c..33822d5cd4d 100644 --- a/testing/marionette/listener.js +++ b/testing/marionette/listener.js @@ -15,7 +15,6 @@ loader.loadSubScript("chrome://marionette/content/simpletest.js"); loader.loadSubScript("chrome://marionette/content/common.js"); loader.loadSubScript("chrome://marionette/content/actions.js"); Cu.import("chrome://marionette/content/elements.js"); -Cu.import("chrome://marionette/content/error.js"); Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -94,7 +93,7 @@ let modalHandler = function() { * If the actor returns an ID, we start the listeners. Otherwise, nothing happens. */ function registerSelf() { - let msg = {value: winUtil.outerWindowID}; + let msg = {value: winUtil.outerWindowID} // register will have the ID and a boolean describing if this is the main process or not let register = sendSyncMessage("Marionette:register", msg); @@ -147,28 +146,6 @@ function emitTouchEventForIFrame(message) { message.force, 90); } -function dispatch(fn) { - return function(msg) { - let id = msg.json.command_id; - try { - let rv; - if (typeof msg.json == "undefined" || msg.json instanceof Array) { - rv = fn.apply(null, msg.json); - } else { - rv = fn(msg.json); - } - - if (typeof rv == "undefined") { - sendOk(id); - } else { - sendResponse({value: rv}, id); - } - } catch (e) { - sendError(e, id); - } - }; -} - /** * Add a message listener that's tied to our listenerId. */ @@ -183,15 +160,6 @@ function removeMessageListenerId(messageName, handler) { removeMessageListener(messageName + listenerId, handler); } -let getElementSizeFn = dispatch(getElementSize); -let getActiveElementFn = dispatch(getActiveElement); -let clickElementFn = dispatch(clickElement); -let getElementAttributeFn = dispatch(getElementAttribute); -let getElementTextFn = dispatch(getElementText); -let getElementTagNameFn = dispatch(getElementTagName); -let getElementRectFn = dispatch(getElementRect); -let isElementEnabledFn = dispatch(isElementEnabled); - /** * Start all message listeners */ @@ -214,17 +182,17 @@ function startListeners() { addMessageListenerId("Marionette:refresh", refresh); addMessageListenerId("Marionette:findElementContent", findElementContent); addMessageListenerId("Marionette:findElementsContent", findElementsContent); - addMessageListenerId("Marionette:getActiveElement", getActiveElementFn); - addMessageListenerId("Marionette:clickElement", clickElementFn); - addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn); - addMessageListenerId("Marionette:getElementText", getElementTextFn); - addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn); + addMessageListenerId("Marionette:getActiveElement", getActiveElement); + addMessageListenerId("Marionette:clickElement", clickElement); + addMessageListenerId("Marionette:getElementAttribute", getElementAttribute); + addMessageListenerId("Marionette:getElementText", getElementText); + addMessageListenerId("Marionette:getElementTagName", getElementTagName); addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); addMessageListenerId("Marionette:submitElement", submitElement); - addMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated - addMessageListenerId("Marionette:getElementRect", getElementRectFn); - addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); + addMessageListenerId("Marionette:getElementSize", getElementSize); + addMessageListenerId("Marionette:getElementRect", getElementRect); + addMessageListenerId("Marionette:isElementEnabled", isElementEnabled); addMessageListenerId("Marionette:isElementSelected", isElementSelected); addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); addMessageListenerId("Marionette:getElementLocation", getElementLocation); //deprecated @@ -319,17 +287,17 @@ function deleteSession(msg) { removeMessageListenerId("Marionette:refresh", refresh); removeMessageListenerId("Marionette:findElementContent", findElementContent); removeMessageListenerId("Marionette:findElementsContent", findElementsContent); - removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn); - removeMessageListenerId("Marionette:clickElement", clickElementFn); - removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn); - removeMessageListenerId("Marionette:getElementText", getElementTextFn); - removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn); + removeMessageListenerId("Marionette:getActiveElement", getActiveElement); + removeMessageListenerId("Marionette:clickElement", clickElement); + removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute); + removeMessageListenerId("Marionette:getElementText", getElementText); + removeMessageListenerId("Marionette:getElementTagName", getElementTagName); removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed); removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty); removeMessageListenerId("Marionette:submitElement", submitElement); - removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated - removeMessageListenerId("Marionette:getElementRect", getElementRectFn); - removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn); + removeMessageListenerId("Marionette:getElementSize", getElementSize); //deprecated + removeMessageListenerId("Marionette:getElementRect", getElementRect); + removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled); removeMessageListenerId("Marionette:isElementSelected", isElementSelected); removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement); removeMessageListenerId("Marionette:getElementLocation", getElementLocation); @@ -363,42 +331,40 @@ function deleteSession(msg) { /** * Generic method to send a message to the server */ -function sendToServer(name, data, objs, id) { - if (!data) { - data = {} +function sendToServer(msg, value, command_id) { + if (command_id) { + value.command_id = command_id; } - if (id) { - data.command_id = id; - } - sendAsyncMessage(name, data, objs); + sendAsyncMessage(msg, value); } /** * Send response back to server */ function sendResponse(value, command_id) { - sendToServer("Marionette:done", value, null, command_id); + sendToServer("Marionette:done", value, command_id); } /** * Send ack back to server */ function sendOk(command_id) { - sendToServer("Marionette:ok", null, null, command_id); + sendToServer("Marionette:ok", {}, command_id); } /** * Send log message to server */ function sendLog(msg) { - sendToServer("Marionette:log", {message: msg}); + sendToServer("Marionette:log", { message: msg }); } /** * Send error message to server */ -function sendError(err, cmdId) { - sendToServer("Marionette:error", null, {error: err}, cmdId); +function sendError(msg, code, stack, cmdId) { + let payload = {message: msg, code: code, stack: stack}; + sendToServer("Marionette:error", payload, cmdId); } /** @@ -492,8 +458,8 @@ function createExecuteContentSandbox(aWindow, timeout) { }); } - sandbox.asyncComplete = function(obj, id) { - if (id == asyncTestCommandId) { + sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) { + if (commandId == asyncTestCommandId) { curFrame.removeEventListener("unload", onunload, false); curFrame.clearTimeout(asyncTestTimeoutId); @@ -501,19 +467,24 @@ function createExecuteContentSandbox(aWindow, timeout) { curFrame.clearTimeout(inactivityTimeoutId); } + sendSyncMessage("Marionette:shareData", - {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); + {log: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); - if (error.isError(obj)) { - sendError(obj, id); - } else { + if (status == 0){ if (Object.keys(_emu_cbs).length) { _emu_cbs = {}; - sendError(new WebDriverError("Emulator callback still pending when finish() called"), id); - } else { - sendResponse({value: elementManager.wrapValue(obj)}, id); + sendError("Emulator callback still pending when finish() called", + 500, null, commandId); } + else { + sendResponse({value: elementManager.wrapValue(value), status: status}, + commandId); + } + } + else { + sendError(value, status, stack, commandId); } asyncTestRunning = false; @@ -524,32 +495,33 @@ function createExecuteContentSandbox(aWindow, timeout) { }; sandbox.finish = function sandbox_finish() { if (asyncTestRunning) { - sandbox.asyncComplete(marionette.generate_results(), sandbox.asyncTestCommandId); + sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId); } else { return marionette.generate_results(); } }; - sandbox.marionetteScriptFinished = val => - sandbox.asyncComplete(val, sandbox.asyncTestCommandId); + sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) { + return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId); + }; return sandbox; } /** * Execute the given script either as a function body (executeScript) - * or directly (for mochitest like JS Marionette tests). + * or directly (for 'mochitest' like JS Marionette tests) */ function executeScript(msg, directInject) { // Set up inactivity timeout. if (msg.json.inactivityTimeout) { let setTimer = function() { - inactivityTimeoutId = curFrame.setTimeout(function() { - sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId); + inactivityTimeoutId = curFrame.setTimeout(function() { + sendError('timed out due to inactivity', 28, null, asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); - heartbeatCallback = function() { + heartbeatCallback = function resetInactivityTimeout() { curFrame.clearTimeout(inactivityTimeoutId); setTimer(); }; @@ -562,10 +534,11 @@ function executeScript(msg, directInject) { sandbox = createExecuteContentSandbox(curFrame, msg.json.timeout); if (!sandbox) { - sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId); + sendError("Could not create sandbox!", 500, null, asyncTestCommandId); return; } - } else { + } + else { sandbox.asyncTestCommandId = asyncTestCommandId; } @@ -585,7 +558,7 @@ function executeScript(msg, directInject) { marionetteLogObj.clearLogs(); if (res == undefined || res.passed == undefined) { - sendError(new JavaScriptError("Marionette.finish() not called"), asyncTestCommandId); + sendError("Marionette.finish() not called", 17, null, asyncTestCommandId); } else { sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); @@ -595,8 +568,9 @@ function executeScript(msg, directInject) { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( msg.json.args, curFrame), sandbox, { wrapReflectors: true }); - } catch (e) { - sendError(e, asyncTestCommandId); + } + catch(e) { + sendError(e.message, e.code, e.stack, asyncTestCommandId); return; } @@ -616,14 +590,15 @@ function executeScript(msg, directInject) { marionetteLogObj.clearLogs(); sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId); } - } catch (e) { - let err = new JavaScriptError( - e, - "execute_script", - msg.json.filename, - msg.json.line, - script); - sendError(err, asyncTestCommandId); + } + catch (e) { + // 17 = JavascriptException + let error = createStackMessage(e, + "execute_script", + msg.json.filename, + msg.json.line, + script); + sendError(error[0], 17, error[1], asyncTestCommandId); } } @@ -667,12 +642,12 @@ function executeWithCallback(msg, useFinish) { if (msg.json.inactivityTimeout) { let setTimer = function() { inactivityTimeoutId = curFrame.setTimeout(function() { - sandbox.asyncComplete(new ScriptTimeout("timed out due to inactivity"), asyncTestCommandId); + sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId); }, msg.json.inactivityTimeout); }; setTimer(); - heartbeatCallback = function() { + heartbeatCallback = function resetInactivityTimeout() { curFrame.clearTimeout(inactivityTimeoutId); setTimer(); }; @@ -682,7 +657,7 @@ function executeWithCallback(msg, useFinish) { asyncTestCommandId = msg.json.command_id; onunload = function() { - sendError(new JavaScriptError("unload was called"), asyncTestCommandId); + sendError("unload was called", 17, null, asyncTestCommandId); }; curFrame.addEventListener("unload", onunload, false); @@ -690,7 +665,7 @@ function executeWithCallback(msg, useFinish) { sandbox = createExecuteContentSandbox(curFrame, msg.json.timeout); if (!sandbox) { - sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId); + sendError("Could not create sandbox!", 17, null, asyncTestCommandId); return; } } @@ -705,19 +680,19 @@ function executeWithCallback(msg, useFinish) { // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js. // We'll stay compatible with the Selenium code. asyncTestTimeoutId = curFrame.setTimeout(function() { - sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId); + sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId); }, msg.json.timeout); originalOnError = curFrame.onerror; - curFrame.onerror = function errHandler(msg, url, line) { - sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId); + curFrame.onerror = function errHandler(errMsg, url, line) { + sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId); curFrame.onerror = originalOnError; }; let scriptSrc; if (useFinish) { if (msg.json.timeout == null || msg.json.timeout == 0) { - sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId); + sendError("Please set a timeout", 21, null, asyncTestCommandId); } scriptSrc = script; } @@ -725,8 +700,9 @@ function executeWithCallback(msg, useFinish) { try { sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments( msg.json.args, curFrame), sandbox, { wrapReflectors: true }); - } catch (e) { - sendError(e, asyncTestCommandId); + } + catch(e) { + sendError(e.message, e.code, e.stack, asyncTestCommandId); return; } @@ -747,13 +723,13 @@ function executeWithCallback(msg, useFinish) { } Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0); } catch (e) { - let err = new JavaScriptError( - e, - "execute_async_script", - msg.json.filename, - msg.json.line, - scriptSrc); - sandbox.asyncComplete(err, asyncTestCommandId); + // 17 = JavascriptException + let error = createStackMessage(e, + "execute_async_script", + msg.json.filename, + msg.json.line, + scriptSrc); + sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId); } } @@ -880,7 +856,7 @@ function singleTap(msg) { let visible = checkVisible(el, msg.json.corx, msg.json.cory); checkVisibleAccessibility(acc, visible); if (!visible) { - sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id); + sendError("Element is not currently visible and may not be manipulated", 11, null, command_id); return; } checkActionableAccessibility(acc); @@ -895,9 +871,10 @@ function singleTap(msg) { emitTouchEvent('touchend', touch); } actions.mouseTap(el.ownerDocument, c.x, c.y); - sendOk(command_id); - } catch (e) { - sendError(e, command_id); + sendOk(msg.json.command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, msg.json.command_id); } } @@ -982,8 +959,12 @@ function actionChain(msg) { let touchId = msg.json.nextId; let callbacks = {}; - callbacks.onSuccess = value => sendResponse(value, command_id); - callbacks.onError = err => sendError(err, command_id); + callbacks.onSuccess = (value) => { + sendResponse(value, command_id); + }; + callbacks.onError = (message, code, trace) => { + sendError(message, code, trace, msg.json.command_id); + }; let touchProvider = {}; touchProvider.createATouch = createATouch; @@ -998,7 +979,7 @@ function actionChain(msg) { callbacks, touchProvider); } catch (e) { - sendError(e, command_id); + sendError(e.message, e.code, e.stack, command_id); } } @@ -1163,8 +1144,9 @@ function multiAction(msg) { // pendingTouches keeps track of current touches that's on the screen let pendingTouches = []; setDispatch(concurrentEvent, pendingTouches, command_id); - } catch (e) { - sendError(e, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, msg.json.command_id); } } @@ -1196,7 +1178,7 @@ function pollForReadyState(msg, start, callback) { !curFrame.document.baseURI.startsWith(url)) { // We have reached an error url without requesting it. callback(); - sendError(new UnknownError("Error loading page"), command_id); + sendError("Error loading page", 13, null, command_id); } else if (curFrame.document.readyState == "interactive" && curFrame.document.baseURI.startsWith("about:")) { callback(); @@ -1204,9 +1186,11 @@ function pollForReadyState(msg, start, callback) { } else { navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); } - } else { + } + else { callback(); - sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id); + sendError("Error loading page, timed out (checkLoad)", 21, null, + command_id); } } checkLoad(); @@ -1236,7 +1220,8 @@ function get(msg) { function timerFunc() { removeEventListener("DOMContentLoaded", onDOMContentLoaded, false); - sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id); + sendError("Error loading page, timed out (onDOMContentLoaded)", 21, + null, msg.json.command_id); } if (msg.json.pageTimeout != null) { navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT); @@ -1321,12 +1306,13 @@ function refresh(msg) { function findElementContent(msg) { let command_id = msg.json.command_id; try { - let onSuccess = (el, id) => sendResponse({value: el}, id); - let onError = (err, id) => sendError(err, id); + let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) }; + let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); }; elementManager.find(curFrame, msg.json, msg.json.searchTimeout, - false /* all */, onSuccess, onError, command_id); - } catch (e) { - sendError(e, command_id); + false /* all */, on_success, on_error, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } @@ -1336,90 +1322,97 @@ function findElementContent(msg) { function findElementsContent(msg) { let command_id = msg.json.command_id; try { - let onSuccess = (els, id) => sendResponse({value: els}, id); - let onError = (err, id) => sendError(err, id); + let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); }; + let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); }; elementManager.find(curFrame, msg.json, msg.json.searchTimeout, - true /* all */, onSuccess, onError, command_id); - } catch (e) { - sendError(e, command_id); + true /* all */, on_success, on_error, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } /** - * Find and return the active element on the page. - * - * @return {WebElement} - * Reference to web element. + * Find and return the active element on the page */ -function getActiveElement() { - let el = curFrame.document.activeElement; - return elementManager.addToKnownElements(el); +function getActiveElement(msg) { + let command_id = msg.json.command_id; + var element = curFrame.document.activeElement; + var id = elementManager.addToKnownElements(element); + sendResponse({value: id}, command_id); } /** - * Send click event to element. - * - * @param {WebElement} id - * Reference to the web element to click. + * Send click event to element */ -function clickElement(id) { - let el = elementManager.getKnownElement(id, curFrame); - let acc = accessibility.getAccessibleObject(el, true); - let visible = checkVisible(el); - checkVisibleAccessibility(acc, visible); - if (!visible) { - throw new ElementNotVisibleError("Element is not visible"); +function clickElement(msg) { + let command_id = msg.json.command_id; + let el; + try { + el = elementManager.getKnownElement(msg.json.id, curFrame); + let acc = accessibility.getAccessibleObject(el, true); + let visible = checkVisible(el); + checkVisibleAccessibility(acc, visible); + if (visible) { + checkActionableAccessibility(acc); + if (utils.isElementEnabled(el)) { + utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView) + } + else { + sendError("Element is not Enabled", 12, null, command_id) + } + } + else { + sendError("Element is not visible", 11, null, command_id) + } + sendOk(command_id); } - checkActionableAccessibility(acc); - if (utils.isElementEnabled(el)) { - utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView); - } else { - throw new InvalidElementStateError("Element is not Enabled"); + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } /** - * Get a given attribute of an element. - * - * @param {WebElement} id - * Reference to the web element to get the attribute of. - * @param {string} name - * Name of the attribute. - * - * @return {string} - * The value of the attribute. + * Get a given attribute of an element */ -function getElementAttribute(id, name) { - let el = elementManager.getKnownElement(id, curFrame); - return utils.getElementAttribute(el, name); +function getElementAttribute(msg) { + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + sendResponse({value: utils.getElementAttribute(el, msg.json.name)}, + command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** * Get the text of this element. This includes text from child elements. - * - * @param {WebElement} id - * Reference to web element. - * - * @return {string} - * Text of element. */ -function getElementText(id) { - let el = elementManager.getKnownElement(id, curFrame); - return utils.getElementText(el); +function getElementText(msg) { + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + sendResponse({value: utils.getElementText(el)}, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** * Get the tag name of an element. - * - * @param {WebElement} id - * Reference to web element. - * - * @return {string} - * Tag name of element. */ -function getElementTagName(id) { - let el = elementManager.getKnownElement(id, curFrame); - return el.tagName.toLowerCase(); +function getElementTagName(msg) { + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + sendResponse({value: el.tagName.toLowerCase()}, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** @@ -1432,8 +1425,9 @@ function isElementDisplayed(msg) { let displayed = utils.isElementDisplayed(el); checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed); sendResponse({value: displayed}, command_id); - } catch (e) { - sendError(e, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } @@ -1452,8 +1446,9 @@ function getElementValueOfCssProperty(msg){ let el = elementManager.getKnownElement(msg.json.id, curFrame); sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)}, command_id); - } catch (e) { - sendError(e, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } @@ -1472,63 +1467,67 @@ function submitElement (msg) { if (el.tagName && el.tagName.toLowerCase() == 'form') { el.submit(); sendOk(command_id); - } else { - sendError(new NoSuchElementError("Element is not a form element or in a form"), command_id); } - } catch (e) { - sendError(e, command_id); + else { + sendError("Element is not a form element or in a form", 7, null, command_id); + } + + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } /** - * Get the size of the element. - * - * @param {WebElement} id - * Web element reference. - * - * @return {Object.} - * The width/height dimensions of th element. + * Get the size of the element and return it */ -function getElementSize(id) { - let el = elementManager.getKnownElement(id, curFrame); - let clientRect = el.getBoundingClientRect(); - return {width: clientRect.width, height: clientRect.height}; +function getElementSize(msg){ + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + let clientRect = el.getBoundingClientRect(); + sendResponse({value: {width: clientRect.width, height: clientRect.height}}, + command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** - * Get the size of the element. - * - * @param {WebElement} id - * Reference to web element. - * - * @return {Object.} - * The x, y, width, and height properties of the element. + * Get the size of the element and return it */ -function getElementRect(id) { - let el = elementManager.getKnownElement(id, curFrame); - let clientRect = el.getBoundingClientRect(); - return { - x: clientRect.x + curFrame.pageXOffset, - y: clientRect.y + curFrame.pageYOffset, - width: clientRect.width, - height: clientRect.height - }; +function getElementRect(msg){ + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + let clientRect = el.getBoundingClientRect(); + sendResponse({value: {x: clientRect.x + curFrame.pageXOffset, + y: clientRect.y + curFrame.pageYOffset, + width: clientRect.width, + height: clientRect.height}}, + command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** - * Check if element is enabled. - * - * @param {WebElement} id - * Reference to web element. - * - * @return {boolean} - * True if enabled, false otherwise. + * Check if element is enabled */ -function isElementEnabled(id) { - let el = elementManager.getKnownElement(id, curFrame); - let enabled = utils.isElementEnabled(el); - checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), enabled); - return enabled; +function isElementEnabled(msg) { + let command_id = msg.json.command_id; + try { + let el = elementManager.getKnownElement(msg.json.id, curFrame); + let enabled = utils.isElementEnabled(el); + checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), + enabled); + sendResponse({value: enabled}, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); + } } /** @@ -1539,8 +1538,9 @@ function isElementSelected(msg) { try { let el = elementManager.getKnownElement(msg.json.id, curFrame); sendResponse({value: utils.isElementSelected(el)}, command_id); - } catch (e) { - sendError(e, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } @@ -1567,7 +1567,7 @@ function sendKeysToElement(msg) { file = new File(p); } catch (e) { let err = new IllegalArgumentError(`File not found: ${val}`); - sendError(err, command_id); + sendError(err.message, err.code, err.stack, command_id); return; } fs.push(file); @@ -1598,8 +1598,9 @@ function getElementLocation(msg) { location.y = rect.top; sendResponse({value: location}, command_id); - } catch (e) { - sendError(e, command_id); + } + catch (e) { + sendError(e.message, e.code, e.stack, command_id); } } @@ -1617,7 +1618,7 @@ function clearElement(msg) { } sendOk(command_id); } catch (e) { - sendError(e, command_id); + sendError(e.message, e.code, e.stack, command_id); } } @@ -1632,9 +1633,9 @@ function switchToFrame(msg) { if (curFrame.document.readyState == "complete") { sendOk(command_id); return; - } else if (curFrame.document.readyState == "interactive" && - errorRegex.exec(curFrame.document.baseURI)) { - sendError(new UnknownError("Error loading page"), command_id); + } + else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) { + sendError("Error loading page", 13, null, command_id); return; } checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT); @@ -1674,8 +1675,9 @@ function switchToFrame(msg) { let wantedFrame; try { wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //Frame Element - } catch (e) { - sendError(e, command_id); + } + catch(e) { + sendError(e.message, e.code, e.stack, command_id); } if (frames.length > 0) { @@ -1733,9 +1735,8 @@ function switchToFrame(msg) { } } } - if (foundFrame === null) { - sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id); + sendError("Unable to locate frame: " + (msg.json.id || msg.json.element), 8, null, command_id); return true; } @@ -1776,11 +1777,12 @@ function addCookie(msg) { if (!cookie.domain) { var location = curFrame.document.location; cookie.domain = location.hostname; - } else { + } + else { var currLocation = curFrame.location; var currDomain = currLocation.host; if (currDomain.indexOf(cookie.domain) == -1) { - sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id); + sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id); } } @@ -1793,12 +1795,12 @@ function addCookie(msg) { var document = curFrame.document; if (!document || !document.contentType.match(/html/i)) { - sendError(new UnableToSetCookie("You may only set cookies on html documents"), msg.json.command_id); + sendError('You may only set cookies on html documents', 25, null, msg.json.command_id); } let added = sendSyncMessage("Marionette:addCookie", {value: cookie}); if (added[0] !== true) { - sendError(new UnknownError("Error setting cookie"), msg.json.command_id); + sendError("Error setting cookie", 13, null, msg.json.command_id); return; } sendOk(msg.json.command_id); @@ -1839,7 +1841,7 @@ function deleteCookie(msg) { if (cookie.name == toDelete) { let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie}); if (deleted[0] !== true) { - sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id); + sendError("Could not delete cookie: " + msg.json.name, 13, null, msg.json.command_id); return; } } @@ -1856,7 +1858,7 @@ function deleteAllCookies(msg) { for (let cookie of cookies) { let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie}); if (!deleted[0]) { - sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id); + sendError("Could not delete cookie: " + JSON.stringify(cookie), 13, null, msg.json.command_id); return; } } @@ -1910,8 +1912,9 @@ function emulatorCmdResult(msg) { } try { cb(message.result); - } catch (e) { - sendError(e, -1); + } + catch(e) { + sendError(e.message, e.code, e.stack, -1); return; } } diff --git a/testing/marionette/sendkeys.js b/testing/marionette/sendkeys.js index eb2b8e050f1..096330afc0a 100644 --- a/testing/marionette/sendkeys.js +++ b/testing/marionette/sendkeys.js @@ -17,11 +17,8 @@ */ let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("chrome://marionette/content/error.js"); - let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader); + .getService(Ci.mozIJSSubScriptLoader); let utils = {}; loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils); @@ -141,7 +138,8 @@ function sendKeysToElement (document, element, keysToSend, successCallback, erro sendSingleKey(c, modifiers, document); } successCallback(command_id); - } else { - errorCallback(new ElementNotVisibleError("Element is not visible"), command_id); + } + else { + errorCallback("Element is not visible", 11, null, command_id); } };