diff --git a/accessible/src/windows/ia2/moz.build b/accessible/src/windows/ia2/moz.build index 33b68b8fb365..8d3d415e3f80 100644 --- a/accessible/src/windows/ia2/moz.build +++ b/accessible/src/windows/ia2/moz.build @@ -15,7 +15,7 @@ EXPORTS += [ 'ia2AccessibleValue.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'ia2Accessible.cpp', 'ia2AccessibleAction.cpp', 'ia2AccessibleComponent.cpp', @@ -24,12 +24,17 @@ SOURCES += [ 'ia2AccessibleHypertext.cpp', 'ia2AccessibleImage.cpp', 'ia2AccessibleRelation.cpp', - 'ia2AccessibleTable.cpp', - 'ia2AccessibleTableCell.cpp', 'ia2AccessibleText.cpp', 'ia2AccessibleValue.cpp', ] +# These files cannot be built in unified mode because they both include +# AccessibleTable2_i.c. +SOURCES += [ + 'ia2AccessibleTable.cpp', + 'ia2AccessibleTableCell.cpp', +] + LOCAL_INCLUDES += [ '../../base', '../../generic', diff --git a/accessible/src/windows/msaa/moz.build b/accessible/src/windows/msaa/moz.build index 4ef11761c0f2..cb3ff5affb00 100644 --- a/accessible/src/windows/msaa/moz.build +++ b/accessible/src/windows/msaa/moz.build @@ -14,7 +14,7 @@ EXPORTS.mozilla.a11y += [ 'HyperTextAccessibleWrap.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ 'AccessibleWrap.cpp', 'ApplicationAccessibleWrap.cpp', 'ARIAGridAccessibleWrap.cpp', @@ -29,12 +29,16 @@ SOURCES += [ 'nsWinUtils.cpp', 'Platform.cpp', 'RootAccessibleWrap.cpp', - 'ServiceProvider.cpp', 'TextLeafAccessibleWrap.cpp', ] +# This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c. +SOURCES += [ + 'ServiceProvider.cpp', +] + if CONFIG['MOZ_XUL']: - SOURCES += [ + UNIFIED_SOURCES += [ 'XULListboxAccessibleWrap.cpp', 'XULMenuAccessibleWrap.cpp', 'XULTreeGridAccessibleWrap.cpp', diff --git a/accessible/src/windows/sdn/moz.build b/accessible/src/windows/sdn/moz.build index 1ea1388f2ccb..f1a7ba935f96 100644 --- a/accessible/src/windows/sdn/moz.build +++ b/accessible/src/windows/sdn/moz.build @@ -4,7 +4,7 @@ # 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/. -SOURCES += [ +UNIFIED_SOURCES += [ 'sdnAccessible.cpp', 'sdnDocAccessible.cpp', 'sdnTextAccessible.cpp', diff --git a/config/baseconfig.mk b/config/baseconfig.mk index b7adc7aa8c5b..78e50bbfbd40 100644 --- a/config/baseconfig.mk +++ b/config/baseconfig.mk @@ -29,7 +29,7 @@ endif endif # WINNT ifdef .PYMAKE -include_deps = $(eval -includedeps $(1)) +include_deps = $(eval $(if $(2),,-)includedeps $(1)) else -include_deps = $(eval -include $(1)) +include_deps = $(eval $(if $(2),,-)include $(1)) endif diff --git a/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html b/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html index a49ce5b0a20c..765d4287f674 100644 --- a/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html +++ b/content/canvas/test/webgl/non-conf-tests/test_no_arr_points.html @@ -116,11 +116,19 @@ void main(void) { checkGLError(ok, info); var elemTestFunc = todo; // We fail on most implementations. + var checkGLTestFunc = todo; if (DriverInfo.getDriver() == DriverInfo.DRIVER.ANGLE || DriverInfo.getOS() == DriverInfo.OS.ANDROID) { // ANGLE and Android slaves seem to work fine. elemTestFunc = ok; + checkGLTestFunc = ok; + } + if (DriverInfo.getDriver() == DriverInfo.DRIVER.ANDROID_X86_EMULATOR) + { + // ...but the Android 4.2 x86 emulator environment is different + elemTestFunc = todo; + checkGLTestFunc = ok; } // Now for drawElements: @@ -139,7 +147,7 @@ void main(void) { gl.drawElements(gl.POINTS, 1, indexType, 2*indexStride); elemTestFunc(!isScreenBlack(), '[' + info + '] drawElements[huge offset] should color pixels.'); - checkGLError(elemTestFunc, info); + checkGLError(checkGLTestFunc, info); } // Begin drawing diff --git a/content/media/mediasource/MediaSourceDecoder.cpp b/content/media/mediasource/MediaSourceDecoder.cpp index beeb3f3624ad..83f197995fd1 100644 --- a/content/media/mediasource/MediaSourceDecoder.cpp +++ b/content/media/mediasource/MediaSourceDecoder.cpp @@ -85,7 +85,11 @@ public: nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) MOZ_OVERRIDE { // XXX: Merge result with audio reader. - return GetVideoReader()->GetBuffered(aBuffered, aStartTime); + MediaDecoderReader* reader = GetVideoReader() ? GetVideoReader() : GetAudioReader(); + if (reader) { + return reader->GetBuffered(aBuffered, aStartTime); + } + return NS_OK; } MediaQueue& AudioQueue() MOZ_OVERRIDE diff --git a/content/media/plugins/MPAPI.h b/content/media/plugins/MPAPI.h index 1ba8069098d3..4ebe642989bb 100644 --- a/content/media/plugins/MPAPI.h +++ b/content/media/plugins/MPAPI.h @@ -11,7 +11,7 @@ namespace MPAPI { enum ColorFormat { - YCbCr, + I420, RGB565 }; diff --git a/content/media/plugins/MediaPluginReader.cpp b/content/media/plugins/MediaPluginReader.cpp index a76950f9428e..7f14dfce5af2 100644 --- a/content/media/plugins/MediaPluginReader.cpp +++ b/content/media/plugins/MediaPluginReader.cpp @@ -18,6 +18,7 @@ namespace mozilla { typedef mozilla::layers::Image Image; +typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage; MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder, const nsACString& aContentType) : @@ -170,7 +171,7 @@ bool MediaPluginReader::DecodeVideoFrame(bool &aKeyframeSkip, currentImage = bufferCallback.GetImage(); int64_t pos = mDecoder->GetResource()->Tell(); nsIntRect picture = mPicture; - + nsAutoPtr v; if (currentImage) { gfx::IntSize frameSize = currentImage->GetSize(); @@ -338,33 +339,79 @@ MediaPluginReader::ImageBufferCallback::ImageBufferCallback(mozilla::layers::Ima void * MediaPluginReader::ImageBufferCallback::operator()(size_t aWidth, size_t aHeight, - MPAPI::ColorFormat aColorFormat) + MPAPI::ColorFormat aColorFormat) { if (!mImageContainer) { NS_WARNING("No image container to construct an image"); return nullptr; } - nsRefPtr rgbImage; + nsRefPtr image; switch(aColorFormat) { case MPAPI::RGB565: - rgbImage = mozilla::layers::CreateSharedRGBImage(mImageContainer, - nsIntSize(aWidth, aHeight), - gfxImageFormatRGB16_565); - if (!rgbImage) { + image = mozilla::layers::CreateSharedRGBImage(mImageContainer, + nsIntSize(aWidth, aHeight), + gfxImageFormatRGB16_565); + if (!image) { NS_WARNING("Could not create rgb image"); return nullptr; } - mImage = rgbImage; - return rgbImage->AsSharedImage()->GetBuffer(); - case MPAPI::YCbCr: + mImage = image; + return image->AsSharedImage()->GetBuffer(); + case MPAPI::I420: + return CreateI420Image(aWidth, aHeight); default: NS_NOTREACHED("Color format not supported"); return nullptr; } } +uint8_t * +MediaPluginReader::ImageBufferCallback::CreateI420Image(size_t aWidth, + size_t aHeight) +{ + ImageFormat format = PLANAR_YCBCR; + + mImage = mImageContainer->CreateImage(&format, 1 /* numFormats */); + PlanarYCbCrImage *yuvImage = static_cast(mImage.get()); + + if (!yuvImage) { + NS_WARNING("Could not create I420 image"); + return nullptr; + } + + size_t frameSize = aWidth * aHeight; + + // Allocate enough for one full resolution Y plane + // and two quarter resolution Cb/Cr planes. + uint8_t *buffer = yuvImage->AllocateAndGetNewBuffer(frameSize * 3 / 2); + + mozilla::layers::PlanarYCbCrData frameDesc; + + frameDesc.mYChannel = buffer; + frameDesc.mCbChannel = buffer + frameSize; + frameDesc.mCrChannel = buffer + frameSize * 5 / 4; + + frameDesc.mYSize = gfxIntSize(aWidth, aHeight); + frameDesc.mCbCrSize = gfxIntSize(aWidth / 2, aHeight / 2); + + frameDesc.mYStride = aWidth; + frameDesc.mCbCrStride = aWidth / 2; + + frameDesc.mYSkip = 0; + frameDesc.mCbSkip = 0; + frameDesc.mCrSkip = 0; + + frameDesc.mPicX = 0; + frameDesc.mPicY = 0; + frameDesc.mPicSize = gfxIntSize(aWidth, aHeight); + + yuvImage->SetDataNoCopy(frameDesc); + + return buffer; +} + already_AddRefed MediaPluginReader::ImageBufferCallback::GetImage() { diff --git a/content/media/plugins/MediaPluginReader.h b/content/media/plugins/MediaPluginReader.h index 2a48666e77f2..8143c0507277 100644 --- a/content/media/plugins/MediaPluginReader.h +++ b/content/media/plugins/MediaPluginReader.h @@ -65,17 +65,23 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags); virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); + class ImageBufferCallback : public MPAPI::BufferCallback { typedef mozilla::layers::Image Image; + public: ImageBufferCallback(mozilla::layers::ImageContainer *aImageContainer); void *operator()(size_t aWidth, size_t aHeight, MPAPI::ColorFormat aColorFormat) MOZ_OVERRIDE; already_AddRefed GetImage(); + private: + uint8_t *CreateI420Image(size_t aWidth, size_t aHeight); + mozilla::layers::ImageContainer *mImageContainer; nsRefPtr mImage; }; + }; } // namespace mozilla diff --git a/dom/bindings/Makefile.in b/dom/bindings/Makefile.in index 3f008078fd5c..f9da051c61f0 100644 --- a/dom/bindings/Makefile.in +++ b/dom/bindings/Makefile.in @@ -58,11 +58,10 @@ codegen_dependencies := \ $(GLOBAL_DEPS) \ $(NULL) -$(call include_deps,codegen.pp) +# The 1 is to make codegen.pp not optional. +$(call include_deps,codegen.pp,1) -codegen.pp: codegen.done - -codegen.done: $(codegen_dependencies) +codegen.pp: $(codegen_dependencies) $(call py_action,webidl,$(srcdir)) @$(TOUCH) $@ diff --git a/dom/bindings/mozwebidlcodegen/__init__.py b/dom/bindings/mozwebidlcodegen/__init__.py index 34b1f1bf8cbf..fdbb49164c94 100644 --- a/dom/bindings/mozwebidlcodegen/__init__.py +++ b/dom/bindings/mozwebidlcodegen/__init__.py @@ -546,7 +546,7 @@ def create_build_system_manager(topsrcdir, topobjdir, dist_dir): cache_dir=cache_dir, # The make rules include a codegen.pp file containing dependencies. make_deps_path=os.path.join(obj_dir, 'codegen.pp'), - make_deps_target='codegen.done', + make_deps_target='codegen.pp', ) diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index c48df3993060..7b2ddb2be277 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -15,6 +15,7 @@ #include "nsContentUtils.h" #include "nsPIDOMWindow.h" #include "WorkerPrivate.h" +#include "WorkerRunnable.h" #include "nsJSPrincipals.h" #include "nsJSUtils.h" #include "nsPIDOMWindow.h" @@ -60,8 +61,7 @@ class WorkerPromiseTask MOZ_FINAL : public WorkerRunnable { public: WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise) - : WorkerRunnable(aWorkerPrivate, WorkerThread, - UnchangedBusyCount, SkipWhenClearing) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) , mPromise(aPromise) { MOZ_ASSERT(aPromise); @@ -169,8 +169,7 @@ public: Promise* aPromise, JS::Handle aValue, Promise::PromiseState aState) - : WorkerRunnable(aWorkerPrivate, WorkerThread, - UnchangedBusyCount, SkipWhenClearing), + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), PromiseResolverMixin(aPromise, aValue, aState) {} diff --git a/dom/workers/MessagePort.cpp b/dom/workers/MessagePort.cpp index bf8012d0e0b9..f476d79d0ab7 100644 --- a/dom/workers/MessagePort.cpp +++ b/dom/workers/MessagePort.cpp @@ -11,6 +11,7 @@ #include "SharedWorker.h" #include "WorkerPrivate.h" +#include "WorkerRunnable.h" using mozilla::dom::EventHandlerNonNull; using mozilla::dom::MessagePortBase; @@ -28,13 +29,10 @@ class DelayedEventRunnable MOZ_FINAL : public WorkerRunnable public: DelayedEventRunnable(WorkerPrivate* aWorkerPrivate, - Target aTarget, + TargetAndBusyBehavior aBehavior, MessagePort* aMessagePort, nsTArray>& aEvents) - : WorkerRunnable(aWorkerPrivate, aTarget, - aTarget == WorkerThread ? ModifyBusyCount : UnchangedBusyCount, - SkipWhenClearing), - mMessagePort(aMessagePort) + : WorkerRunnable(aWorkerPrivate, aBehavior), mMessagePort(aMessagePort) { AssertIsOnMainThread(); MOZ_ASSERT(aMessagePort); @@ -109,16 +107,22 @@ MessagePort::Start() mStarted = true; if (!mQueuedEvents.IsEmpty()) { - WorkerRunnable::Target target = WorkerRunnable::WorkerThread; - WorkerPrivate* workerPrivate = mWorkerPrivate; + WorkerPrivate* workerPrivate; + WorkerRunnable::TargetAndBusyBehavior behavior; - if (!workerPrivate) { - target = WorkerRunnable::ParentThread; + if (mWorkerPrivate) { + workerPrivate = mWorkerPrivate; + behavior = WorkerRunnable::WorkerThreadModifyBusyCount; + } + else { workerPrivate = mSharedWorker->GetWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + behavior = WorkerRunnable::ParentThreadUnchangedBusyCount; } nsRefPtr runnable = - new DelayedEventRunnable(workerPrivate, target, this, mQueuedEvents); + new DelayedEventRunnable(workerPrivate, behavior, this, mQueuedEvents); runnable->Dispatch(nullptr); } } diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 9993feb43087..6cd7669b2412 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -43,6 +43,7 @@ #include "nsLayoutStatics.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" +#include "nsThread.h" #include "nsThreadUtils.h" #include "nsTraceRefcnt.h" #include "nsXPCOM.h" @@ -50,13 +51,18 @@ #include "OSFileConstants.h" #include "xpcpublic.h" -#include "SharedWorker.h" -#include "WorkerPrivate.h" - #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif +#ifdef DEBUG +#include "nsThreadManager.h" +#endif + +#include "SharedWorker.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + using namespace mozilla; using namespace mozilla::dom; @@ -593,6 +599,8 @@ void ErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aReport) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(worker); + return worker->ReportError(aCx, aMessage, aReport); } @@ -600,6 +608,7 @@ bool OperationCallback(JSContext* aCx) { WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(worker); // Now is a good time to turn on profiling if it's pending. profiler_js_operation_callback(); @@ -607,98 +616,42 @@ OperationCallback(JSContext* aCx) return worker->OperationCallback(aCx); } -class LogViolationDetailsRunnable : public nsRunnable +class LogViolationDetailsRunnable MOZ_FINAL : public nsRunnable { WorkerPrivate* mWorkerPrivate; + nsCOMPtr mSyncLoopTarget; nsString mFileName; uint32_t mLineNum; - uint32_t mSyncQueueKey; - -private: - class LogViolationDetailsResponseRunnable : public WorkerSyncRunnable - { - uint32_t mSyncQueueKey; - - public: - LogViolationDetailsResponseRunnable(WorkerPrivate* aWorkerPrivate, - uint32_t aSyncQueueKey) - : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, false), - mSyncQueueKey(aSyncQueueKey) - { - NS_ASSERTION(aWorkerPrivate, "Don't hand me a null WorkerPrivate!"); - } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true); - return true; - } - - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - AssertIsOnMainThread(); - } - }; public: LogViolationDetailsRunnable(WorkerPrivate* aWorker, const nsString& aFileName, uint32_t aLineNum) - : mWorkerPrivate(aWorker), - mFileName(aFileName), - mLineNum(aLineNum), - mSyncQueueKey(0) + : mWorkerPrivate(aWorker), mFileName(aFileName), mLineNum(aLineNum) { - NS_ASSERTION(aWorker, "WorkerPrivate cannot be null"); + MOZ_ASSERT(aWorker); } + NS_DECL_ISUPPORTS_INHERITED + bool Dispatch(JSContext* aCx) { AutoSyncLoopHolder syncLoop(mWorkerPrivate); - mSyncQueueKey = syncLoop.SyncQueueKey(); + + mSyncLoopTarget = syncLoop.EventTarget(); + MOZ_ASSERT(mSyncLoopTarget); if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { JS_ReportError(aCx, "Failed to dispatch to main thread!"); return false; } - return syncLoop.RunAndForget(aCx); + return syncLoop.Run(); } - NS_IMETHOD - Run() - { - AssertIsOnMainThread(); - - nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP(); - if (csp) { - NS_NAMED_LITERAL_STRING(scriptSample, - "Call to eval() or related function blocked by CSP."); - if (mWorkerPrivate->GetReportCSPViolations()) { - csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, - mFileName, scriptSample, mLineNum, EmptyString()); - } - } - - nsRefPtr response = - new LogViolationDetailsResponseRunnable(mWorkerPrivate, mSyncQueueKey); - if (!response->Dispatch(nullptr)) { - NS_WARNING("Failed to dispatch response!"); - } - - return NS_OK; - } +private: + NS_DECL_NSIRUNNABLE }; bool @@ -715,7 +668,7 @@ ContentSecurityPolicyAllows(JSContext* aCx) const char* file; if (JS_DescribeScriptedCaller(aCx, &script, &lineNum) && (file = JS_GetScriptFilename(aCx, script))) { - fileName.AssignASCII(file); + fileName = NS_ConvertUTF8toUTF16(file); } else { JS_ReportPendingException(aCx); } @@ -959,81 +912,158 @@ private: WorkerPrivate* mWorkerPrivate; }; -class WorkerThreadRunnable : public nsRunnable +class WorkerThreadPrimaryRunnable MOZ_FINAL : public nsRunnable { WorkerPrivate* mWorkerPrivate; + nsRefPtr mThread; + + class FinishedRunnable MOZ_FINAL : public nsRunnable + { + nsRefPtr mThread; + + public: + FinishedRunnable(already_AddRefed aThread) + : mThread(aThread) + { + MOZ_ASSERT(mThread); + } + + NS_DECL_ISUPPORTS_INHERITED + + private: + ~FinishedRunnable() + { } + + NS_DECL_NSIRUNNABLE + }; public: - WorkerThreadRunnable(WorkerPrivate* aWorkerPrivate) - : mWorkerPrivate(aWorkerPrivate) + WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate, + RuntimeService::WorkerThread* aThread) + : mWorkerPrivate(aWorkerPrivate), mThread(aThread) { - NS_ASSERTION(mWorkerPrivate, "This should never be null!"); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aThread); } - NS_IMETHOD - Run() + NS_DECL_ISUPPORTS_INHERITED + +private: + ~WorkerThreadPrimaryRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class WorkerTaskRunnable MOZ_FINAL : public WorkerRunnable +{ + nsRefPtr mTask; + +public: + WorkerTaskRunnable(WorkerPrivate* aWorkerPrivate, WorkerTask* aTask) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mTask(aTask) { -#ifdef MOZ_NUWA_PROCESS - if (IsNuwaProcess()) { - NS_ASSERTION(NuwaMarkCurrentThread != nullptr, - "NuwaMarkCurrentThread is undefined!"); - NuwaMarkCurrentThread(nullptr, nullptr); - NuwaFreezeCurrentThread(); - } -#endif - WorkerPrivate* workerPrivate = mWorkerPrivate; - mWorkerPrivate = nullptr; + MOZ_ASSERT(aTask); + } - workerPrivate->AssertIsOnWorkerThread(); +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + // May be called on any thread! + return true; + } - { - nsCycleCollector_startup(); + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE + { + // May be called on any thread! + } - WorkerJSRuntime runtime(workerPrivate); - JSRuntime* rt = runtime.Runtime(); - JSContext* cx = CreateJSContextForWorker(workerPrivate, rt); - if (!cx) { - // XXX need to fire an error at parent. - NS_ERROR("Failed to create runtime and context!"); - return NS_ERROR_FAILURE; - } - - char aLocal; - profiler_register_thread("WebWorker", &aLocal); - #ifdef MOZ_ENABLE_PROFILER_SPS - if (PseudoStack* stack = mozilla_get_pseudo_stack()) - stack->sampleRuntime(rt); - #endif - - { - JSAutoRequest ar(cx); - workerPrivate->DoRunLoop(cx); - } - - // Destroy the main context. This will unroot the main worker global and - // GC. This is not the last JSContext (WorkerJSRuntime maintains an - // internal JSContext). - JS_DestroyContext(cx); - - // Now WorkerJSRuntime goes out of scope and its destructor will shut - // down the cycle collector and destroy the final JSContext. This - // breaks any remaining cycles and collects the C++ and JS objects - // participating. - } - -#ifdef MOZ_ENABLE_PROFILER_SPS - if (PseudoStack* stack = mozilla_get_pseudo_stack()) - stack->sampleRuntime(nullptr); -#endif - - workerPrivate->ScheduleDeletion(false); - profiler_unregister_thread(); - return NS_OK; + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + return mTask->RunTask(aCx); } }; } /* anonymous namespace */ +class RuntimeService::WorkerThread MOZ_FINAL : public nsThread +{ + class Observer MOZ_FINAL : public nsIThreadObserver + { + WorkerPrivate* mWorkerPrivate; + + public: + Observer(WorkerPrivate* aWorkerPrivate) + : mWorkerPrivate(aWorkerPrivate) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + NS_DECL_THREADSAFE_ISUPPORTS + + private: + ~Observer() + { + mWorkerPrivate->AssertIsOnWorkerThread(); + } + + NS_DECL_NSITHREADOBSERVER + }; + + WorkerPrivate* mWorkerPrivate; + nsRefPtr mObserver; + +#ifdef DEBUG + // Protected by nsThread::mLock. + bool mAcceptingNonWorkerRunnables; +#endif + +public: + static already_AddRefed + Create(); + + void + SetWorker(WorkerPrivate* aWorkerPrivate); + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE; + +#ifdef DEBUG + bool + IsAcceptingNonWorkerRunnables() + { + MutexAutoLock lock(mLock); + return mAcceptingNonWorkerRunnables; + } + + void + SetAcceptingNonWorkerRunnables(bool aAcceptingNonWorkerRunnables) + { + MutexAutoLock lock(mLock); + mAcceptingNonWorkerRunnables = aAcceptingNonWorkerRunnables; + } +#endif + +private: + WorkerThread() + : nsThread(nsThread::NOT_MAIN_THREAD, WORKER_STACK_SIZE), + mWorkerPrivate(nullptr) +#ifdef DEBUG + , mAcceptingNonWorkerRunnables(true) +#endif + { } + + ~WorkerThread() + { } +}; + BEGIN_WORKERS_NAMESPACE // Entry point for main thread non-window globals. @@ -1113,71 +1143,64 @@ ResumeWorkersForWindow(nsPIDOMWindow* aWindow) } } -namespace { - -class WorkerTaskRunnable : public WorkerRunnable +WorkerCrossThreadDispatcher::WorkerCrossThreadDispatcher( + WorkerPrivate* aWorkerPrivate) +: mMutex("WorkerCrossThreadDispatcher::mMutex"), + mWorkerPrivate(aWorkerPrivate) { -public: - WorkerTaskRunnable(WorkerPrivate* aPrivate, WorkerTask* aTask) - : WorkerRunnable(aPrivate, WorkerThread, UnchangedBusyCount, - SkipWhenClearing), - mTask(aTask) - { } - - virtual bool PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { - return true; - } - - virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { } - - virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate); - -private: - nsRefPtr mTask; -}; - -bool -WorkerTaskRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) -{ - return mTask->RunTask(aCx); -} - + MOZ_ASSERT(aWorkerPrivate); } bool WorkerCrossThreadDispatcher::PostTask(WorkerTask* aTask) { - mozilla::MutexAutoLock lock(mMutex); - if (!mPrivate) { + MOZ_ASSERT(aTask); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + NS_WARNING("Posted a task to a WorkerCrossThreadDispatcher that is no " + "longer accepting tasks!"); return false; } - nsRefPtr runnable = new WorkerTaskRunnable(mPrivate, aTask); - runnable->Dispatch(nullptr); - return true; + nsRefPtr runnable = + new WorkerTaskRunnable(mWorkerPrivate, aTask); + return runnable->Dispatch(nullptr); } WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx) { - NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - return static_cast(JS_GetRuntimePrivate(JS_GetRuntime(aCx)))->mWorkerPrivate; + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aCx); + + JSRuntime* rt = JS_GetRuntime(aCx); + MOZ_ASSERT(rt); + + void* rtPrivate = JS_GetRuntimePrivate(rt); + MOZ_ASSERT(rtPrivate); + + return static_cast(rtPrivate)->mWorkerPrivate; } WorkerPrivate* GetCurrentThreadWorkerPrivate() { - MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!NS_IsMainThread()); + CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get(); if (!ccrt) { return nullptr; } JSRuntime* rt = ccrt->Runtime(); - return static_cast(JS_GetRuntimePrivate(rt))-> - mWorkerPrivate; + MOZ_ASSERT(rt); + + void* rtPrivate = JS_GetRuntimePrivate(rt); + MOZ_ASSERT(rtPrivate); + + return static_cast(rtPrivate)->mWorkerPrivate; } bool @@ -1427,7 +1450,7 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) } if (!domainInfo->ActiveWorkerCount()) { - NS_ASSERTION(domainInfo->mQueuedWorkers.IsEmpty(), "Huh?!"); + MOZ_ASSERT(domainInfo->mQueuedWorkers.IsEmpty()); mDomainMap.Remove(domain); } } @@ -1455,16 +1478,11 @@ RuntimeService::UnregisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) nsPIDOMWindow* window = aWorkerPrivate->GetWindow(); nsTArray* windowArray; - if (!mWindowMap.Get(window, &windowArray)) { - MOZ_ASSERT(false, "Don't have an entry for this window!"); - } + MOZ_ALWAYS_TRUE(mWindowMap.Get(window, &windowArray)); - if (!windowArray->RemoveElement(aWorkerPrivate)) { - MOZ_ASSERT(false, "Worker wasn't in the correct window array!"); - } + MOZ_ALWAYS_TRUE(windowArray->RemoveElement(aWorkerPrivate)); if (windowArray->IsEmpty()) { - MOZ_ASSERT(!queuedWorker, "queuedWorker should be in this array!"); mWindowMap.Remove(window); } } @@ -1482,7 +1500,7 @@ RuntimeService::ScheduleWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) return true; } - nsCOMPtr thread; + nsRefPtr thread; { MutexAutoLock lock(mMutex); if (!mIdleThreadArray.IsEmpty()) { @@ -1493,35 +1511,36 @@ RuntimeService::ScheduleWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate) } if (!thread) { - if (NS_FAILED(NS_NewNamedThread("DOM Worker", - getter_AddRefs(thread), nullptr, - WORKER_STACK_SIZE))) { + thread = WorkerThread::Create(); + if (!thread) { UnregisterWorker(aCx, aWorkerPrivate); JS_ReportError(aCx, "Could not create new thread!"); return false; } } + MOZ_ASSERT(thread->IsAcceptingNonWorkerRunnables()); + int32_t priority = aWorkerPrivate->IsChromeWorker() ? nsISupportsPriority::PRIORITY_NORMAL : nsISupportsPriority::PRIORITY_LOW; - nsCOMPtr threadPriority = do_QueryInterface(thread); - if (!threadPriority || NS_FAILED(threadPriority->SetPriority(priority))) { + if (NS_FAILED(thread->SetPriority(priority))) { NS_WARNING("Could not set the thread's priority!"); } -#ifdef DEBUG - aWorkerPrivate->SetThread(thread); -#endif - - nsCOMPtr runnable = new WorkerThreadRunnable(aWorkerPrivate); + nsCOMPtr runnable = + new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread); if (NS_FAILED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { UnregisterWorker(aCx, aWorkerPrivate); JS_ReportError(aCx, "Could not dispatch to thread!"); return false; } +#ifdef DEBUG + thread->SetAcceptingNonWorkerRunnables(false); +#endif + return true; } @@ -1541,7 +1560,7 @@ RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */) TimeStamp nextExpiration; - nsAutoTArray, 20> expiredThreads; + nsAutoTArray, 20> expiredThreads; { MutexAutoLock lock(runtime->mMutex); @@ -1553,7 +1572,7 @@ RuntimeService::ShutdownIdleThreads(nsITimer* aTimer, void* /* aClosure */) break; } - nsCOMPtr* thread = expiredThreads.AppendElement(); + nsRefPtr* thread = expiredThreads.AppendElement(); thread->swap(info.mThread); } @@ -1756,7 +1775,6 @@ RuntimeService::Shutdown() mDomainMap.EnumerateRead(AddAllTopLevelWorkersToArray, &workers); if (!workers.IsEmpty()) { - // Cancel all top-level workers. { MutexAutoUnlock unlock(mMutex); @@ -1803,7 +1821,7 @@ RuntimeService::Cleanup() // Shut down any idle threads. if (!mIdleThreadArray.IsEmpty()) { - nsAutoTArray, 20> idleThreads; + nsAutoTArray, 20> idleThreads; uint32_t idleThreadCount = mIdleThreadArray.Length(); idleThreads.SetLength(idleThreadCount); @@ -2186,10 +2204,14 @@ RuntimeService::ForgetSharedWorker(WorkerPrivate* aWorkerPrivate) } void -RuntimeService::NoteIdleThread(nsIThread* aThread) +RuntimeService::NoteIdleThread(WorkerThread* aThread) { AssertIsOnMainThread(); - NS_ASSERTION(aThread, "Null pointer!"); + MOZ_ASSERT(aThread); + +#ifdef DEBUG + aThread->SetAcceptingNonWorkerRunnables(true); +#endif static TimeDuration timeout = TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC); @@ -2216,19 +2238,15 @@ RuntimeService::NoteIdleThread(nsIThread* aThread) // Too many idle threads, just shut this one down. if (shutdown) { - if (NS_FAILED(aThread->Shutdown())) { - NS_WARNING("Failed to shutdown thread!"); - } + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aThread->Shutdown())); return; } // Schedule timer. - if (NS_FAILED(mIdleThreadTimer-> - InitWithFuncCallback(ShutdownIdleThreads, nullptr, - IDLE_THREAD_TIMEOUT_SEC * 1000, - nsITimer::TYPE_ONE_SHOT))) { - NS_ERROR("Can't schedule timer!"); - } + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mIdleThreadTimer->InitWithFuncCallback( + ShutdownIdleThreads, nullptr, + IDLE_THREAD_TIMEOUT_SEC * 1000, + nsITimer::TYPE_ONE_SHOT))); } void @@ -2354,3 +2372,272 @@ RuntimeService::JSVersionChanged(const char* /* aPrefName */, void* /* aClosure JS::CompartmentOptions& options = sDefaultJSSettings.content.compartmentOptions; options.setVersion(useLatest ? JSVERSION_LATEST : JSVERSION_DEFAULT); } + +// static +already_AddRefed +RuntimeService::WorkerThread::Create() +{ + MOZ_ASSERT(nsThreadManager::get()); + + nsRefPtr thread = new WorkerThread(); + if (NS_FAILED(thread->Init())) { + NS_WARNING("Failed to create new thread!"); + return nullptr; + } + + NS_SetThreadName(thread, "DOM Worker"); + + return thread.forget(); +} + +void +RuntimeService::WorkerThread::SetWorker(WorkerPrivate* aWorkerPrivate) +{ + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + MOZ_ASSERT_IF(aWorkerPrivate, !mWorkerPrivate); + MOZ_ASSERT_IF(!aWorkerPrivate, mWorkerPrivate); + + // No need to lock here because mWorkerPrivate is only modified on mThread. + + if (mWorkerPrivate) { + MOZ_ASSERT(mObserver); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(RemoveObserver(mObserver))); + + mObserver = nullptr; + mWorkerPrivate->SetThread(nullptr); + } + + mWorkerPrivate = aWorkerPrivate; + + if (mWorkerPrivate) { + mWorkerPrivate->SetThread(this); + + nsRefPtr observer = new Observer(mWorkerPrivate); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(AddObserver(observer))); + + mObserver.swap(observer); + } +} + +NS_IMPL_ISUPPORTS_INHERITED0(RuntimeService::WorkerThread, nsThread) + +NS_IMETHODIMP +RuntimeService::WorkerThread::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) +{ + // May be called on any thread! + +#ifdef DEBUG + if (PR_GetCurrentThread() == mThread) { + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + } + else if (aRunnable && !IsAcceptingNonWorkerRunnables()) { + // Only enforce cancelable runnables after we've started the worker loop. + nsCOMPtr cancelable = do_QueryInterface(aRunnable); + MOZ_ASSERT(cancelable, + "Should have been wrapped by the worker's event target!"); + } +#endif + + // Workers only support asynchronous dispatch for now. + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_UNEXPECTED; + } + + nsIRunnable* runnableToDispatch; + nsRefPtr workerRunnable; + + if (aRunnable && PR_GetCurrentThread() == mThread) { + // No need to lock here because mWorkerPrivate is only modified on mThread. + workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(aRunnable); + runnableToDispatch = workerRunnable; + } + else { + runnableToDispatch = aRunnable; + } + + nsresult rv = nsThread::Dispatch(runnableToDispatch, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(RuntimeService::WorkerThread::Observer, nsIThreadObserver) + +NS_IMETHODIMP +RuntimeService::WorkerThread::Observer::OnDispatchedEvent( + nsIThreadInternal* /*aThread */) +{ + MOZ_ASSUME_UNREACHABLE("This should never be called!"); +} + +NS_IMETHODIMP +RuntimeService::WorkerThread::Observer::OnProcessNextEvent( + nsIThreadInternal* /* aThread */, + bool aMayWait, + uint32_t aRecursionDepth) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(!aMayWait); + + mWorkerPrivate->OnProcessNextEvent(aRecursionDepth); + return NS_OK; +} + +NS_IMETHODIMP +RuntimeService::WorkerThread::Observer::AfterProcessNextEvent( + nsIThreadInternal* /* aThread */, + uint32_t aRecursionDepth, + bool /* aEventWasProcessed */) +{ + mWorkerPrivate->AssertIsOnWorkerThread(); + + mWorkerPrivate->AfterProcessNextEvent(aRecursionDepth); + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(LogViolationDetailsRunnable, nsRunnable) + +NS_IMETHODIMP +LogViolationDetailsRunnable::Run() +{ + AssertIsOnMainThread(); + + nsIContentSecurityPolicy* csp = mWorkerPrivate->GetCSP(); + if (csp) { + NS_NAMED_LITERAL_STRING(scriptSample, + "Call to eval() or related function blocked by CSP."); + if (mWorkerPrivate->GetReportCSPViolations()) { + csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, + mFileName, scriptSample, mLineNum, + EmptyString()); + } + } + + nsRefPtr response = + new MainThreadStopSyncLoopRunnable(mWorkerPrivate, mSyncLoopTarget.forget(), + true); + MOZ_ALWAYS_TRUE(response->Dispatch(nullptr)); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable, nsRunnable) + +NS_IMETHODIMP +WorkerThreadPrimaryRunnable::Run() +{ +#ifdef MOZ_NUWA_PROCESS + if (IsNuwaProcess()) { + NS_ASSERTION(NuwaMarkCurrentThread != nullptr, + "NuwaMarkCurrentThread is undefined!"); + NuwaMarkCurrentThread(nullptr, nullptr); + NuwaFreezeCurrentThread(); + } +#endif + + char stackBaseGuess; + + nsAutoCString threadName; + threadName.AssignLiteral("WebWorker '"); + threadName.Append(NS_LossyConvertUTF16toASCII(mWorkerPrivate->ScriptURL())); + threadName.Append('\''); + + profiler_register_thread(threadName.get(), &stackBaseGuess); + + mThread->SetWorker(mWorkerPrivate); + + mWorkerPrivate->AssertIsOnWorkerThread(); + + { + nsCycleCollector_startup(); + + WorkerJSRuntime runtime(mWorkerPrivate); + JSRuntime* rt = runtime.Runtime(); + + JSContext* cx = CreateJSContextForWorker(mWorkerPrivate, rt); + if (!cx) { + // XXX need to fire an error at parent. + NS_ERROR("Failed to create runtime and context!"); + return NS_ERROR_FAILURE; + } + + { +#ifdef MOZ_ENABLE_PROFILER_SPS + PseudoStack* stack = mozilla_get_pseudo_stack(); + if (stack) { + stack->sampleRuntime(rt); + } +#endif + + { + JSAutoRequest ar(cx); + + mWorkerPrivate->DoRunLoop(cx); + + JS_ReportPendingException(cx); + } + +#ifdef MOZ_ENABLE_PROFILER_SPS + if (stack) { + stack->sampleRuntime(nullptr); + } +#endif + } + + // Destroy the main context. This will unroot the main worker global and + // GC. This is not the last JSContext (WorkerJSRuntime maintains an + // internal JSContext). + JS_DestroyContext(cx); + + // Now WorkerJSRuntime goes out of scope and its destructor will shut + // down the cycle collector and destroy the final JSContext. This + // breaks any remaining cycles and collects the C++ and JS objects + // participating. + } + + mThread->SetWorker(nullptr); + + mWorkerPrivate->ScheduleDeletion(); + + // It is no longer safe to touch mWorkerPrivate. + mWorkerPrivate = nullptr; + + // Now recycle this thread. + nsCOMPtr mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + + nsRefPtr finishedRunnable = + new FinishedRunnable(mThread.forget()); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(finishedRunnable, + NS_DISPATCH_NORMAL))); + + profiler_unregister_thread(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadPrimaryRunnable::FinishedRunnable, + nsRunnable) + +NS_IMETHODIMP +WorkerThreadPrimaryRunnable::FinishedRunnable::Run() +{ + AssertIsOnMainThread(); + + nsRefPtr thread; + mThread.swap(thread); + + RuntimeService* rts = RuntimeService::GetService(); + if (rts) { + rts->NoteIdleThread(thread); + } + else if (thread->ShutdownRequired()) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->Shutdown())); + } + + return NS_OK; +} diff --git a/dom/workers/RuntimeService.h b/dom/workers/RuntimeService.h index 79fe6139ce0d..57a17a59c33e 100644 --- a/dom/workers/RuntimeService.h +++ b/dom/workers/RuntimeService.h @@ -11,18 +11,13 @@ #include "nsIObserver.h" -#include "mozilla/Attributes.h" -#include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/BindingDeclarations.h" -#include "nsAutoPtr.h" #include "nsClassHashtable.h" -#include "nsCOMPtr.h" -#include "nsCycleCollectionParticipant.h" #include "nsHashKeys.h" -#include "nsString.h" #include "nsTArray.h" +class nsIRunnable; class nsIThread; class nsITimer; class nsPIDOMWindow; @@ -34,6 +29,10 @@ class WorkerPrivate; class RuntimeService MOZ_FINAL : public nsIObserver { +public: + class WorkerThread; + +private: struct SharedWorkerInfo { WorkerPrivate* mWorkerPrivate; @@ -68,7 +67,7 @@ class RuntimeService MOZ_FINAL : public nsIObserver struct IdleThreadInfo { - nsCOMPtr mThread; + nsRefPtr mThread; mozilla::TimeStamp mExpirationTime; }; @@ -173,7 +172,7 @@ public: } void - NoteIdleThread(nsIThread* aThread); + NoteIdleThread(WorkerThread* aThread); static void GetDefaultJSSettings(JSSettings& aSettings) diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index edee86a8eada..5d1fae8dada4 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -35,6 +35,7 @@ #include "Principal.h" #include "WorkerFeature.h" #include "WorkerPrivate.h" +#include "WorkerRunnable.h" #define MAX_CONCURRENT_SCRIPTS 1000 @@ -131,8 +132,6 @@ ChannelFromScriptURL(nsIPrincipal* principal, return rv; } -class ScriptLoaderRunnable; - struct ScriptLoadInfo { ScriptLoadInfo() @@ -155,7 +154,9 @@ struct ScriptLoadInfo bool mExecutionResult; }; -class ScriptExecutorRunnable : public WorkerSyncRunnable +class ScriptLoaderRunnable; + +class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable { ScriptLoaderRunnable& mScriptLoader; uint32_t mFirstIndex; @@ -163,38 +164,29 @@ class ScriptExecutorRunnable : public WorkerSyncRunnable public: ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, - uint32_t aSyncQueueKey, uint32_t aFirstIndex, + nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex, uint32_t aLastIndex); - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - AssertIsOnMainThread(); - return true; - } +private: + ~ScriptExecutorRunnable() + { } - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - AssertIsOnMainThread(); - } + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate); - - void - PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult); + virtual void + PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) + MOZ_OVERRIDE; }; -class ScriptLoaderRunnable : public WorkerFeature, - public nsIRunnable, - public nsIStreamLoaderObserver +class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature, + public nsIRunnable, + public nsIStreamLoaderObserver { friend class ScriptExecutorRunnable; WorkerPrivate* mWorkerPrivate; - uint32_t mSyncQueueKey; + nsCOMPtr mSyncLoopTarget; nsTArray mLoadInfos; bool mIsWorkerScript; bool mCanceled; @@ -204,21 +196,26 @@ public: NS_DECL_THREADSAFE_ISUPPORTS ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, - uint32_t aSyncQueueKey, + nsIEventTarget* aSyncLoopTarget, nsTArray& aLoadInfos, bool aIsWorkerScript) - : mWorkerPrivate(aWorkerPrivate), mSyncQueueKey(aSyncQueueKey), + : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), mIsWorkerScript(aIsWorkerScript), mCanceled(false), mCanceledMainThread(false) { aWorkerPrivate->AssertIsOnWorkerThread(); - NS_ASSERTION(!aIsWorkerScript || aLoadInfos.Length() == 1, "Bad args!"); + MOZ_ASSERT(aSyncLoopTarget); + MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1); mLoadInfos.SwapElements(aLoadInfos); } +private: + ~ScriptLoaderRunnable() + { } + NS_IMETHOD - Run() + Run() MOZ_OVERRIDE { AssertIsOnMainThread(); @@ -232,7 +229,7 @@ public: NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, nsresult aStatus, uint32_t aStringLen, - const uint8_t* aString) + const uint8_t* aString) MOZ_OVERRIDE { AssertIsOnMainThread(); @@ -256,8 +253,8 @@ public: return NS_OK; } - bool - Notify(JSContext* aCx, Status aStatus) + virtual bool + Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE { mWorkerPrivate->AssertIsOnWorkerThread(); @@ -587,9 +584,10 @@ public: if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) { nsRefPtr runnable = - new ScriptExecutorRunnable(*this, mSyncQueueKey, firstIndex, lastIndex); + new ScriptExecutorRunnable(*this, mSyncLoopTarget, firstIndex, + lastIndex); if (!runnable->Dispatch(nullptr)) { - NS_ERROR("This should never fail!"); + MOZ_ASSERT(false, "This should never fail!"); } } } @@ -597,45 +595,26 @@ public: NS_IMPL_ISUPPORTS2(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver) -class StopSyncLoopRunnable MOZ_FINAL : public MainThreadSyncRunnable -{ -public: - StopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate, - uint32_t aSyncQueueKey) - : MainThreadSyncRunnable(aWorkerPrivate, SkipWhenClearing, aSyncQueueKey, - false) - { } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE - { - aWorkerPrivate->AssertIsOnWorkerThread(); - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true); - return true; - } -}; - class ChannelGetterRunnable MOZ_FINAL : public nsRunnable { WorkerPrivate* mParentWorker; - uint32_t mSyncQueueKey; + nsCOMPtr mSyncLoopTarget; const nsAString& mScriptURL; nsIChannel** mChannel; nsresult mResult; public: ChannelGetterRunnable(WorkerPrivate* aParentWorker, - uint32_t aSyncQueueKey, + nsIEventTarget* aSyncLoopTarget, const nsAString& aScriptURL, nsIChannel** aChannel) - : mParentWorker(aParentWorker), mSyncQueueKey(aSyncQueueKey), + : mParentWorker(aParentWorker), mSyncLoopTarget(aSyncLoopTarget), mScriptURL(aScriptURL), mChannel(aChannel), mResult(NS_ERROR_FAILURE) { aParentWorker->AssertIsOnWorkerThread(); + MOZ_ASSERT(aSyncLoopTarget); } - virtual ~ChannelGetterRunnable() { } - NS_IMETHOD Run() MOZ_OVERRIDE { @@ -660,8 +639,9 @@ public: channel.forget(mChannel); } - nsRefPtr runnable = - new StopSyncLoopRunnable(mParentWorker, mSyncQueueKey); + nsRefPtr runnable = + new MainThreadStopSyncLoopRunnable(mParentWorker, + mSyncLoopTarget.forget(), true); if (!runnable->Dispatch(nullptr)) { NS_ERROR("This should never fail!"); } @@ -675,19 +655,21 @@ public: return mResult; } +private: + virtual ~ChannelGetterRunnable() + { } }; ScriptExecutorRunnable::ScriptExecutorRunnable( ScriptLoaderRunnable& aScriptLoader, - uint32_t aSyncQueueKey, + nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex, uint32_t aLastIndex) -: WorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncQueueKey), +: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget), mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex) { - NS_ASSERTION(aFirstIndex <= aLastIndex, "Bad first index!"); - NS_ASSERTION(aLastIndex < aScriptLoader.mLoadInfos.Length(), - "Bad last index!"); + MOZ_ASSERT(aFirstIndex <= aLastIndex); + MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length()); } bool @@ -759,7 +741,7 @@ ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, } aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader); - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, result); + aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, result); } } @@ -773,7 +755,7 @@ LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate, AutoSyncLoopHolder syncLoop(aWorkerPrivate); nsRefPtr loader = - new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.SyncQueueKey(), + new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(), aLoadInfos, aIsWorkerScript); NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!"); @@ -789,7 +771,7 @@ LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate, return false; } - return syncLoop.RunAndForget(aCx); + return syncLoop.Run(); } } /* anonymous namespace */ @@ -832,15 +814,15 @@ ChannelFromScriptURLWorkerThread(JSContext* aCx, AutoSyncLoopHolder syncLoop(aParent); nsRefPtr getter = - new ChannelGetterRunnable(aParent, syncLoop.SyncQueueKey(), - aScriptURL, aChannel); + new ChannelGetterRunnable(aParent, syncLoop.EventTarget(), aScriptURL, + aChannel); if (NS_FAILED(NS_DispatchToMainThread(getter, NS_DISPATCH_NORMAL))) { NS_ERROR("Failed to dispatch!"); return NS_ERROR_FAILURE; } - if (!syncLoop.RunAndForget(aCx)) { + if (!syncLoop.Run()) { return NS_ERROR_FAILURE; } diff --git a/dom/workers/URL.cpp b/dom/workers/URL.cpp index d9b69ff67978..96bc2a826714 100644 --- a/dom/workers/URL.cpp +++ b/dom/workers/URL.cpp @@ -4,24 +4,24 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "URL.h" -#include "File.h" - -#include "WorkerPrivate.h" -#include "nsThreadUtils.h" - -#include "nsPIDOMWindow.h" -#include "nsGlobalWindow.h" -#include "nsHostObjectProtocolHandler.h" -#include "nsServiceManagerUtils.h" #include "nsIDocument.h" #include "nsIDOMFile.h" +#include "nsIIOService.h" +#include "nsPIDOMWindow.h" #include "mozilla/dom/URL.h" #include "mozilla/dom/URLBinding.h" #include "mozilla/dom/URLSearchParams.h" -#include "nsIIOService.h" +#include "nsGlobalWindow.h" +#include "nsHostObjectProtocolHandler.h" #include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#include "File.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" BEGIN_WORKERS_NAMESPACE using mozilla::dom::GlobalObject; @@ -67,43 +67,7 @@ class URLRunnable : public nsRunnable { protected: WorkerPrivate* mWorkerPrivate; - uint32_t mSyncQueueKey; - -private: - class ResponseRunnable : public WorkerSyncRunnable - { - uint32_t mSyncQueueKey; - - public: - ResponseRunnable(WorkerPrivate* aWorkerPrivate, - uint32_t aSyncQueueKey) - : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, false), - mSyncQueueKey(aSyncQueueKey) - { - NS_ASSERTION(aWorkerPrivate, "Don't hand me a null WorkerPrivate!"); - } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true); - return true; - } - - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - AssertIsOnMainThread(); - } - }; + nsCOMPtr mSyncLoopTarget; protected: URLRunnable(WorkerPrivate* aWorkerPrivate) @@ -117,15 +81,17 @@ public: Dispatch(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); + AutoSyncLoopHolder syncLoop(mWorkerPrivate); - mSyncQueueKey = syncLoop.SyncQueueKey(); + + mSyncLoopTarget = syncLoop.EventTarget(); if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { JS_ReportError(aCx, "Failed to dispatch to main thread!"); return false; } - return syncLoop.RunAndForget(aCx); + return syncLoop.Run(); } private: @@ -135,8 +101,10 @@ private: MainThreadRun(); - nsRefPtr response = - new ResponseRunnable(mWorkerPrivate, mSyncQueueKey); + nsRefPtr response = + new MainThreadStopSyncLoopRunnable(mWorkerPrivate, + mSyncLoopTarget.forget(), + true); if (!response->Dispatch(nullptr)) { NS_WARNING("Failed to dispatch response!"); } diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index f89f14a88165..bd8f60bea70d 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -23,6 +23,7 @@ #include "nsIScriptSecurityManager.h" #include "nsPIDOMWindow.h" #include "nsITextToSubURI.h" +#include "nsIThreadInternal.h" #include "nsITimer.h" #include "nsIURI.h" #include "nsIURL.h" @@ -32,11 +33,13 @@ #include "jsfriendapi.h" #include "js/OldDebugAPI.h" #include "js/MemoryMetrics.h" +#include "mozilla/Assertions.h" #include "mozilla/ContentEvents.h" #include "mozilla/Likely.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/ErrorEventBinding.h" +#include "mozilla/dom/Exceptions.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/ImageData.h" #include "mozilla/dom/ImageDataBinding.h" @@ -64,7 +67,10 @@ #include #endif -#include "mozilla/dom/Exceptions.h" +#ifdef DEBUG +#include "nsThreadManager.h" +#endif + #include "File.h" #include "MessagePort.h" #include "Principal.h" @@ -72,32 +78,53 @@ #include "ScriptLoader.h" #include "SharedWorker.h" #include "WorkerFeature.h" +#include "WorkerRunnable.h" #include "WorkerScope.h" -// GC will run once every thirty seconds during normal execution. -#define NORMAL_GC_TIMER_DELAY_MS 30000 +// JS_MaybeGC will run once every second during normal execution. +#define PERIODIC_GC_TIMER_DELAY_SEC 1 -// GC will run five seconds after the last event is processed. -#define IDLE_GC_TIMER_DELAY_MS 5000 +// A shrinking GC will run five seconds after the last event is processed. +#define IDLE_GC_TIMER_DELAY_SEC 5 #define PREF_WORKERS_ENABLED "dom.workers.enabled" -using mozilla::InternalScriptErrorEvent; -using mozilla::MutexAutoLock; -using mozilla::TimeDuration; -using mozilla::TimeStamp; -using mozilla::dom::Throw; -using mozilla::AutoCxPusher; -using mozilla::AutoPushJSContext; -using mozilla::AutoSafeJSContext; +#ifdef WORKER_LOGGING +#define LOG(_args) do { printf _args ; fflush(stdout); } while (0) +#else +#define LOG(_args) do { } while (0) +#endif -USING_WORKERS_NAMESPACE +using namespace mozilla; using namespace mozilla::dom; +USING_WORKERS_NAMESPACE MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf) +#ifdef DEBUG + +BEGIN_WORKERS_NAMESPACE + +void +AssertIsOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); +} + +END_WORKERS_NAMESPACE + +#endif + namespace { +#ifdef DEBUG + +const nsIID kDEBUGWorkerEventTargetIID = { + 0xccaba3fa, 0x5be2, 0x4de2, { 0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb } +}; + +#endif + template class AutoPtrComparator { @@ -142,6 +169,51 @@ SwapToISupportsArray(SmartPtr& aSrc, dest->swap(rawSupports); } +// This class is used to wrap any runnables that the worker receives via the +// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or +// from the worker's EventTarget). +class ExternalRunnableWrapper MOZ_FINAL : public WorkerRunnable +{ + nsCOMPtr mWrappedRunnable; + +public: + ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate, + nsICancelableRunnable* aWrappedRunnable) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mWrappedRunnable(aWrappedRunnable) + { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWrappedRunnable); + } + + NS_DECL_ISUPPORTS_INHERITED + +private: + ~ExternalRunnableWrapper() + { } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + nsresult rv = mWrappedRunnable->Run(); + if (NS_FAILED(rv)) { + if (!JS_IsExceptionPending(aCx)) { + Throw(aCx, rv); + } + return false; + } + return true; + } + + NS_IMETHOD + Cancel() MOZ_OVERRIDE + { + nsresult rv = mWrappedRunnable->Cancel(); + nsresult rv2 = WorkerRunnable::Cancel(); + return NS_FAILED(rv) ? rv : rv2; + } +}; + struct WindowAction { nsPIDOMWindow* mWindow; @@ -623,87 +695,75 @@ JSStructuredCloneCallbacks gMainThreadChromeWorkerStructuredCloneCallbacks = { MainThreadChromeWorkerStructuredCloneCallbacks::Error }; -class MainThreadReleaseRunnable : public nsRunnable +class MainThreadReleaseRunnable MOZ_FINAL : public nsRunnable { - nsCOMPtr mThread; - nsTArray > mDoomed; + nsTArray> mDoomed; nsTArray mHostObjectURIs; public: - MainThreadReleaseRunnable(nsCOMPtr& aThread, - nsTArray >& aDoomed, + MainThreadReleaseRunnable(nsTArray>& aDoomed, nsTArray& aHostObjectURIs) { - mThread.swap(aThread); mDoomed.SwapElements(aDoomed); mHostObjectURIs.SwapElements(aHostObjectURIs); } - MainThreadReleaseRunnable(nsTArray >& aDoomed, - nsTArray& aHostObjectURIs) - { - mDoomed.SwapElements(aDoomed); - mHostObjectURIs.SwapElements(aHostObjectURIs); - } + NS_DECL_ISUPPORTS_INHERITED NS_IMETHOD - Run() + Run() MOZ_OVERRIDE { mDoomed.Clear(); - if (mThread) { - RuntimeService* runtime = RuntimeService::GetService(); - NS_ASSERTION(runtime, "This should never be null!"); - - runtime->NoteIdleThread(mThread); - } - - for (uint32_t i = 0, len = mHostObjectURIs.Length(); i < len; ++i) { - nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[i]); + for (uint32_t index = 0; index < mHostObjectURIs.Length(); index++) { + nsHostObjectProtocolHandler::RemoveDataEntry(mHostObjectURIs[index]); } return NS_OK; } + +private: + ~MainThreadReleaseRunnable() + { } }; -class WorkerFinishedRunnable : public WorkerControlRunnable +class WorkerFinishedRunnable MOZ_FINAL : public WorkerControlRunnable { WorkerPrivate* mFinishedWorker; - nsCOMPtr mThread; public: WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate, - WorkerPrivate* aFinishedWorker, - nsIThread* aFinishedThread) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), - mFinishedWorker(aFinishedWorker), mThread(aFinishedThread) + WorkerPrivate* aFinishedWorker) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mFinishedWorker(aFinishedWorker) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // Silence bad assertions. return true; } - void + virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) + bool aDispatchResult) MOZ_OVERRIDE { // Silence bad assertions. } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { - nsTArray > doomed; + nsTArray> doomed; mFinishedWorker->ForgetMainThreadObjects(doomed); nsTArray hostObjectURIs; mFinishedWorker->StealHostObjectURIs(hostObjectURIs); nsRefPtr runnable = - new MainThreadReleaseRunnable(mThread, doomed, hostObjectURIs); + new MainThreadReleaseRunnable(doomed, hostObjectURIs); if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { NS_WARNING("Failed to dispatch, going to leak!"); } @@ -720,21 +780,22 @@ public: } }; -class TopLevelWorkerFinishedRunnable : public nsRunnable +class TopLevelWorkerFinishedRunnable MOZ_FINAL : public nsRunnable { WorkerPrivate* mFinishedWorker; - nsCOMPtr mThread; public: - TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker, - nsIThread* aFinishedThread) - : mFinishedWorker(aFinishedWorker), mThread(aFinishedThread) + TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker) + : mFinishedWorker(aFinishedWorker) { aFinishedWorker->AssertIsOnWorkerThread(); } + NS_DECL_ISUPPORTS_INHERITED + +private: NS_IMETHOD - Run() + Run() MOZ_OVERRIDE { AssertIsOnMainThread(); @@ -760,34 +821,32 @@ public: NS_WARNING("Failed to dispatch, going to leak!"); } - if (mThread) { - runtime->NoteIdleThread(mThread); - } - mFinishedWorker->Release(); return NS_OK; } }; -class ModifyBusyCountRunnable : public WorkerControlRunnable +class ModifyBusyCountRunnable MOZ_FINAL : public WorkerControlRunnable { bool mIncrease; public: ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease) - : WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), mIncrease(aIncrease) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { return aWorkerPrivate->ModifyBusyCount(aCx, mIncrease); } - void + virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) + MOZ_OVERRIDE { if (mIncrease) { WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); @@ -798,16 +857,16 @@ public: } }; -class CompileScriptRunnable : public WorkerRunnable +class CompileScriptRunnable MOZ_FINAL : public WorkerRunnable { public: CompileScriptRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerRunnable(aWorkerPrivate, WorkerThread, ModifyBusyCount, - SkipWhenClearing) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { JS::Rooted global(aCx, aWorkerPrivate->CreateGlobalScope(aCx)); @@ -821,16 +880,29 @@ public: } }; -class CloseEventRunnable : public WorkerRunnable +class CloseEventRunnable MOZ_FINAL : public WorkerRunnable { public: CloseEventRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount, - SkipWhenClearing) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on CloseEventRunnable!"); + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE + { + MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on CloseEventRunnable!"); + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { JS::Rooted target(aCx, JS::CurrentGlobalOrNull(aCx)); NS_ASSERTION(target, "This must never be null!"); @@ -842,10 +914,16 @@ public: nsCOMPtr event; nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), globalScope, nullptr, nullptr); - NS_ENSURE_SUCCESS(rv, false); + if (NS_FAILED(rv)) { + Throw(aCx, rv); + return false; + } rv = event->InitEvent(NS_LITERAL_STRING("close"), false, false); - NS_ENSURE_SUCCESS(rv, false); + if (NS_FAILED(rv)) { + Throw(aCx, rv); + return false; + } event->SetTrusted(true); @@ -854,8 +932,9 @@ public: return true; } - void + virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) + MOZ_OVERRIDE { // Report errors. WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); @@ -869,7 +948,7 @@ public: } }; -class MessageEventRunnable : public WorkerRunnable +class MessageEventRunnable MOZ_FINAL : public WorkerRunnable { JSAutoStructuredCloneBuffer mBuffer; nsTArray > mClonedObjects; @@ -877,14 +956,12 @@ class MessageEventRunnable : public WorkerRunnable bool mToMessagePort; public: - MessageEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, + MessageEventRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior, JSAutoStructuredCloneBuffer& aData, nsTArray >& aClonedObjects, bool aToMessagePort, uint64_t aMessagePortSerial) - : WorkerRunnable(aWorkerPrivate, aTarget, aTarget == WorkerThread ? - ModifyBusyCount : - UnchangedBusyCount, - SkipWhenClearing), + : WorkerRunnable(aWorkerPrivate, aBehavior), mMessagePortSerial(aMessagePortSerial), mToMessagePort(aToMessagePort) { mBuffer.swap(aData); @@ -931,12 +1008,13 @@ public: return true; } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { MOZ_ASSERT_IF(mToMessagePort, aWorkerPrivate->IsSharedWorker()); - if (mTarget == ParentThread) { + if (mBehavior == ParentThreadUnchangedBusyCount) { // Don't fire this event if the JS object has been disconnected from the // private object. if (!aWorkerPrivate->IsAcceptingEvents()) { @@ -963,6 +1041,7 @@ public: } MOZ_ASSERT(aWorkerPrivate == GetWorkerPrivateFromContext(aCx)); + if (mToMessagePort) { nsRefPtr port = aWorkerPrivate->GetMessagePort(mMessagePortSerial); @@ -976,43 +1055,33 @@ public: return DispatchDOMEvent(aCx, aWorkerPrivate, aWorkerPrivate->GlobalScope(), false); } - - void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) - { - WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); - } }; -class NotifyRunnable : public WorkerControlRunnable +class NotifyRunnable MOZ_FINAL : public WorkerControlRunnable { Status mStatus; public: NotifyRunnable(WorkerPrivate* aWorkerPrivate, Status aStatus) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mStatus(aStatus) { - NS_ASSERTION(aStatus == Terminating || aStatus == Canceling || - aStatus == Killing, "Bad status!"); + MOZ_ASSERT(aStatus == Terminating || aStatus == Canceling || + aStatus == Killing); } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // Modify here, but not in PostRun! This busy count addition will be matched // by the CloseEventRunnable. return aWorkerPrivate->ModifyBusyCount(aCx, true); } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - return aWorkerPrivate->NotifyInternal(aCx, mStatus); - } - - void + virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) + bool aDispatchResult) MOZ_OVERRIDE { if (!aDispatchResult) { // We couldn't dispatch to the worker, which means it's already dead. @@ -1020,17 +1089,24 @@ public: aWorkerPrivate->ModifyBusyCount(aCx, false); } } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + return aWorkerPrivate->NotifyInternal(aCx, mStatus); + } }; class CloseRunnable MOZ_FINAL : public WorkerControlRunnable { public: CloseRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount) + : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // This busy count will be matched by the CloseEventRunnable. return aWorkerPrivate->ModifyBusyCount(aCx, true) && @@ -1038,35 +1114,37 @@ public: } }; -class SuspendRunnable : public WorkerControlRunnable +class SuspendRunnable MOZ_FINAL : public WorkerControlRunnable { public: SuspendRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { return aWorkerPrivate->SuspendInternal(aCx); } }; -class ResumeRunnable : public WorkerControlRunnable +class ResumeRunnable MOZ_FINAL : public WorkerControlRunnable { public: ResumeRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { return aWorkerPrivate->ResumeInternal(aCx); } }; -class ReportErrorRunnable : public WorkerRunnable +class ReportErrorRunnable MOZ_FINAL : public WorkerRunnable { nsString mMessage; nsString mFilename; @@ -1077,77 +1155,6 @@ class ReportErrorRunnable : public WorkerRunnable uint32_t mErrorNumber; public: - ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage, - const nsString& aFilename, const nsString& aLine, - uint32_t aLineNumber, uint32_t aColumnNumber, - uint32_t aFlags, uint32_t aErrorNumber) - : WorkerRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount, - SkipWhenClearing), - mMessage(aMessage), mFilename(aFilename), mLine(aLine), - mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags), - mErrorNumber(aErrorNumber) - { } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - aWorkerPrivate->AssertIsOnWorkerThread(); - - // Dispatch may fail if the worker was canceled, no need to report that as - // an error, so don't call base class PostDispatch. - } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - // Don't fire this event if the JS object has been disconnected from the - // private object. - if (!aWorkerPrivate->IsAcceptingEvents()) { - return true; - } - - JS::Rooted target(aCx, aWorkerPrivate->GetWrapper()); - - uint64_t innerWindowId; - bool fireAtScope = true; - - WorkerPrivate* parent = aWorkerPrivate->GetParent(); - if (parent) { - innerWindowId = 0; - } - else { - AssertIsOnMainThread(); - - if (aWorkerPrivate->IsSuspended()) { - aWorkerPrivate->QueueRunnable(this); - return true; - } - - if (aWorkerPrivate->IsSharedWorker()) { - aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename, - mLine, mLineNumber, - mColumnNumber, mFlags); - return true; - } - - aWorkerPrivate->AssertInnerWindowIsCorrect(); - - innerWindowId = aWorkerPrivate->GetInnerWindowId(); - } - - return ReportErrorRunnable::ReportError(aCx, parent, fireAtScope, - aWorkerPrivate, mMessage, - mFilename, mLine, mLineNumber, - mColumnNumber, mFlags, - mErrorNumber, innerWindowId); - } - - void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) - { - WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult); - } - // aWorkerPrivate is the worker thread we're on (or the main thread, if null) // aTarget is the worker object that we are going to fire an error at // (if any). @@ -1263,32 +1270,96 @@ public: aFlags, aInnerWindowId); return true; } + +private: + ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage, + const nsString& aFilename, const nsString& aLine, + uint32_t aLineNumber, uint32_t aColumnNumber, + uint32_t aFlags, uint32_t aErrorNumber) + : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), + mMessage(aMessage), mFilename(aFilename), mLine(aLine), + mLineNumber(aLineNumber), mColumnNumber(aColumnNumber), mFlags(aFlags), + mErrorNumber(aErrorNumber) + { } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE + { + aWorkerPrivate->AssertIsOnWorkerThread(); + + // Dispatch may fail if the worker was canceled, no need to report that as + // an error, so don't call base class PostDispatch. + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + // Don't fire this event if the JS object has been disconnected from the + // private object. + if (!aWorkerPrivate->IsAcceptingEvents()) { + return true; + } + + JS::Rooted target(aCx, aWorkerPrivate->GetWrapper()); + + uint64_t innerWindowId; + bool fireAtScope = true; + + WorkerPrivate* parent = aWorkerPrivate->GetParent(); + if (parent) { + innerWindowId = 0; + } + else { + AssertIsOnMainThread(); + + if (aWorkerPrivate->IsSuspended()) { + aWorkerPrivate->QueueRunnable(this); + return true; + } + + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->BroadcastErrorToSharedWorkers(aCx, mMessage, mFilename, + mLine, mLineNumber, + mColumnNumber, mFlags); + return true; + } + + aWorkerPrivate->AssertInnerWindowIsCorrect(); + + innerWindowId = aWorkerPrivate->GetInnerWindowId(); + } + + return ReportError(aCx, parent, fireAtScope, aWorkerPrivate, mMessage, + mFilename, mLine, mLineNumber, mColumnNumber, mFlags, + mErrorNumber, innerWindowId); + } }; -class TimerRunnable : public WorkerRunnable +class TimerRunnable MOZ_FINAL : public WorkerRunnable { public: TimerRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount, - SkipWhenClearing) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // Silence bad assertions. return true; } - void + virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) + bool aDispatchResult) MOZ_OVERRIDE { // Silence bad assertions. } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { return aWorkerPrivate->RunExpiredTimeouts(aCx); } @@ -1300,24 +1371,31 @@ DummyCallback(nsITimer* aTimer, void* aClosure) // Nothing! } -class WorkerRunnableEventTarget MOZ_FINAL : public nsIEventTarget +class TimerThreadEventTarget MOZ_FINAL : public nsIEventTarget { -protected: + WorkerPrivate* mWorkerPrivate; nsRefPtr mWorkerRunnable; public: - WorkerRunnableEventTarget(WorkerRunnable* aWorkerRunnable) - : mWorkerRunnable(aWorkerRunnable) - { } + TimerThreadEventTarget(WorkerPrivate* aWorkerPrivate, + WorkerRunnable* aWorkerRunnable) + : mWorkerPrivate(aWorkerPrivate), mWorkerRunnable(aWorkerRunnable) + { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWorkerRunnable); + } NS_DECL_THREADSAFE_ISUPPORTS +protected: NS_IMETHOD - Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) + Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE { - NS_ASSERTION(aFlags == nsIEventTarget::DISPATCH_NORMAL, "Don't call me!"); + // This should only happen on the timer thread. + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aFlags == nsIEventTarget::DISPATCH_NORMAL); - nsRefPtr kungFuDeathGrip = this; + nsRefPtr kungFuDeathGrip = this; // Run the runnable we're given now (should just call DummyCallback()), // otherwise the timer thread will leak it... If we run this after @@ -1332,42 +1410,47 @@ public: } NS_IMETHOD - IsOnCurrentThread(bool* aIsOnCurrentThread) + IsOnCurrentThread(bool* aIsOnCurrentThread) MOZ_OVERRIDE { - *aIsOnCurrentThread = false; + MOZ_ASSERT(aIsOnCurrentThread); + + nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; } }; -NS_IMPL_ISUPPORTS1(WorkerRunnableEventTarget, nsIEventTarget) - -class KillCloseEventRunnable : public WorkerRunnable +class KillCloseEventRunnable MOZ_FINAL : public WorkerRunnable { nsCOMPtr mTimer; - class KillScriptRunnable : public WorkerControlRunnable + class KillScriptRunnable MOZ_FINAL : public WorkerControlRunnable { public: KillScriptRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { - // Silence bad assertions. + // Silence bad assertions, this is dispatched from the timer thread. return true; } - void + virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) + bool aDispatchResult) MOZ_OVERRIDE { - // Silence bad assertions. + // Silence bad assertions, this is dispatched from the timer thread. } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // Kill running script. return false; @@ -1376,24 +1459,9 @@ class KillCloseEventRunnable : public WorkerRunnable public: KillCloseEventRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount, - SkipWhenClearing) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) { } - ~KillCloseEventRunnable() - { - if (mTimer) { - mTimer->Cancel(); - } - } - - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - NS_NOTREACHED("Not meant to be dispatched!"); - return false; - } - bool SetTimeout(JSContext* aCx, uint32_t aDelayMS) { @@ -1406,8 +1474,8 @@ public: nsRefPtr runnable = new KillScriptRunnable(mWorkerPrivate); - nsRefPtr target = - new WorkerRunnableEventTarget(runnable); + nsRefPtr target = + new TimerThreadEventTarget(mWorkerPrivate, runnable); if (NS_FAILED(timer->SetTarget(target))) { JS_ReportError(aCx, "Failed to set timer's target!"); @@ -1424,8 +1492,29 @@ public: return true; } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + ~KillCloseEventRunnable() + { + if (mTimer) { + mTimer->Cancel(); + } + } + + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on KillCloseEventRunnable!"); + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE + { + MOZ_ASSUME_UNREACHABLE("Don't call Dispatch() on KillCloseEventRunnable!"); + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { if (mTimer) { mTimer->Cancel(); @@ -1436,7 +1525,7 @@ public: } }; -class UpdateJSContextOptionsRunnable : public WorkerControlRunnable +class UpdateJSContextOptionsRunnable MOZ_FINAL : public WorkerControlRunnable { JS::ContextOptions mContentOptions; JS::ContextOptions mChromeOptions; @@ -1445,12 +1534,13 @@ public: UpdateJSContextOptionsRunnable(WorkerPrivate* aWorkerPrivate, const JS::ContextOptions& aContentOptions, const JS::ContextOptions& aChromeOptions) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mContentOptions(aContentOptions), mChromeOptions(aChromeOptions) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->UpdateJSContextOptionsInternal(aCx, mContentOptions, mChromeOptions); @@ -1458,7 +1548,7 @@ public: } }; -class UpdatePreferenceRunnable : public WorkerControlRunnable +class UpdatePreferenceRunnable MOZ_FINAL : public WorkerControlRunnable { WorkerPreference mPref; bool mValue; @@ -1467,21 +1557,21 @@ public: UpdatePreferenceRunnable(WorkerPrivate* aWorkerPrivate, WorkerPreference aPref, bool aValue) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mPref(aPref), mValue(aValue) - { - } + { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->UpdatePreferenceInternal(aCx, mPref, mValue); return true; } }; -class UpdateJSWorkerMemoryParameterRunnable : public WorkerControlRunnable +class UpdateJSWorkerMemoryParameterRunnable MOZ_FINAL : + public WorkerControlRunnable { uint32_t mValue; JSGCParamKey mKey; @@ -1490,12 +1580,13 @@ public: UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate, JSGCParamKey aKey, uint32_t aValue) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mValue(aValue), mKey(aKey) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue); return true; @@ -1503,7 +1594,7 @@ public: }; #ifdef JS_GC_ZEAL -class UpdateGCZealRunnable : public WorkerControlRunnable +class UpdateGCZealRunnable MOZ_FINAL : public WorkerControlRunnable { uint8_t mGCZeal; uint32_t mFrequency; @@ -1512,12 +1603,13 @@ public: UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal, uint32_t aFrequency) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mGCZeal(aGCZeal), mFrequency(aFrequency) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency); return true; @@ -1525,55 +1617,56 @@ public: }; #endif -class UpdateJITHardeningRunnable : public WorkerControlRunnable +class UpdateJITHardeningRunnable MOZ_FINAL : public WorkerControlRunnable { bool mJITHardening; public: UpdateJITHardeningRunnable(WorkerPrivate* aWorkerPrivate, bool aJITHardening) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mJITHardening(aJITHardening) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->UpdateJITHardeningInternal(aCx, mJITHardening); return true; } }; -class GarbageCollectRunnable : public WorkerControlRunnable +class GarbageCollectRunnable MOZ_FINAL : public WorkerControlRunnable { -protected: bool mShrinking; bool mCollectChildren; public: GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking, bool aCollectChildren) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mShrinking(aShrinking), mCollectChildren(aCollectChildren) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { // Silence bad assertions, this can be dispatched from either the main // thread or the timer thread.. return true; } - void + virtual void PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) + bool aDispatchResult) MOZ_OVERRIDE { // Silence bad assertions, this can be dispatched from either the main // thread or the timer thread.. } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren); return true; @@ -1582,31 +1675,14 @@ public: class CycleCollectRunnable : public WorkerControlRunnable { -protected: bool mCollectChildren; public: CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), mCollectChildren(aCollectChildren) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - // Silence bad assertions, this can be dispatched from either the main - // thread or the timer thread.. - return true; - } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - // Silence bad assertions, this can be dispatched from either the main - // thread or the timer thread.. - } - bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { @@ -1677,7 +1753,7 @@ public: } }; -class MessagePortRunnable : public WorkerRunnable +class MessagePortRunnable MOZ_FINAL : public WorkerRunnable { uint64_t mMessagePortSerial; bool mConnect; @@ -1686,14 +1762,18 @@ public: MessagePortRunnable(WorkerPrivate* aWorkerPrivate, uint64_t aMessagePortSerial, bool aConnect) - : WorkerRunnable(aWorkerPrivate, WorkerThread, - aConnect ? ModifyBusyCount : UnchangedBusyCount, - SkipWhenClearing), + : WorkerRunnable(aWorkerPrivate, aConnect ? + WorkerThreadModifyBusyCount : + WorkerThreadUnchangedBusyCount), mMessagePortSerial(aMessagePortSerial), mConnect(aConnect) { } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +private: + ~MessagePortRunnable() + { } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { if (mConnect) { return aWorkerPrivate->ConnectMessagePort(aCx, mMessagePortSerial); @@ -1704,224 +1784,41 @@ public: } }; +#ifdef DEBUG + +PRThread* +PRThreadFromThread(nsIThread* aThread) +{ + MOZ_ASSERT(aThread); + + PRThread* result; + MOZ_ASSERT(NS_SUCCEEDED(aThread->GetPRThread(&result))); + MOZ_ASSERT(result); + + return result; +} + +#endif // DEBUG + } /* anonymous namespace */ -#ifdef DEBUG -void -mozilla::dom::workers::AssertIsOnMainThread() -{ - MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); -} +NS_IMPL_ISUPPORTS_INHERITED0(MainThreadReleaseRunnable, nsRunnable) -WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, - BusyBehavior aBusyBehavior, - ClearingBehavior aClearingBehavior) -: mWorkerPrivate(aWorkerPrivate), mTarget(aTarget), - mBusyBehavior(aBusyBehavior), mClearingBehavior(aClearingBehavior) -{ - NS_ASSERTION(aWorkerPrivate, "Null worker private!"); -} -#endif +NS_IMPL_ISUPPORTS_INHERITED0(TopLevelWorkerFinishedRunnable, nsRunnable) -NS_IMPL_ISUPPORTS1(WorkerRunnable, nsIRunnable) - -bool -WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) -{ -#ifdef DEBUG - if (mBusyBehavior == ModifyBusyCount) { - NS_ASSERTION(mTarget == WorkerThread, - "Don't set this option unless targeting the worker thread!"); - } - if (mTarget == ParentThread) { - aWorkerPrivate->AssertIsOnWorkerThread(); - } - else { - aWorkerPrivate->AssertIsOnParentThread(); - } -#endif - - if (mBusyBehavior == ModifyBusyCount && aCx) { - return aWorkerPrivate->ModifyBusyCount(aCx, true); - } - - return true; -} - -bool -WorkerRunnable::Dispatch(JSContext* aCx) -{ - bool ok; - - if (!aCx) { - ok = PreDispatch(nullptr, mWorkerPrivate); - if (ok) { - ok = DispatchInternal(); - } - PostDispatch(nullptr, mWorkerPrivate, ok); - return ok; - } - - JSAutoRequest ar(aCx); - - JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); - - Maybe ac; - if (global) { - ac.construct(aCx, global); - } - - ok = PreDispatch(aCx, mWorkerPrivate); - - if (ok && !DispatchInternal()) { - ok = false; - } - - PostDispatch(aCx, mWorkerPrivate, ok); - - return ok; -} - -// static -bool -WorkerRunnable::DispatchToMainThread(nsIRunnable* aRunnable) -{ - nsCOMPtr mainThread = do_GetMainThread(); - NS_ASSERTION(mainThread, "This should never fail!"); - - return NS_SUCCEEDED(mainThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL)); -} - -// These DispatchInternal functions look identical but carry important type -// informaton so they can't be consolidated... - -#define IMPL_DISPATCH_INTERNAL(_class) \ - bool \ - _class ::DispatchInternal() \ - { \ - if (mTarget == WorkerThread) { \ - return mWorkerPrivate->Dispatch(this); \ - } \ - \ - if (mWorkerPrivate->GetParent()) { \ - return mWorkerPrivate->GetParent()->Dispatch(this); \ - } \ - \ - return DispatchToMainThread(this); \ - } - -IMPL_DISPATCH_INTERNAL(WorkerRunnable) -IMPL_DISPATCH_INTERNAL(WorkerSyncRunnable) -IMPL_DISPATCH_INTERNAL(WorkerControlRunnable) - -#undef IMPL_DISPATCH_INTERNAL - -void -WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) -{ -#ifdef DEBUG - if (mTarget == ParentThread) { - aWorkerPrivate->AssertIsOnWorkerThread(); - } - else { - aWorkerPrivate->AssertIsOnParentThread(); - } -#endif - - if (!aDispatchResult && aCx) { - if (mBusyBehavior == ModifyBusyCount) { - aWorkerPrivate->ModifyBusyCount(aCx, false); - } - JS_ReportPendingException(aCx); - } -} - -NS_IMETHODIMP -WorkerRunnable::Run() -{ - JSContext* cx; - nsRefPtr kungFuDeathGrip; - nsCxPusher pusher; - - if (mTarget == WorkerThread) { - mWorkerPrivate->AssertIsOnWorkerThread(); - cx = mWorkerPrivate->GetJSContext(); - } else { - kungFuDeathGrip = mWorkerPrivate; - mWorkerPrivate->AssertIsOnParentThread(); - cx = mWorkerPrivate->ParentJSContext(); - - if (!mWorkerPrivate->GetParent()) { - AssertIsOnMainThread(); - pusher.Push(cx); - } - } - - JS::Rooted targetCompartmentObject(cx); - - if (mTarget == WorkerThread) { - targetCompartmentObject = JS::CurrentGlobalOrNull(cx); - } else { - targetCompartmentObject = mWorkerPrivate->GetWrapper(); - } - - NS_ASSERTION(cx, "Must have a context!"); - - JSAutoRequest ar(cx); - - Maybe ac; - if (targetCompartmentObject) { - ac.construct(cx, targetCompartmentObject); - } - - bool result = WorkerRun(cx, mWorkerPrivate); - // In the case of CompileScriptRunnnable, WorkerRun above can cause us to - // lazily create a global, in which case we need to be in its compartment - // when calling PostRun() below. Maybe<> this time... - if (mTarget == WorkerThread && ac.empty() && - js::DefaultObjectForContextOrNull(cx)) { - ac.construct(cx, js::DefaultObjectForContextOrNull(cx)); - } - PostRun(cx, mWorkerPrivate, result); - return result ? NS_OK : NS_ERROR_FAILURE; -} - -void -WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aRunResult) -{ -#ifdef DEBUG - if (mTarget == ParentThread) { - mWorkerPrivate->AssertIsOnParentThread(); - } - else { - mWorkerPrivate->AssertIsOnWorkerThread(); - } -#endif - - if (mBusyBehavior == ModifyBusyCount) { - if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) { - aRunResult = false; - } - } - - if (!aRunResult) { - JS_ReportPendingException(aCx); - } -} +NS_IMPL_ISUPPORTS1(TimerThreadEventTarget, nsIEventTarget) template -class WorkerPrivateParent::SynchronizeAndResumeRunnable +class WorkerPrivateParent::SynchronizeAndResumeRunnable MOZ_FINAL : public nsRunnable { - friend class WorkerPrivateParent; friend class nsRevocableEventPtr; WorkerPrivate* mWorkerPrivate; nsCOMPtr mWindow; nsCOMPtr mScriptContext; +public: SynchronizeAndResumeRunnable(WorkerPrivate* aWorkerPrivate, nsPIDOMWindow* aWindow, nsIScriptContext* aScriptContext) @@ -1934,8 +1831,12 @@ class WorkerPrivateParent::SynchronizeAndResumeRunnable MOZ_ASSERT(!aWorkerPrivate->GetParent()); } +private: + ~SynchronizeAndResumeRunnable() + { } + NS_IMETHOD - Run() + Run() MOZ_OVERRIDE { AssertIsOnMainThread(); @@ -1965,6 +1866,62 @@ class WorkerPrivateParent::SynchronizeAndResumeRunnable } }; +template +class WorkerPrivateParent::EventTarget MOZ_FINAL + : public nsIEventTarget +{ + // This mutex protects mWorkerPrivate and must be acquired *before* the + // WorkerPrivate's mutex whenever they must both be held. + mozilla::Mutex mMutex; + WorkerPrivate* mWorkerPrivate; + nsIEventTarget* mWeakNestedEventTarget; + nsCOMPtr mNestedEventTarget; + +public: + EventTarget(WorkerPrivate* aWorkerPrivate) + : mMutex("WorkerPrivateParent::EventTarget::mMutex"), + mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(nullptr) + { + MOZ_ASSERT(aWorkerPrivate); + } + + EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget) + : mMutex("WorkerPrivateParent::EventTarget::mMutex"), + mWorkerPrivate(aWorkerPrivate), mWeakNestedEventTarget(aNestedEventTarget), + mNestedEventTarget(aNestedEventTarget) + { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aNestedEventTarget); + } + + void + Disable() + { + nsCOMPtr nestedEventTarget; + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate = nullptr; + mNestedEventTarget.swap(nestedEventTarget); + } + } + + nsIEventTarget* + GetWeakNestedEventTarget() const + { + MOZ_ASSERT(mWeakNestedEventTarget); + return mWeakNestedEventTarget; + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET + +private: + ~EventTarget() + { } +}; + struct WorkerPrivate::TimeoutInfo { TimeoutInfo() @@ -2118,6 +2075,21 @@ private: NS_IMPL_ISUPPORTS1(WorkerPrivate::MemoryReporter, nsIMemoryReporter) +WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget) +: mEventTarget(aEventTarget), mCompleted(false), mResult(false) +#ifdef DEBUG + , mHasRun(false) +#endif +{ +} + +// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the +// templates. +template +typename WorkerPrivateParent::cycleCollection + WorkerPrivateParent::_cycleCollectorGlobal = + WorkerPrivateParent::cycleCollection(); + template WorkerPrivateParent::WorkerPrivateParent( JSContext* aCx, @@ -2171,38 +2143,6 @@ WorkerPrivateParent::~WorkerPrivateParent() DropJSObjects(this); } -template -NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent, nsDOMEventTargetHelper) - -template -NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent, nsDOMEventTargetHelper) - -template -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent) - // No new interfaces, just cycle collection. -NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) - -template -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent, - nsDOMEventTargetHelper) - // Nothing else to traverse - // The various strong references in LoadInfo are managed manually and cannot - // be cycle collected. - tmp->AssertIsOnParentThread(); -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -template -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent, - nsDOMEventTargetHelper) - tmp->AssertIsOnParentThread(); -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -template -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent, - nsDOMEventTargetHelper) - tmp->AssertIsOnParentThread(); -NS_IMPL_CYCLE_COLLECTION_TRACE_END - template JSObject* WorkerPrivateParent::WrapObject(JSContext* aCx, @@ -2213,8 +2153,7 @@ WorkerPrivateParent::WrapObject(JSContext* aCx, AssertIsOnParentThread(); - JS::Rooted obj(aCx, WorkerBinding::Wrap(aCx, aScope, - ParentAsWorkerPrivate())); + JSObject* obj = WorkerBinding::Wrap(aCx, aScope, ParentAsWorkerPrivate()); if (mRooted) { PreserveWrapper(this); @@ -2223,6 +2162,148 @@ WorkerPrivateParent::WrapObject(JSContext* aCx, return obj; } +template +nsresult +WorkerPrivateParent::DispatchPrivate(WorkerRunnable* aRunnable, + nsIEventTarget* aSyncLoopTarget) +{ + // May be called on any thread! + + WorkerPrivate* self = ParentAsWorkerPrivate(); + + { + MutexAutoLock lock(mMutex); + + MOZ_ASSERT_IF(aSyncLoopTarget, self->mThread); + + if (!self->mThread) { + if (ParentStatus() == Pending || self->mStatus == Pending) { + mPreStartRunnables.AppendElement(aRunnable); + return NS_OK; + } + + NS_WARNING("Using a worker event target after the thread has already" + "been released!"); + return NS_ERROR_UNEXPECTED; + } + + if (self->mStatus == Dead || + (!aSyncLoopTarget && ParentStatus() > Running)) { + NS_WARNING("A runnable was posted to a worker that is already shutting " + "down!"); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr target; + if (aSyncLoopTarget) { + target = aSyncLoopTarget; + } + else { + target = self->mThread; + } + + nsresult rv = target->Dispatch(aRunnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCondVar.Notify(); + } + + return NS_OK; +} + +template +nsresult +WorkerPrivateParent::DispatchControlRunnable( + WorkerControlRunnable* aWorkerControlRunnable) +{ + // May be called on any thread! + + MOZ_ASSERT(aWorkerControlRunnable); + + nsRefPtr runnable = aWorkerControlRunnable; + + WorkerPrivate* self = ParentAsWorkerPrivate(); + + { + MutexAutoLock lock(mMutex); + + if (self->mStatus == Dead) { + NS_WARNING("A control runnable was posted to a worker that is already " + "shutting down!"); + return NS_ERROR_UNEXPECTED; + } + + // Transfer ownership to the control queue. + self->mControlQueue.Push(runnable.forget().get()); + + if (JSContext* cx = self->mJSContext) { + MOZ_ASSERT(self->mThread); + + JSRuntime* rt = JS_GetRuntime(cx); + MOZ_ASSERT(rt); + + JS_TriggerOperationCallback(rt); + } + + mCondVar.Notify(); + } + + return NS_OK; +} + +template +already_AddRefed +WorkerPrivateParent::MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable) +{ + // May be called on any thread! + + MOZ_ASSERT(aRunnable); + + nsRefPtr workerRunnable = + WorkerRunnable::FromRunnable(aRunnable); + if (workerRunnable) { + return workerRunnable.forget(); + } + + nsCOMPtr cancelable = do_QueryInterface(aRunnable); + if (!cancelable) { + MOZ_CRASH("All runnables destined for a worker thread must be cancelable!"); + } + + workerRunnable = + new ExternalRunnableWrapper(ParentAsWorkerPrivate(), cancelable); + return workerRunnable.forget(); +} + +template +already_AddRefed +WorkerPrivateParent::GetEventTarget() +{ + WorkerPrivate* self = ParentAsWorkerPrivate(); + + nsCOMPtr target; + + { + MutexAutoLock lock(mMutex); + + if (!mEventTarget && + ParentStatus() <= Running && + self->mStatus <= Running) { + mEventTarget = new EventTarget(self); + } + + target = mEventTarget; + } + + NS_WARN_IF_FALSE(target, + "Requested event target for a worker that is already " + "shutting down!"); + + return target.forget(); +} + template bool WorkerPrivateParent::Start() @@ -2270,17 +2351,22 @@ WorkerPrivateParent::NotifyPrivate(JSContext* aCx, Status aStatus) if (pending) { WorkerPrivate* self = ParentAsWorkerPrivate(); + #ifdef DEBUG { - // Silence useless assertions in debug builds. + // Fake a thread here just so that our assertions don't go off for no + // reason. nsIThread* currentThread = NS_GetCurrentThread(); - NS_ASSERTION(currentThread, "This should never be null!"); + MOZ_ASSERT(currentThread); - self->SetThread(currentThread); + MOZ_ASSERT(!self->mPRThread); + self->mPRThread = PRThreadFromThread(currentThread); + MOZ_ASSERT(self->mPRThread); } #endif + // Worker never got a chance to run, go ahead and delete it. - self->ScheduleDeletion(true); + self->ScheduleDeletion(); return true; } @@ -2469,12 +2555,11 @@ WorkerPrivateParent::Resume(JSContext* aCx, nsPIDOMWindow* aWindow) AssertIsOnMainThread(); MOZ_ASSERT(IsDedicatedWorker()); - nsTArray > runnables; + nsTArray> runnables; mQueuedRunnables.SwapElements(runnables); for (uint32_t index = 0; index < runnables.Length(); index++) { - nsRefPtr& runnable = runnables[index]; - runnable->Run(); + runnables[index]->Run(); } } @@ -2698,8 +2783,9 @@ WorkerPrivateParent::PostMessageInternal( nsRefPtr runnable = new MessageEventRunnable(ParentAsWorkerPrivate(), - WorkerRunnable::WorkerThread, buffer, - clonedObjects, aToMessagePort, aMessagePortSerial); + WorkerRunnable::WorkerThreadModifyBusyCount, + buffer, clonedObjects, aToMessagePort, + aMessagePortSerial); if (!runnable->Dispatch(aCx)) { aRv.Throw(NS_ERROR_FAILURE); } @@ -3344,7 +3430,7 @@ WorkerPrivateParent::ParentJSContext() const if (mParent) { return mParent->GetJSContext(); - } + } AssertIsOnMainThread(); @@ -3353,6 +3439,93 @@ WorkerPrivateParent::ParentJSContext() const nsContentUtils::GetSafeJSContext(); } +template +void +WorkerPrivateParent::RegisterHostObjectURI(const nsACString& aURI) +{ + AssertIsOnMainThread(); + mHostObjectURIs.AppendElement(aURI); +} + +template +void +WorkerPrivateParent::UnregisterHostObjectURI(const nsACString& aURI) +{ + AssertIsOnMainThread(); + mHostObjectURIs.RemoveElement(aURI); +} + +template +void +WorkerPrivateParent::StealHostObjectURIs(nsTArray& aArray) +{ + aArray.SwapElements(mHostObjectURIs); +} + +template +NS_IMPL_ADDREF_INHERITED(WorkerPrivateParent, nsDOMEventTargetHelper) + +template +NS_IMPL_RELEASE_INHERITED(WorkerPrivateParent, nsDOMEventTargetHelper) + +template +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WorkerPrivateParent) +NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) + +template +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerPrivateParent, + nsDOMEventTargetHelper) + // Nothing else to traverse + // The various strong references in LoadInfo are managed manually and cannot + // be cycle collected. + tmp->AssertIsOnParentThread(); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +template +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerPrivateParent, + nsDOMEventTargetHelper) + tmp->AssertIsOnParentThread(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +template +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WorkerPrivateParent, + nsDOMEventTargetHelper) + tmp->AssertIsOnParentThread(); +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +#ifdef DEBUG + +template +void +WorkerPrivateParent::AssertIsOnParentThread() const +{ + if (GetParent()) { + GetParent()->AssertIsOnWorkerThread(); + } + else { + AssertIsOnMainThread(); + } +} + +template +void +WorkerPrivateParent::AssertInnerWindowIsCorrect() const +{ + AssertIsOnParentThread(); + + // Only care about top level workers from windows. + if (mParent || !mLoadInfo.mWindow) { + return; + } + + AssertIsOnMainThread(); + + nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow(); + NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow, + "Inner window no longer correct!"); +} + +#endif WorkerPrivate::WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, @@ -3366,7 +3539,11 @@ WorkerPrivate::WorkerPrivate(JSContext* aCx, mStatus(Pending), mSuspended(false), mTimerRunning(false), mRunningExpiredTimeouts(false), mCloseHandlerStarted(false), mCloseHandlerFinished(false), mMemoryReporterRunning(false), - mBlockedForMemoryReporter(false) + mBlockedForMemoryReporter(false), mCancelAllPendingRunnables(false), + mPeriodicGCTimerRunning(false) +#ifdef DEBUG + , mPRThread(nullptr) +#endif { MOZ_ASSERT_IF(IsSharedWorker(), !aSharedWorkerName.IsVoid()); MOZ_ASSERT_IF(!IsSharedWorker(), aSharedWorkerName.IsEmpty()); @@ -3456,7 +3633,7 @@ WorkerPrivate::Constructor(const GlobalObject& aGlobal, MOZ_ASSERT_IF(aWorkerType != WorkerTypeShared, aSharedWorkerName.IsEmpty()); - mozilla::Maybe stackLoadInfo; + Maybe stackLoadInfo; if (!aLoadInfo) { stackLoadInfo.construct(); @@ -3494,13 +3671,13 @@ WorkerPrivate::Constructor(const GlobalObject& aGlobal, new WorkerPrivate(cx, parent, aScriptURL, aIsChromeWorker, aWorkerType, aSharedWorkerName, *aLoadInfo); - nsRefPtr compiler = new CompileScriptRunnable(worker); - if (!compiler->Dispatch(cx)) { + if (!runtimeService->RegisterWorker(cx, worker)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } - if (!runtimeService->RegisterWorker(cx, worker)) { + nsRefPtr compiler = new CompileScriptRunnable(worker); + if (!compiler->Dispatch(cx)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } @@ -3554,7 +3731,7 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, { MutexAutoLock lock(aParent->mMutex); parentStatus = aParent->mStatus; - } + } if (parentStatus > Running) { nsCOMPtr mainThread; @@ -3591,9 +3768,9 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, // See if we're being called from a window. nsCOMPtr globalWindow = aWindow; if (!globalWindow) { - nsCOMPtr scriptGlobal = - nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx)); - if (scriptGlobal) { + nsCOMPtr scriptGlobal = + nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx)); + if (scriptGlobal) { globalWindow = do_QueryInterface(scriptGlobal); MOZ_ASSERT(globalWindow); } @@ -3654,13 +3831,13 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, // No unsandboxed ancestor, use our GUID. rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain); NS_ENSURE_SUCCESS(rv, rv); - } + } } else { // Document creating the worker is not sandboxed. rv = loadInfo.mPrincipal->GetBaseDomain(loadInfo.mDomain); NS_ENSURE_SUCCESS(rv, rv); - } } + } nsCOMPtr permMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); @@ -3744,7 +3921,7 @@ WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindow* aWindow, rv = NS_GetFinalChannelURI(loadInfo.mChannel, getter_AddRefs(loadInfo.mResolvedScriptURI)); NS_ENSURE_SUCCESS(rv, rv); - } + } aLoadInfo->StealFrom(loadInfo); return NS_OK; @@ -3754,145 +3931,50 @@ void WorkerPrivate::DoRunLoop(JSContext* aCx) { AssertIsOnWorkerThread(); + MOZ_ASSERT(mThread); { MutexAutoLock lock(mMutex); mJSContext = aCx; - NS_ASSERTION(mStatus == Pending, "Huh?!"); + MOZ_ASSERT(mStatus == Pending); mStatus = Running; } - // We need a timer for GC. The basic plan is to run a normal (non-shrinking) - // GC periodically (NORMAL_GC_TIMER_DELAY_MS) while the worker is running. - // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_MS) timer to - // run a shrinking GC. If the worker receives more messages then the short - // timer is canceled and the periodic timer resumes. - nsCOMPtr gcTimer = do_CreateInstance(NS_TIMER_CONTRACTID); - if (!gcTimer) { - JS_ReportError(aCx, "Failed to create GC timer!"); - return; - } - - bool normalGCTimerRunning = false; - - // We need to swap event targets below to get different types of GC behavior. - nsCOMPtr normalGCEventTarget; - nsCOMPtr idleGCEventTarget; - - // We also need to track the idle GC event so that we don't confuse it with a - // generic event that should re-trigger the idle GC timer. - nsCOMPtr idleGCEvent; - { - nsRefPtr runnable = - new GarbageCollectRunnable(this, false, false); - normalGCEventTarget = new WorkerRunnableEventTarget(runnable); - - runnable = new GarbageCollectRunnable(this, true, false); - idleGCEventTarget = new WorkerRunnableEventTarget(runnable); - - idleGCEvent = runnable; - } - EnableMemoryReporter(); - Maybe maybeAC; - for (;;) { - Status currentStatus; - bool scheduleIdleGC; + InitializeGCTimers(); + + Maybe workerCompartment; + + for (;;) { + // Workers lazily create a global object in CompileScriptRunnable. We need + // to enter the global's compartment as soon as it has been created. + if (workerCompartment.empty()) { + if (JSObject* global = js::DefaultObjectForContextOrNull(aCx)) { + workerCompartment.construct(aCx, global); + } + } + + Status currentStatus; + bool normalRunnablesPending = false; - WorkerRunnable* event; { MutexAutoLock lock(mMutex); - while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) { + while (mControlQueue.IsEmpty() && + !(normalRunnablesPending = NS_HasPendingEvents(mThread))) { WaitForWorkerEvents(); } - bool eventIsNotIdleGCEvent; - currentStatus = mStatus; - - { - MutexAutoUnlock unlock(mMutex); - - // Workers lazily create a global object in CompileScriptRunnable. In - // the old world, creating the global would implicitly set it as the - // default compartment object on mJSContext, meaning that subsequent - // runnables would be able to operate in that compartment without - // explicitly entering it. That no longer works, so we mimic the - // "operate in the compartment of the worker global once it exists" - // behavior here. This could probably be improved with some refactoring. - if (maybeAC.empty() && js::DefaultObjectForContextOrNull(aCx)) { - maybeAC.construct(aCx, js::DefaultObjectForContextOrNull(aCx)); - } - - if (!normalGCTimerRunning && - event != idleGCEvent && - currentStatus <= Terminating) { - // Must always cancel before changing the timer's target. - if (NS_FAILED(gcTimer->Cancel())) { - NS_WARNING("Failed to cancel GC timer!"); - } - - if (NS_SUCCEEDED(gcTimer->SetTarget(normalGCEventTarget)) && - NS_SUCCEEDED(gcTimer->InitWithFuncCallback( - DummyCallback, nullptr, - NORMAL_GC_TIMER_DELAY_MS, - nsITimer::TYPE_REPEATING_SLACK))) { - normalGCTimerRunning = true; - } - else { - JS_ReportError(aCx, "Failed to start normal GC timer!"); - } - } - - // Keep track of whether or not this is the idle GC event. - eventIsNotIdleGCEvent = event != idleGCEvent; - - static_cast(event)->Run(); - NS_RELEASE(event); - } + ProcessAllControlRunnablesLocked(); currentStatus = mStatus; - scheduleIdleGC = mControlQueue.IsEmpty() && - mQueue.IsEmpty() && - eventIsNotIdleGCEvent && - JS::CurrentGlobalOrNull(aCx); - } - - // Take care of the GC timer. If we're starting the close sequence then we - // kill the timer once and for all. Otherwise we schedule the idle timeout - // if there are no more events. - if (currentStatus > Terminating || scheduleIdleGC) { - if (NS_SUCCEEDED(gcTimer->Cancel())) { - normalGCTimerRunning = false; - } - else { - NS_WARNING("Failed to cancel GC timer!"); - } - } - - if (scheduleIdleGC) { - NS_ASSERTION(JS::CurrentGlobalOrNull(aCx), "Should have global here!"); - - // Now *might* be a good time to GC. Let the JS engine make the decision. - JSAutoCompartment ac(aCx, JS::CurrentGlobalOrNull(aCx)); - JS_MaybeGC(aCx); - - if (NS_SUCCEEDED(gcTimer->SetTarget(idleGCEventTarget)) && - NS_SUCCEEDED(gcTimer->InitWithFuncCallback( - DummyCallback, nullptr, - IDLE_GC_TIMER_DELAY_MS, - nsITimer::TYPE_ONE_SHOT))) { - } - else { - JS_ReportError(aCx, "Failed to start idle GC timer!"); - } } + // If the close handler has finished and all features are done then we can + // kill this thread. if (currentStatus != Running && !HasActiveFeatures()) { - // If the close handler has finished and all features are done then we can - // kill this thread. if (mCloseHandlerFinished && currentStatus != Killing) { if (!NotifyInternal(aCx, Killing)) { JS_ReportPendingException(aCx); @@ -3902,7 +3984,7 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) MutexAutoLock lock(mMutex); currentStatus = mStatus; } - NS_ASSERTION(currentStatus == Killing, "Should have changed status!"); + MOZ_ASSERT(currentStatus == Killing); #else currentStatus = Killing; #endif @@ -3910,14 +3992,26 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) // If we're supposed to die then we should exit the loop. if (currentStatus == Killing) { - // Always make sure the timer is canceled. - if (NS_FAILED(gcTimer->Cancel())) { - NS_WARNING("Failed to cancel the GC timer!"); - } + ShutdownGCTimers(); DisableMemoryReporter(); - StopAcceptingEvents(); + { + MutexAutoLock lock(mMutex); + + mStatus = Dead; + mJSContext = nullptr; + } + + // After mStatus is set to Dead there can be no more + // WorkerControlRunnables so no need to lock here. + if (!mControlQueue.IsEmpty()) { + WorkerControlRunnable* runnable; + while (mControlQueue.Pop(runnable)) { + runnable->Cancel(); + runnable->Release(); + } + } // Clear away our MessagePorts. mWorkerPorts.Clear(); @@ -3928,9 +4022,155 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) return; } } + + // Nothing to do here if we don't have any runnables in the main queue. + if (!normalRunnablesPending) { + SetGCTimerMode(IdleTimer); + continue; + } + + MOZ_ASSERT(NS_HasPendingEvents(mThread)); + + // Start the periodic GC timer if it is not already running. + SetGCTimerMode(PeriodicTimer); + + // Process a single runnable from the main queue. + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false)); + + if (NS_HasPendingEvents(mThread)) { + // Now *might* be a good time to GC. Let the JS engine make the decision. + if (!workerCompartment.empty()) { + JS_MaybeGC(aCx); + } + } + else { + // The normal event queue has been exhausted, cancel the periodic GC timer + // and schedule the idle GC timer. + SetGCTimerMode(IdleTimer); + } } - NS_NOTREACHED("Shouldn't get here!"); + MOZ_ASSUME_UNREACHABLE("Shouldn't get here!"); +} + +void +WorkerPrivate::OnProcessNextEvent(uint32_t aRecursionDepth) +{ + AssertIsOnWorkerThread(); + MOZ_ASSERT(aRecursionDepth); + + // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop. + // However, it's possible that non-worker C++ could spin its own nested event + // loop, and in that case we must ensure that we continue to process control + // runnables here. + if (aRecursionDepth > 1 && + mSyncLoopStack.Length() < aRecursionDepth - 1) { + ProcessAllControlRunnables(); + } +} + +void +WorkerPrivate::AfterProcessNextEvent(uint32_t aRecursionDepth) +{ + AssertIsOnWorkerThread(); + MOZ_ASSERT(aRecursionDepth); +} + +void +WorkerPrivate::InitializeGCTimers() +{ + AssertIsOnWorkerThread(); + + // We need a timer for GC. The basic plan is to run a non-shrinking GC + // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running. + // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to + // run a shrinking GC. If the worker receives more messages then the short + // timer is canceled and the periodic timer resumes. + mGCTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + MOZ_ASSERT(mGCTimer); + + nsRefPtr runnable = + new GarbageCollectRunnable(this, false, false); + mPeriodicGCTimerTarget = new TimerThreadEventTarget(this, runnable); + + runnable = new GarbageCollectRunnable(this, true, false); + mIdleGCTimerTarget = new TimerThreadEventTarget(this, runnable); + + mPeriodicGCTimerRunning = false; +} + +void +WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) +{ + AssertIsOnWorkerThread(); + MOZ_ASSERT(mGCTimer); + MOZ_ASSERT(mPeriodicGCTimerTarget); + MOZ_ASSERT(mIdleGCTimerTarget); + + if (aMode == PeriodicTimer && mPeriodicGCTimerRunning) { + return; + } + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->Cancel())); + + mPeriodicGCTimerRunning = false; + + LOG(("Worker %p canceled GC timer because %s\n", this, + aMode == PeriodicTimer ? + "periodic" : + aMode == IdleTimer ? "idle" : "none")); + + if (aMode == NoTimer) { + return; + } + + MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer); + + nsIEventTarget* target; + uint32_t delay; + int16_t type; + + if (aMode == PeriodicTimer) { + target = mPeriodicGCTimerTarget; + delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000; + type = nsITimer::TYPE_REPEATING_SLACK; + } + else { + target = mIdleGCTimerTarget; + delay = IDLE_GC_TIMER_DELAY_SEC * 1000; + type = nsITimer::TYPE_ONE_SHOT; + } + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->SetTarget(target))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->InitWithFuncCallback(DummyCallback, + nullptr, delay, + type))); + + if (aMode == PeriodicTimer) { + LOG(("Worker %p scheduled periodic GC timer\n", this)); + mPeriodicGCTimerRunning = true; + } + else { + LOG(("Worker %p scheduled idle GC timer\n", this)); + } +} + +void +WorkerPrivate::ShutdownGCTimers() +{ + AssertIsOnWorkerThread(); + + MOZ_ASSERT(mGCTimer); + + // Always make sure the timer is canceled. + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->Cancel())); + + LOG(("Worker %p killed the GC timer\n", this)); + + mGCTimer = nullptr; + mPeriodicGCTimerTarget = nullptr; + mIdleGCTimerTarget = nullptr; + mPeriodicGCTimerRunning = false; } bool @@ -3939,6 +4179,7 @@ WorkerPrivate::OperationCallback(JSContext* aCx) AssertIsOnWorkerThread(); bool mayContinue = true; + bool scheduledIdleGC = false; for (;;) { // Run all control events now. @@ -3954,8 +4195,12 @@ WorkerPrivate::OperationCallback(JSContext* aCx) break; } - // Clean up before suspending. - JS_GC(JS_GetRuntime(aCx)); + // Cancel the periodic GC timer here before suspending. The idle GC timer + // will clean everything up once it runs. + if (!scheduledIdleGC) { + SetGCTimerMode(IdleTimer); + scheduledIdleGC = true; + } while ((mayContinue = MayContinueRunning())) { MutexAutoLock lock(mMutex); @@ -3974,39 +4219,58 @@ WorkerPrivate::OperationCallback(JSContext* aCx) return false; } + // Make sure the periodic timer gets turned back on here. + SetGCTimerMode(PeriodicTimer); + return true; } +nsresult +WorkerPrivate::IsOnCurrentThread(bool* aIsOnCurrentThread) +{ + // May be called on any thread! + + MOZ_ASSERT(aIsOnCurrentThread); + + nsCOMPtr thread; + { + MutexAutoLock lock(mMutex); + thread = mThread; + } + + if (!thread) { + NS_WARNING("Trying to test thread correctness after the worker has " + "released its thread!"); + return NS_ERROR_FAILURE; + } + + nsresult rv = thread->IsOnCurrentThread(aIsOnCurrentThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + void -WorkerPrivate::ScheduleDeletion(bool aWasPending) +WorkerPrivate::ScheduleDeletion() { AssertIsOnWorkerThread(); - NS_ASSERTION(mChildWorkers.IsEmpty(), "Live child workers!"); - NS_ASSERTION(mSyncQueues.IsEmpty(), "Should have no sync queues here!"); + MOZ_ASSERT(mChildWorkers.IsEmpty()); + MOZ_ASSERT(mSyncLoopStack.IsEmpty()); - StopAcceptingEvents(); + ClearMainEventQueue(); - nsIThread* currentThread; - if (aWasPending) { - // Don't want to close down this thread since we never got to run! - currentThread = nullptr; - } - else { - currentThread = NS_GetCurrentThread(); - NS_ASSERTION(currentThread, "This should never be null!"); - } - - WorkerPrivate* parent = GetParent(); - if (parent) { + if (WorkerPrivate* parent = GetParent()) { nsRefPtr runnable = - new WorkerFinishedRunnable(parent, this, currentThread); + new WorkerFinishedRunnable(parent, this); if (!runnable->Dispatch(nullptr)) { NS_WARNING("Failed to dispatch runnable!"); } } else { nsRefPtr runnable = - new TopLevelWorkerFinishedRunnable(this, currentThread); + new TopLevelWorkerFinishedRunnable(this); if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { NS_WARNING("Failed to dispatch runnable!"); } @@ -4066,6 +4330,7 @@ void WorkerPrivate::EnableMemoryReporter() { AssertIsOnWorkerThread(); + MOZ_ASSERT(!mMemoryReporter); // No need to lock here since the main thread can't race until we've // successfully registered the reporter. @@ -4076,8 +4341,6 @@ WorkerPrivate::EnableMemoryReporter() // No need to lock here since a failed registration means our memory // reporter can't start running. Just clean up. mMemoryReporter = nullptr; - - return; } } @@ -4163,136 +4426,71 @@ WorkerPrivate::WaitForWorkerEvents(PRIntervalTime aInterval) } bool -WorkerPrivate::ProcessAllControlRunnables() +WorkerPrivate::ProcessAllControlRunnablesLocked() { AssertIsOnWorkerThread(); + mMutex.AssertCurrentThreadOwns(); bool result = true; for (;;) { - WorkerRunnable* event; - { - MutexAutoLock lock(mMutex); + // Block here if the memory reporter is trying to run. + if (mMemoryReporterRunning) { + MOZ_ASSERT(!mBlockedForMemoryReporter); - // Block here if the memory reporter is trying to run. - if (mMemoryReporterRunning) { - NS_ASSERTION(!mBlockedForMemoryReporter, - "Can't be blocked in more than one place at the same " - "time!"); + // Let the main thread know that we've received the block request and + // that memory reporting may proceed. + mBlockedForMemoryReporter = true; - // Let the main thread know that we've received the block request and - // that memory reporting may proceed. - mBlockedForMemoryReporter = true; + // The main thread is almost certainly waiting so we must notify here. + mMemoryReportCondVar.Notify(); - // The main thread is almost certainly waiting so we must notify here. - mMemoryReportCondVar.Notify(); - - // Wait for the memory report to finish. - while (mMemoryReporterRunning) { - mMemoryReportCondVar.Wait(); - } - - NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!"); - - // No need to notify here as the main thread isn't watching for this - // state. - mBlockedForMemoryReporter = false; + // Wait for the memory report to finish. + while (mMemoryReporterRunning) { + mMemoryReportCondVar.Wait(); } - if (!mControlQueue.Pop(event)) { - break; - } + MOZ_ASSERT(mBlockedForMemoryReporter); + + // No need to notify here as the main thread isn't watching for this + // state. + mBlockedForMemoryReporter = false; } + WorkerControlRunnable* event; + if (!mControlQueue.Pop(event)) { + break; + } + + MutexAutoUnlock unlock(mMutex); + + MOZ_ASSERT(event); if (NS_FAILED(static_cast(event)->Run())) { result = false; } - NS_RELEASE(event); + event->Release(); } return result; } -bool -WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue) -{ - nsRefPtr event(aEvent); - - { - MutexAutoLock lock(mMutex); - - if (mStatus == Dead) { - // Nothing may be added after we've set Dead. - return false; - } - - if (aQueue == &mQueue) { - // Check parent status. - Status parentStatus = ParentStatus(); - if (parentStatus >= Terminating) { - // Throw. - return false; - } - - // Check inner status too. - if (parentStatus >= Closing || mStatus >= Closing) { - // Silently eat this one. - return true; - } - } - - if (!aQueue->Push(event)) { - return false; - } - - if (aQueue == &mControlQueue && mJSContext) { - JS_TriggerOperationCallback(JS_GetRuntime(mJSContext)); - } - - mCondVar.Notify(); - } - - event.forget(); - return true; -} - -bool -WorkerPrivate::DispatchToSyncQueue(WorkerSyncRunnable* aEvent) -{ - nsRefPtr event(aEvent); - - { - MutexAutoLock lock(mMutex); - - NS_ASSERTION(mSyncQueues.Length() > aEvent->mSyncQueueKey, "Bad event!"); - - if (!mSyncQueues[aEvent->mSyncQueueKey]->mQueue.Push(event)) { - return false; - } - - mCondVar.Notify(); - } - - event.forget(); - return true; -} - void -WorkerPrivate::ClearQueue(EventQueue* aQueue) +WorkerPrivate::ClearMainEventQueue() { AssertIsOnWorkerThread(); - mMutex.AssertCurrentThreadOwns(); - WorkerRunnable* event; - while (aQueue->Pop(event)) { - if (event->WantsToRunDuringClear()) { - MutexAutoUnlock unlock(mMutex); + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); - static_cast(event)->Run(); - } - event->Release(); - } + MOZ_ASSERT(!mCancelAllPendingRunnables); + mCancelAllPendingRunnables = true; + + NS_ProcessPendingEvents(currentThread); + MOZ_ASSERT(!NS_HasPendingEvents(currentThread)); + + MOZ_ASSERT(mCancelAllPendingRunnables); + mCancelAllPendingRunnables = false; } uint32_t @@ -4377,8 +4575,8 @@ WorkerPrivate::AddChildWorker(JSContext* aCx, ParentType* aChildWorker) { Status currentStatus; { - MutexAutoLock lock(mMutex); - currentStatus = mStatus; + MutexAutoLock lock(mMutex); + currentStatus = mStatus; } MOZ_ASSERT(currentStatus == Running); @@ -4504,92 +4702,190 @@ WorkerPrivate::CancelAllTimeouts(JSContext* aCx) mTimer = nullptr; } -uint32_t +already_AddRefed WorkerPrivate::CreateNewSyncLoop() { AssertIsOnWorkerThread(); - NS_ASSERTION(mSyncQueues.Length() < UINT32_MAX, - "Should have bailed by now!"); + nsCOMPtr thread = do_QueryInterface(NS_GetCurrentThread()); + MOZ_ASSERT(thread); - mSyncQueues.AppendElement(new SyncQueue()); - return mSyncQueues.Length() - 1; + nsCOMPtr realEventTarget; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->PushEventQueue( + getter_AddRefs(realEventTarget)))); + + nsRefPtr workerEventTarget = + new EventTarget(this, realEventTarget); + + { + // Modifications must be protected by mMutex in DEBUG builds, see comment + // about mSyncLoopStack in WorkerPrivate.h. +#ifdef DEBUG + MutexAutoLock lock(mMutex); +#endif + + mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget)); + } + + return workerEventTarget.forget(); } bool -WorkerPrivate::RunSyncLoop(JSContext* aCx, uint32_t aSyncLoopKey) +WorkerPrivate::RunCurrentSyncLoop() { AssertIsOnWorkerThread(); - NS_ASSERTION(!mSyncQueues.IsEmpty() || - (aSyncLoopKey != mSyncQueues.Length() - 1), - "Forgot to call CreateNewSyncLoop!"); - if (aSyncLoopKey != mSyncQueues.Length() - 1) { - return false; - } + JSContext* cx = GetJSContext(); + MOZ_ASSERT(cx); - SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get(); + // This should not change between now and the time we finish running this sync + // loop. + uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1; - for (;;) { - WorkerRunnable* event; + SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex]; + + MOZ_ASSERT(loopInfo); + MOZ_ASSERT(!loopInfo->mHasRun); + MOZ_ASSERT(!loopInfo->mCompleted); + +#ifdef DEBUG + loopInfo->mHasRun = true; +#endif + + nsCOMPtr thread = do_QueryInterface(mThread); + MOZ_ASSERT(thread); + + while (!loopInfo->mCompleted) { + bool normalRunnablesPending = false; + + // Don't block with the periodic GC timer running. + if (!NS_HasPendingEvents(thread)) { + SetGCTimerMode(IdleTimer); + } + + // Wait for something to do. { MutexAutoLock lock(mMutex); - while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) { - WaitForWorkerEvents(); + for (;;) { + while (mControlQueue.IsEmpty() && + !normalRunnablesPending && + !(normalRunnablesPending = NS_HasPendingEvents(thread))) { + WaitForWorkerEvents(); + } + + ProcessAllControlRunnablesLocked(); + + if (normalRunnablesPending) { + break; + } } } - static_cast(event)->Run(); - NS_RELEASE(event); + // Make sure the periodic timer is running before we continue. + SetGCTimerMode(PeriodicTimer); - if (syncQueue->mComplete) { - NS_ASSERTION(mSyncQueues.Length() - 1 == aSyncLoopKey, - "Mismatched calls!"); - NS_ASSERTION(syncQueue->mQueue.IsEmpty(), "Unprocessed sync events!"); + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false)); - bool result = syncQueue->mResult; - DestroySyncLoop(aSyncLoopKey); + // Now *might* be a good time to GC. Let the JS engine make the decision. + JS_MaybeGC(cx); + } + // Make sure that the stack didn't change underneath us. + MOZ_ASSERT(!mSyncLoopStack.IsEmpty()); + MOZ_ASSERT(mSyncLoopStack.Length() - 1 == currentLoopIndex); + MOZ_ASSERT(mSyncLoopStack[currentLoopIndex] == loopInfo); + + // We're about to delete |loop|, stash its event target and result. + nsIEventTarget* nestedEventTarget = + loopInfo->mEventTarget->GetWeakNestedEventTarget(); + MOZ_ASSERT(nestedEventTarget); + + bool result = loopInfo->mResult; + + { + // Modifications must be protected by mMutex in DEBUG builds, see comment + // about mSyncLoopStack in WorkerPrivate.h. #ifdef DEBUG - syncQueue = nullptr; + MutexAutoLock lock(mMutex); #endif - return result; + // This will delete |loop|! + mSyncLoopStack.RemoveElementAt(currentLoopIndex); + } + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(thread->PopEventQueue(nestedEventTarget))); + + return result; +} + +void +WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult) +{ + AssertIsOnWorkerThread(); + AssertValidSyncLoop(aSyncLoopTarget); + + MOZ_ASSERT(!mSyncLoopStack.IsEmpty()); + + for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) { + nsAutoPtr& loopInfo = mSyncLoopStack[index - 1]; + MOZ_ASSERT(loopInfo); + MOZ_ASSERT(loopInfo->mEventTarget); + + if (loopInfo->mEventTarget == aSyncLoopTarget) { + // Can't assert |loop->mHasRun| here because dispatch failures can cause + // us to bail out early. + MOZ_ASSERT(!loopInfo->mCompleted); + + loopInfo->mResult = aResult; + loopInfo->mCompleted = true; + + loopInfo->mEventTarget->Disable(); + + return; + } + + MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget)); + } + + MOZ_CRASH("Unknown sync loop!"); +} + +#ifdef DEBUG +void +WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) +{ + MOZ_ASSERT(aSyncLoopTarget); + + EventTarget* workerTarget; + nsresult rv = + aSyncLoopTarget->QueryInterface(kDEBUGWorkerEventTargetIID, + reinterpret_cast(&workerTarget)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(workerTarget); + + bool valid = false; + + { + MutexAutoLock lock(mMutex); + + for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) { + nsAutoPtr& loopInfo = mSyncLoopStack[index]; + MOZ_ASSERT(loopInfo); + MOZ_ASSERT(loopInfo->mEventTarget); + + if (loopInfo->mEventTarget == aSyncLoopTarget) { + valid = true; + break; + } + + MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget)); } } - NS_NOTREACHED("Shouldn't get here!"); - return false; -} - -void -WorkerPrivate::StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult) -{ - AssertIsOnWorkerThread(); - - NS_ASSERTION(mSyncQueues.IsEmpty() || - (aSyncLoopKey == mSyncQueues.Length() - 1), - "Forgot to call CreateNewSyncLoop!"); - if (aSyncLoopKey != mSyncQueues.Length() - 1) { - return; - } - - SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get(); - - NS_ASSERTION(!syncQueue->mComplete, "Already called StopSyncLoop?!"); - - syncQueue->mResult = aSyncResult; - syncQueue->mComplete = true; -} - -void -WorkerPrivate::DestroySyncLoop(uint32_t aSyncLoopKey) -{ - AssertIsOnWorkerThread(); - - mSyncQueues.RemoveElementAt(aSyncLoopKey); + MOZ_ASSERT(valid); } +#endif void WorkerPrivate::PostMessageToParentInternal( @@ -4629,8 +4925,10 @@ WorkerPrivate::PostMessageToParentInternal( } nsRefPtr runnable = - new MessageEventRunnable(this, WorkerRunnable::ParentThread, buffer, - clonedObjects, aToMessagePort, aMessagePortSerial); + new MessageEventRunnable(this, + WorkerRunnable::ParentThreadUnchangedBusyCount, + buffer, clonedObjects, aToMessagePort, + aMessagePortSerial); if (!runnable->Dispatch(aCx)) { aRv = NS_ERROR_FAILURE; } @@ -4663,33 +4961,47 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!"); + nsRefPtr eventTarget; + // Save the old status and set the new status. Status previousStatus; { MutexAutoLock lock(mMutex); if (mStatus >= aStatus) { + MOZ_ASSERT(!mEventTarget); return true; } previousStatus = mStatus; mStatus = aStatus; + + mEventTarget.swap(eventTarget); + } + + // Now that mStatus > Running, no-one can create a new WorkerEventTarget or + // WorkerCrossThreadDispatcher if we don't already have one. + if (eventTarget) { + // Since we'll no longer process events, make sure we no longer allow anyone + // to post them. We have to do this without mMutex held, since our mutex + // must be acquired *after* the WorkerEventTarget's mutex when they're both + // held. + eventTarget->Disable(); + eventTarget = nullptr; } - // Now that status > Running, no-one can create a new mCrossThreadDispatcher - // if we don't already have one. if (mCrossThreadDispatcher) { // Since we'll no longer process events, make sure we no longer allow - // anyone to post them. - // We have to do this without mMutex held, since our mutex must be - // acquired *after* mCrossThreadDispatcher's mutex when they're both held. + // anyone to post them. We have to do this without mMutex held, since our + // mutex must be acquired *after* mCrossThreadDispatcher's mutex when + // they're both held. mCrossThreadDispatcher->Forget(); + mCrossThreadDispatcher = nullptr; } - NS_ASSERTION(previousStatus != Pending, "How is this possible?!"); + MOZ_ASSERT(previousStatus != Pending); - NS_ASSERTION(previousStatus >= Canceling || mKillTime.IsNull(), - "Bad kill time set!"); + MOZ_ASSERT(previousStatus >= Canceling || mKillTime.IsNull()); // Let all our features know the new status. NotifyFeatures(aCx, aStatus); @@ -4697,8 +5009,7 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) // If this is the first time our status has changed then we need to clear the // main event queue. if (previousStatus == Running) { - MutexAutoLock lock(mMutex); - ClearQueue(&mQueue); + ClearMainEventQueue(); } // If we've run the close handler, we don't need to do anything else. @@ -4717,19 +5028,10 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) // If this is the first time our status has changed we also need to schedule // the close handler unless we're being shut down. if (previousStatus == Running && aStatus != Killing) { - NS_ASSERTION(!mCloseHandlerStarted && !mCloseHandlerFinished, - "This is impossible!"); + MOZ_ASSERT(!mCloseHandlerStarted && !mCloseHandlerFinished); nsRefPtr closeRunnable = new CloseEventRunnable(this); - - MutexAutoLock lock(mMutex); - - if (!mQueue.Push(closeRunnable)) { - NS_WARNING("Failed to push closeRunnable!"); - return false; - } - - closeRunnable.forget(); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(closeRunnable))); } if (aStatus == Closing) { @@ -4750,9 +5052,7 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) if (aStatus == Canceling) { // We need to enforce a timeout on the close handler. - NS_ASSERTION(previousStatus == Running || previousStatus == Closing || - previousStatus == Terminating, - "Bad previous status!"); + MOZ_ASSERT(previousStatus >= Running && previousStatus <= Terminating); uint32_t killSeconds = IsChromeWorker() ? RuntimeService::GetChromeCloseHandlerTimeoutSeconds() : @@ -4770,18 +5070,15 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus) return mCloseHandlerStarted; } - if (aStatus == Killing) { - mKillTime = TimeStamp::Now(); + MOZ_ASSERT(aStatus == Killing); - if (!mCloseHandlerFinished && !ScheduleKillCloseEventRunnable(aCx)) { - return false; - } + mKillTime = TimeStamp::Now(); - // Always abort the script. - return false; + if (mCloseHandlerStarted && !mCloseHandlerFinished) { + ScheduleKillCloseEventRunnable(aCx); } - NS_NOTREACHED("Should never get here!"); + // Always abort the script. return false; } @@ -4789,7 +5086,7 @@ bool WorkerPrivate::ScheduleKillCloseEventRunnable(JSContext* aCx) { AssertIsOnWorkerThread(); - NS_ASSERTION(!mKillTime.IsNull(), "Must have a kill time!"); + MOZ_ASSERT(!mKillTime.IsNull()); nsRefPtr killCloseEventRunnable = new KillCloseEventRunnable(this); @@ -4797,14 +5094,9 @@ WorkerPrivate::ScheduleKillCloseEventRunnable(JSContext* aCx) return false; } - MutexAutoLock lock(mMutex); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread( + killCloseEventRunnable))); - if (!mQueue.Push(killCloseEventRunnable)) { - NS_WARNING("Failed to push killCloseEventRunnable!"); - return false; - } - - killCloseEventRunnable.forget(); return true; } @@ -4954,21 +5246,24 @@ WorkerPrivate::SetTimeout(JSContext* aCx, nsresult rv; if (!mTimer) { - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); if (NS_FAILED(rv)) { aRv.Throw(rv); return 0; } - nsRefPtr timerRunnable = new TimerRunnable(this); + nsRefPtr runnable = new TimerRunnable(this); - nsCOMPtr target = - new WorkerRunnableEventTarget(timerRunnable); - rv = mTimer->SetTarget(target); + nsRefPtr target = + new TimerThreadEventTarget(this, runnable); + + rv = timer->SetTarget(target); if (NS_FAILED(rv)) { aRv.Throw(rv); return 0; } + + timer.swap(mTimer); } if (!mTimerRunning) { @@ -5244,19 +5539,30 @@ WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking, { AssertIsOnWorkerThread(); + if (!JS::CurrentGlobalOrNull(aCx)) { + // We haven't compiled anything yet. Just bail out. + return; + } + if (aShrinking || aCollectChildren) { JSRuntime* rt = JS_GetRuntime(aCx); JS::PrepareForFullGC(rt); if (aShrinking) { JS::ShrinkingGC(rt, JS::gcreason::DOM_WORKER); + + if (!aCollectChildren) { + LOG(("Worker %p collected idle garbage\n", this)); + } } else { JS::GCForReason(rt, JS::gcreason::DOM_WORKER); + LOG(("Worker %p collected garbage\n", this)); } } else { JS_MaybeGC(aCx); + LOG(("Worker %p collected periodic garbage\n", this)); } if (aCollectChildren) { @@ -5280,28 +5586,49 @@ WorkerPrivate::CycleCollectInternal(JSContext* aCx, bool aCollectChildren) } } - -template void -WorkerPrivateParent::RegisterHostObjectURI(const nsACString& aURI) +WorkerPrivate::SetThread(nsIThread* aThread) { - AssertIsOnMainThread(); - mHostObjectURIs.AppendElement(aURI); -} +#ifdef DEBUG + if (aThread) { + bool isOnCurrentThread; + MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread))); + MOZ_ASSERT(isOnCurrentThread); -template -void -WorkerPrivateParent::UnregisterHostObjectURI(const nsACString& aURI) -{ - AssertIsOnMainThread(); - mHostObjectURIs.RemoveElement(aURI); -} + MOZ_ASSERT(!mPRThread); + mPRThread = PRThreadFromThread(aThread); + MOZ_ASSERT(mPRThread); + } + else { + MOZ_ASSERT(mPRThread); + } +#endif -template -void -WorkerPrivateParent::StealHostObjectURIs(nsTArray& aArray) -{ - aArray.SwapElements(mHostObjectURIs); + nsCOMPtr doomedThread; + + { // Scope so that |doomedThread| is released without holding the lock. + MutexAutoLock lock(mMutex); + + if (aThread) { + MOZ_ASSERT(!mThread); + MOZ_ASSERT(mStatus == Pending); + + mThread = aThread; + + if (!mPreStartRunnables.IsEmpty()) { + for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mThread->Dispatch( + mPreStartRunnables[index], + NS_DISPATCH_NORMAL))); + } + mPreStartRunnables.Clear(); + } + } + else { + MOZ_ASSERT(mThread); + mThread.swap(doomedThread); + } + } } WorkerCrossThreadDispatcher* @@ -5321,6 +5648,9 @@ WorkerPrivate::BeginCTypesCall() { AssertIsOnWorkerThread(); + // Don't try to GC while we're blocked in a ctypes call. + SetGCTimerMode(NoTimer); + MutexAutoLock lock(mMutex); NS_ASSERTION(!mBlockedForMemoryReporter, @@ -5342,18 +5672,23 @@ WorkerPrivate::EndCTypesCall() { AssertIsOnWorkerThread(); - MutexAutoLock lock(mMutex); + { + MutexAutoLock lock(mMutex); - NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!"); + NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!"); - // Don't continue until the memory reporter has finished. - while (mMemoryReporterRunning) { - mMemoryReportCondVar.Wait(); + // Don't continue until the memory reporter has finished. + while (mMemoryReporterRunning) { + mMemoryReportCondVar.Wait(); + } + + // No need to notify the main thread here as it shouldn't be waiting to see + // this state. + mBlockedForMemoryReporter = false; } - // No need to notify the main thread here as it shouldn't be waiting to see - // this state. - mBlockedForMemoryReporter = false; + // Make sure the periodic timer is running before we start running JS again. + SetGCTimerMode(PeriodicTimer); } bool @@ -5462,49 +5797,115 @@ WorkerPrivate::CreateGlobalScope(JSContext* aCx) } #ifdef DEBUG -template -void -WorkerPrivateParent::AssertIsOnParentThread() const -{ - if (GetParent()) { - GetParent()->AssertIsOnWorkerThread(); - } - else { - AssertIsOnMainThread(); - } -} - -template -void -WorkerPrivateParent::AssertInnerWindowIsCorrect() const -{ - AssertIsOnParentThread(); - - // Only care about top level workers from windows. - if (mParent || !mLoadInfo.mWindow) { - return; - } - - AssertIsOnMainThread(); - - nsPIDOMWindow* outer = mLoadInfo.mWindow->GetOuterWindow(); - NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow, - "Inner window no longer correct!"); -} void WorkerPrivate::AssertIsOnWorkerThread() const { - MOZ_ASSERT(mThread, - "Trying to assert thread identity after thread has been " - "shutdown!"); + // This is much more complicated than it needs to be but we can't use mThread + // because it must be protected by mMutex and sometimes this method is called + // when mMutex is already locked. This method should always work. + MOZ_ASSERT(mPRThread, + "AssertIsOnWorkerThread() called before a thread was assigned!"); + + MOZ_ASSERT(nsThreadManager::get()); + + nsCOMPtr thread; + nsresult rv = + nsThreadManager::get()->GetThreadFromPRThread(mPRThread, + getter_AddRefs(thread)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(thread); bool current; - MOZ_ASSERT(NS_SUCCEEDED(mThread->IsOnCurrentThread(¤t))); + rv = thread->IsOnCurrentThread(¤t); + MOZ_ASSERT(NS_SUCCEEDED(rv)); MOZ_ASSERT(current, "Wrong thread!"); } + #endif // DEBUG +NS_IMPL_ISUPPORTS_INHERITED0(ExternalRunnableWrapper, WorkerRunnable) + +template +NS_IMPL_ADDREF(WorkerPrivateParent::EventTarget) + +template +NS_IMPL_RELEASE(WorkerPrivateParent::EventTarget) + +template +NS_INTERFACE_MAP_BEGIN(WorkerPrivateParent::EventTarget) + NS_INTERFACE_MAP_ENTRY(nsIEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISupports) +#ifdef DEBUG + // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its + // result. + if (aIID.Equals(kDEBUGWorkerEventTargetIID)) { + *aInstancePtr = this; + return NS_OK; + } + else +#endif +NS_INTERFACE_MAP_END + +template +NS_IMETHODIMP +WorkerPrivateParent:: +EventTarget::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) +{ + // May be called on any thread! + + // Workers only support asynchronous dispatch for now. + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr workerRunnable; + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + NS_WARNING("A runnable was posted to a worker that is already shutting " + "down!"); + return NS_ERROR_UNEXPECTED; + } + + if (aRunnable) { + workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(aRunnable); + } + + nsresult rv = + mWorkerPrivate->DispatchPrivate(workerRunnable, mNestedEventTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +template +NS_IMETHODIMP +WorkerPrivateParent:: +EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) +{ + // May be called on any thread! + + MOZ_ASSERT(aIsOnCurrentThread); + + MutexAutoLock lock(mMutex); + + if (!mWorkerPrivate) { + NS_WARNING("A worker's event target was used after the worker has !"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = mWorkerPrivate->IsOnCurrentThread(aIsOnCurrentThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + BEGIN_WORKERS_NAMESPACE WorkerCrossThreadDispatcher* @@ -5520,14 +5921,6 @@ GetWorkerCrossThreadDispatcher(JSContext* aCx, JS::Value aWorker) return w->GetCrossThreadDispatcher(); } -// Can't use NS_IMPL_CYCLE_COLLECTION_CLASS(WorkerPrivateParent) because of the -// templates. -template <> -WorkerPrivateParent::cycleCollection WorkerPrivateParent::_cycleCollectorGlobal = WorkerPrivateParent::cycleCollection(); - -// Force instantiation. -template class WorkerPrivateParent; - JSStructuredCloneCallbacks* WorkerStructuredCloneCallbacks(bool aMainRuntime) { @@ -5544,4 +5937,7 @@ ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime) &gChromeWorkerStructuredCloneCallbacks; } +// Force instantiation. +template class WorkerPrivateParent; + END_WORKERS_NAMESPACE diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index bac29ee7f9c4..b553d7e08b31 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -9,25 +9,19 @@ #include "Workers.h" #include "nsIContentSecurityPolicy.h" -#include "nsIRunnable.h" -#include "nsIThread.h" -#include "nsIThreadInternal.h" #include "nsPIDOMWindow.h" -#include "mozilla/Assertions.h" #include "mozilla/CondVar.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/BindingDeclarations.h" #include "nsCycleCollectionParticipant.h" #include "nsDataHashtable.h" #include "nsDOMEventTargetHelper.h" -#include "nsEventQueue.h" #include "nsHashKeys.h" #include "nsRefPtrHashtable.h" #include "nsString.h" #include "nsTArray.h" #include "nsThreadUtils.h" -#include "nsTPriorityQueue.h" #include "StructuredCloneTags.h" #include "Queue.h" @@ -36,8 +30,10 @@ class JSAutoStructuredCloneBuffer; class nsIChannel; class nsIDocument; +class nsIEventTarget; class nsIPrincipal; class nsIScriptContext; +class nsIThread; class nsITimer; class nsIURI; @@ -51,142 +47,19 @@ class Function; } } -BEGIN_WORKERS_NAMESPACE - -class MessagePort; -class SharedWorker; -class WorkerGlobalScope; -class WorkerPrivate; - -class WorkerRunnable : public nsIRunnable -{ -public: - enum Target { ParentThread, WorkerThread }; - enum BusyBehavior { ModifyBusyCount, UnchangedBusyCount }; - enum ClearingBehavior { SkipWhenClearing, RunWhenClearing }; - -protected: - WorkerPrivate* mWorkerPrivate; - Target mTarget; - BusyBehavior mBusyBehavior; - ClearingBehavior mClearingBehavior; - -public: - NS_DECL_THREADSAFE_ISUPPORTS - - bool - Dispatch(JSContext* aCx); - - static bool - DispatchToMainThread(nsIRunnable*); - - bool - WantsToRunDuringClear() - { - return mClearingBehavior == RunWhenClearing; - } - -protected: - WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, - BusyBehavior aBusyBehavior, - ClearingBehavior aClearingBehavior) #ifdef DEBUG - ; -#else - : mWorkerPrivate(aWorkerPrivate), mTarget(aTarget), - mBusyBehavior(aBusyBehavior), mClearingBehavior(aClearingBehavior) - { } +struct PRThread; #endif - virtual ~WorkerRunnable() - { } +BEGIN_WORKERS_NAMESPACE - virtual bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate); - - virtual void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult); - - virtual bool - DispatchInternal(); - - virtual bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0; - - virtual void - PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult); - -public: - NS_DECL_NSIRUNNABLE -}; - -class WorkerSyncRunnable : public WorkerRunnable -{ -protected: - uint32_t mSyncQueueKey; - bool mBypassSyncQueue; - -protected: - friend class WorkerPrivate; - - WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, uint32_t aSyncQueueKey, - bool aBypassSyncQueue = false, - ClearingBehavior aClearingBehavior = SkipWhenClearing) - : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount, - aClearingBehavior), - mSyncQueueKey(aSyncQueueKey), mBypassSyncQueue(aBypassSyncQueue) - { } - - virtual ~WorkerSyncRunnable() - { } - - virtual bool - DispatchInternal() MOZ_OVERRIDE; -}; - -class MainThreadSyncRunnable : public WorkerSyncRunnable -{ -public: - MainThreadSyncRunnable(WorkerPrivate* aWorkerPrivate, - ClearingBehavior aClearingBehavior, - uint32_t aSyncQueueKey, - bool aBypassSyncEventQueue) - : WorkerSyncRunnable(aWorkerPrivate, aSyncQueueKey, aBypassSyncEventQueue, - aClearingBehavior) - { - AssertIsOnMainThread(); - } - - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE - { - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) MOZ_OVERRIDE - { - AssertIsOnMainThread(); - } -}; - -class WorkerControlRunnable : public WorkerRunnable -{ -protected: - WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, - BusyBehavior aBusyBehavior) - : WorkerRunnable(aWorkerPrivate, aTarget, aBusyBehavior, SkipWhenClearing) - { } - - virtual ~WorkerControlRunnable() - { } - - virtual bool - DispatchInternal() MOZ_OVERRIDE; -}; +class AutoSyncLoopHolder; +class MessagePort; +class SharedWorker; +class WorkerControlRunnable; +class WorkerGlobalScope; +class WorkerPrivate; +class WorkerRunnable; // SharedMutex is a small wrapper around an (internal) reference-counted Mutex // object. It exists to avoid changing a lot of code to use Mutex* instead of @@ -195,7 +68,7 @@ class SharedMutex { typedef mozilla::Mutex Mutex; - class RefCountedMutex : public Mutex + class RefCountedMutex MOZ_FINAL : public Mutex { public: RefCountedMutex(const char* aName) @@ -222,20 +95,17 @@ public: operator Mutex&() { - MOZ_ASSERT(mMutex); return *mMutex; } operator const Mutex&() const { - MOZ_ASSERT(mMutex); return *mMutex; } void AssertCurrentThreadOwns() const { - MOZ_ASSERT(mMutex); mMutex->AssertCurrentThreadOwns(); } }; @@ -245,6 +115,10 @@ class WorkerPrivateParent : public nsDOMEventTargetHelper { class SynchronizeAndResumeRunnable; +protected: + class EventTarget; + friend class EventTarget; + public: struct LocationInfo { @@ -312,6 +186,10 @@ protected: mozilla::CondVar mCondVar; mozilla::CondVar mMemoryReportCondVar; + // Protected by mMutex. + nsRefPtr mEventTarget; + nsTArray> mPreStartRunnables; + private: WorkerPrivate* mParent; nsString mScriptURL; @@ -322,7 +200,7 @@ private: LoadInfo mLoadInfo; // Only used for top level workers. - nsTArray > mQueuedRunnables; + nsTArray> mQueuedRunnables; nsRevocableEventPtr mSynchronizeRunnable; // Only for ChromeWorkers without window and only touched on the main thread. @@ -376,6 +254,9 @@ private: bool aToMessagePort, uint64_t aMessagePortSerial, ErrorResult& aRv); + nsresult + DispatchPrivate(WorkerRunnable* aRunnable, nsIEventTarget* aSyncLoopTarget); + public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_OVERRIDE; @@ -384,6 +265,21 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WorkerPrivateParent, nsDOMEventTargetHelper) + nsresult + Dispatch(WorkerRunnable* aRunnable) + { + return DispatchPrivate(aRunnable, nullptr); + } + + nsresult + DispatchControlRunnable(WorkerControlRunnable* aWorkerControlRunnable); + + already_AddRefed + MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable); + + already_AddRefed + GetEventTarget(); + // May be called on any thread... bool Start(); @@ -515,7 +411,7 @@ public: WorkerScriptLoaded(); void - QueueRunnable(WorkerRunnable* aRunnable) + QueueRunnable(nsIRunnable* aRunnable) { AssertIsOnMainThread(); mQueuedRunnables.AppendElement(aRunnable); @@ -538,13 +434,10 @@ public: IsAcceptingEvents() { AssertIsOnParentThread(); - bool acceptingEvents; - { - mozilla::MutexAutoLock lock(mMutex); - acceptingEvents = mParentStatus < Terminating; + + MutexAutoLock lock(mMutex); + return mParentStatus < Terminating; } - return acceptingEvents; - } Status ParentStatus() const @@ -775,53 +668,62 @@ class WorkerPrivate : public WorkerPrivateParent { friend class WorkerPrivateParent; typedef WorkerPrivateParent ParentType; + friend class AutoSyncLoopHolder; struct TimeoutInfo; - typedef Queue EventQueue; - EventQueue mQueue; - EventQueue mControlQueue; - - struct SyncQueue - { - Queue mQueue; - bool mComplete; - bool mResult; - - SyncQueue() - : mComplete(false), mResult(false) - { } - - ~SyncQueue() - { - WorkerRunnable* event; - while (mQueue.Pop(event)) { - event->Release(); - } - } - }; - class MemoryReporter; friend class MemoryReporter; - nsTArray > mSyncQueues; + enum GCTimerMode + { + PeriodicTimer = 0, + IdleTimer, + NoTimer + }; + + Queue mControlQueue; // Touched on multiple threads, protected with mMutex. JSContext* mJSContext; nsRefPtr mCrossThreadDispatcher; + nsTArray> mUndispatchedRunnablesForSyncLoop; + nsCOMPtr mThread; // Things touched on worker thread only. nsRefPtr mScope; nsTArray mChildWorkers; nsTArray mFeatures; - nsTArray > mTimeouts; + nsTArray> mTimeouts; + + struct SyncLoopInfo + { + SyncLoopInfo(EventTarget* aEventTarget); + + nsRefPtr mEventTarget; + bool mCompleted; + bool mResult; +#ifdef DEBUG + bool mHasRun; +#endif + }; + + // This is only modified on the worker thread, but in DEBUG builds + // AssertValidSyncLoop function iterates it on other threads. Therefore + // modifications are done with mMutex held *only* in DEBUG builds. + nsTArray> mSyncLoopStack; nsCOMPtr mTimer; + + nsCOMPtr mGCTimer; + nsCOMPtr mPeriodicGCTimerTarget; + nsCOMPtr mIdleGCTimerTarget; + nsRefPtr mMemoryReporter; nsRefPtrHashtable mWorkerPorts; - mozilla::TimeStamp mKillTime; + TimeStamp mKillTime; uint32_t mErrorHandlerRecursionCount; uint32_t mNextTimeoutId; Status mStatus; @@ -832,9 +734,11 @@ class WorkerPrivate : public WorkerPrivateParent bool mCloseHandlerFinished; bool mMemoryReporterRunning; bool mBlockedForMemoryReporter; + bool mCancelAllPendingRunnables; + bool mPeriodicGCTimerRunning; #ifdef DEBUG - nsCOMPtr mThread; + PRThread* mPRThread; #endif bool mPreferences[WORKERPREF_COUNT]; @@ -867,27 +771,8 @@ public: bool OperationCallback(JSContext* aCx); - bool - Dispatch(WorkerRunnable* aEvent) - { - return Dispatch(aEvent, &mQueue); - } - - bool - Dispatch(WorkerSyncRunnable* aEvent) - { - if (aEvent->mBypassSyncQueue) { - return Dispatch(aEvent, &mQueue); - } - - return DispatchToSyncQueue(aEvent); - } - - bool - Dispatch(WorkerControlRunnable* aEvent) - { - return Dispatch(aEvent, &mControlQueue); - } + nsresult + IsOnCurrentThread(bool* aIsOnCurrentThread); bool CloseInternal(JSContext* aCx) @@ -930,18 +815,6 @@ public: mFeatures.IsEmpty()); } - uint32_t - CreateNewSyncLoop(); - - bool - RunSyncLoop(JSContext* aCx, uint32_t aSyncLoopKey); - - void - StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult); - - void - DestroySyncLoop(uint32_t aSyncLoopKey); - void PostMessageToParent(JSContext* aCx, JS::Handle aMessage, @@ -1008,7 +881,7 @@ public: UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, uint32_t aValue); void - ScheduleDeletion(bool aWasPending); + ScheduleDeletion(); bool BlockAndCollectRuntimeStats(JS::RuntimeStats* aRtStats); @@ -1042,18 +915,14 @@ public: return mScope; } -#ifdef DEBUG void - AssertIsOnWorkerThread() const; + SetThread(nsIThread* aThread); - void - SetThread(nsIThread* aThread) - { - mThread = aThread; - } -#else void AssertIsOnWorkerThread() const +#ifdef DEBUG + ; +#else { } #endif @@ -1113,20 +982,37 @@ public: return mPreferences[WORKERPREF_PROMISE]; } + void + StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult); + + bool + AllPendingRunnablesShouldBeCanceled() const + { + return mCancelAllPendingRunnables; + } + + void + OnProcessNextEvent(uint32_t aRecursionDepth); + + void + AfterProcessNextEvent(uint32_t aRecursionDepth); + + void + AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) +#ifdef DEBUG + ; +#else + { } +#endif + private: WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker, WorkerType aWorkerType, const nsAString& aSharedWorkerName, LoadInfo& aLoadInfo); - bool - Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue); - - bool - DispatchToSyncQueue(WorkerSyncRunnable* aEvent); - void - ClearQueue(EventQueue* aQueue); + ClearMainEventQueue(); bool MayContinueRunning() @@ -1135,7 +1021,7 @@ private: Status status; { - mozilla::MutexAutoLock lock(mMutex); + MutexAutoLock lock(mMutex); status = mStatus; } @@ -1157,22 +1043,15 @@ private: bool ScheduleKillCloseEventRunnable(JSContext* aCx); - void - StopAcceptingEvents() + bool + ProcessAllControlRunnables() { - AssertIsOnWorkerThread(); - - mozilla::MutexAutoLock lock(mMutex); - - mStatus = Dead; - mJSContext = nullptr; - - ClearQueue(&mControlQueue); - ClearQueue(&mQueue); + MutexAutoLock lock(mMutex); + return ProcessAllControlRunnablesLocked(); } bool - ProcessAllControlRunnables(); + ProcessAllControlRunnablesLocked(); void EnableMemoryReporter(); @@ -1197,6 +1076,21 @@ private: AssertIsOnWorkerThread(); memcpy(aPreferences, mPreferences, WORKERPREF_COUNT * sizeof(bool)); } + + already_AddRefed + CreateNewSyncLoop(); + + bool + RunCurrentSyncLoop(); + + void + InitializeGCTimers(); + + void + SetGCTimerMode(GCTimerMode aMode); + + void + ShutdownGCTimers(); }; // This class is only used to trick the DOM bindings. We never create @@ -1246,38 +1140,40 @@ ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime); class AutoSyncLoopHolder { + WorkerPrivate* mWorkerPrivate; + nsCOMPtr mTarget; + public: AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate) - : mWorkerPrivate(aWorkerPrivate), mSyncLoopKey(UINT32_MAX) + : mWorkerPrivate(aWorkerPrivate), mTarget(aWorkerPrivate->CreateNewSyncLoop()) { - mSyncLoopKey = mWorkerPrivate->CreateNewSyncLoop(); + aWorkerPrivate->AssertIsOnWorkerThread(); } ~AutoSyncLoopHolder() { if (mWorkerPrivate) { - mWorkerPrivate->StopSyncLoop(mSyncLoopKey, false); - mWorkerPrivate->DestroySyncLoop(mSyncLoopKey); + mWorkerPrivate->AssertIsOnWorkerThread(); + mWorkerPrivate->StopSyncLoop(mTarget, false); } } bool - RunAndForget(JSContext* aCx) + Run() { WorkerPrivate* workerPrivate = mWorkerPrivate; mWorkerPrivate = nullptr; - return workerPrivate->RunSyncLoop(aCx, mSyncLoopKey); + + workerPrivate->AssertIsOnWorkerThread(); + + return workerPrivate->RunCurrentSyncLoop(); } - uint32_t - SyncQueueKey() const + nsIEventTarget* + EventTarget() const { - return mSyncLoopKey; + return mTarget; } - -private: - WorkerPrivate* mWorkerPrivate; - uint32_t mSyncLoopKey; }; END_WORKERS_NAMESPACE diff --git a/dom/workers/WorkerRunnable.cpp b/dom/workers/WorkerRunnable.cpp new file mode 100644 index 000000000000..e9bdb3e43b55 --- /dev/null +++ b/dom/workers/WorkerRunnable.cpp @@ -0,0 +1,480 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WorkerRunnable.h" + +#include "nsIEventTarget.h" +#include "nsIRunnable.h" + +#include "js/RootingAPI.h" +#include "js/Value.h" +#include "nsThreadUtils.h" + +#include "WorkerPrivate.h" + +USING_WORKERS_NAMESPACE + +namespace { + +const nsIID kWorkerRunnableIID = { + 0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 } +}; + +void +MaybeReportMainThreadException(JSContext* aCx, bool aResult) +{ + AssertIsOnMainThread(); + + if (aCx && !aResult) { + JS_ReportPendingException(aCx); + } +} + +} // anonymous namespace + +#ifdef DEBUG +WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior) +: mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0), + mCallingCancelWithinRun(false) +{ + MOZ_ASSERT(aWorkerPrivate); +} +#endif + +bool +WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +{ +#ifdef DEBUG + MOZ_ASSERT(aWorkerPrivate); + + switch (mBehavior) { + case ParentThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnWorkerThread(); + break; + + case WorkerThreadModifyBusyCount: + aWorkerPrivate->AssertIsOnParentThread(); + MOZ_ASSERT(aCx); + break; + + case WorkerThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnParentThread(); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); + } +#endif + + if (mBehavior == WorkerThreadModifyBusyCount) { + return aWorkerPrivate->ModifyBusyCount(aCx, true); + } + + return true; +} + +bool +WorkerRunnable::Dispatch(JSContext* aCx) +{ + bool ok; + + if (!aCx) { + ok = PreDispatch(nullptr, mWorkerPrivate); + if (ok) { + ok = DispatchInternal(); + } + PostDispatch(nullptr, mWorkerPrivate, ok); + return ok; + } + + JSAutoRequest ar(aCx); + + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + + Maybe ac; + if (global) { + ac.construct(aCx, global); + } + + ok = PreDispatch(aCx, mWorkerPrivate); + + if (ok && !DispatchInternal()) { + ok = false; + } + + PostDispatch(aCx, mWorkerPrivate, ok); + + return ok; +} + +bool +WorkerRunnable::DispatchInternal() +{ + if (mBehavior == WorkerThreadModifyBusyCount || + mBehavior == WorkerThreadUnchangedBusyCount) { + return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this)); + } + + MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount); + + if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) { + return NS_SUCCEEDED(parent->Dispatch(this)); + } + + nsCOMPtr mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + + return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void +WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) +{ + MOZ_ASSERT(aWorkerPrivate); + +#ifdef DEBUG + switch (mBehavior) { + case ParentThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnWorkerThread(); + break; + + case WorkerThreadModifyBusyCount: + aWorkerPrivate->AssertIsOnParentThread(); + MOZ_ASSERT(aCx); + break; + + case WorkerThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnParentThread(); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); + } +#endif + + if (!aDispatchResult) { + if (mBehavior == WorkerThreadModifyBusyCount) { + aWorkerPrivate->ModifyBusyCount(aCx, false); + } + if (aCx) { + JS_ReportPendingException(aCx); + } + } +} + +void +WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aRunResult) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aWorkerPrivate); + +#ifdef DEBUG + switch (mBehavior) { + case ParentThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnParentThread(); + break; + + case WorkerThreadModifyBusyCount: + aWorkerPrivate->AssertIsOnWorkerThread(); + break; + + case WorkerThreadUnchangedBusyCount: + aWorkerPrivate->AssertIsOnWorkerThread(); + break; + + default: + MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); + } +#endif + + if (mBehavior == WorkerThreadModifyBusyCount) { + if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) { + aRunResult = false; + } + } + + if (!aRunResult) { + JS_ReportPendingException(aCx); + } +} + +// static +WorkerRunnable* +WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) +{ + MOZ_ASSERT(aRunnable); + + WorkerRunnable* runnable; + nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID, + reinterpret_cast(&runnable)); + if (NS_FAILED(rv)) { + return nullptr; + } + + MOZ_ASSERT(runnable); + return runnable; +} + +NS_IMPL_ADDREF(WorkerRunnable) +NS_IMPL_RELEASE(WorkerRunnable) + +NS_INTERFACE_MAP_BEGIN(WorkerRunnable) + NS_INTERFACE_MAP_ENTRY(nsIRunnable) + NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable) + NS_INTERFACE_MAP_ENTRY(nsISupports) + // kWorkerRunnableIID is special in that it does not AddRef its result. + if (aIID.Equals(kWorkerRunnableIID)) { + *aInstancePtr = this; + return NS_OK; + } + else +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +WorkerRunnable::Run() +{ + bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount || + mBehavior == WorkerThreadUnchangedBusyCount; + +#ifdef DEBUG + MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread); + if (targetIsWorkerThread) { + mWorkerPrivate->AssertIsOnWorkerThread(); + } + else { + MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount); + mWorkerPrivate->AssertIsOnParentThread(); + } +#endif + + if (IsCanceled() && !mCallingCancelWithinRun) { + return NS_OK; + } + + JSContext* cx; + nsRefPtr kungFuDeathGrip; + nsCxPusher pusher; + + if (targetIsWorkerThread) { + if (mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() && + !IsCanceled() && + !mCallingCancelWithinRun) { + + // Prevent recursion. + mCallingCancelWithinRun = true; + + Cancel(); + + MOZ_ASSERT(mCallingCancelWithinRun); + mCallingCancelWithinRun = false; + + MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!"); + + return NS_OK; + } + + cx = mWorkerPrivate->GetJSContext(); + MOZ_ASSERT(cx); + } + else { + cx = mWorkerPrivate->ParentJSContext(); + MOZ_ASSERT(cx); + + kungFuDeathGrip = mWorkerPrivate; + + if (!mWorkerPrivate->GetParent()) { + AssertIsOnMainThread(); + pusher.Push(cx); + } + } + + JSAutoRequest ar(cx); + + JS::Rooted targetCompartmentObject(cx); + if (targetIsWorkerThread) { + targetCompartmentObject = JS::CurrentGlobalOrNull(cx); + } else { + targetCompartmentObject = mWorkerPrivate->GetWrapper(); + } + + Maybe ac; + if (targetCompartmentObject) { + ac.construct(cx, targetCompartmentObject); + } + + bool result = WorkerRun(cx, mWorkerPrivate); + + // In the case of CompileScriptRunnnable, WorkerRun above can cause us to + // lazily create a global, in which case we need to be in its compartment + // when calling PostRun() below. Maybe<> this time... + if (targetIsWorkerThread && + ac.empty() && + js::DefaultObjectForContextOrNull(cx)) { + ac.construct(cx, js::DefaultObjectForContextOrNull(cx)); + } + + PostRun(cx, mWorkerPrivate, result); + + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +WorkerRunnable::Cancel() +{ + uint32_t canceledCount = ++mCanceled; + + MOZ_ASSERT(canceledCount, "Cancel() overflow!"); + + // The docs say that Cancel() should not be called more than once and that we + // should throw NS_ERROR_UNEXPECTED if it is. + return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, + nsIEventTarget* aSyncLoopTarget) +: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mSyncLoopTarget(aSyncLoopTarget) +{ +#ifdef DEBUG + if (mSyncLoopTarget) { + mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); + } +#endif +} + +WorkerSyncRunnable::WorkerSyncRunnable( + WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget) +: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), + mSyncLoopTarget(aSyncLoopTarget) +{ +#ifdef DEBUG + if (mSyncLoopTarget) { + mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); + } +#endif +} + +WorkerSyncRunnable::~WorkerSyncRunnable() +{ +} + +bool +WorkerSyncRunnable::DispatchInternal() +{ + if (mSyncLoopTarget) { + return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL)); + } + + return WorkerRunnable::DispatchInternal(); +} + +void +MainThreadWorkerSyncRunnable::PostDispatch(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) +{ + MaybeReportMainThreadException(aCx, aDispatchResult); +} + +StopSyncLoopRunnable::StopSyncLoopRunnable( + WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget, + bool aResult) +: WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget), mResult(aResult) +{ +#ifdef DEBUG + mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); +#endif +} + +NS_IMETHODIMP +StopSyncLoopRunnable::Cancel() +{ + nsresult rv = Run(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +bool +StopSyncLoopRunnable::WorkerRun(JSContext* aCx, + WorkerPrivate* aWorkerPrivate) +{ + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(mSyncLoopTarget); + + nsCOMPtr syncLoopTarget; + mSyncLoopTarget.swap(syncLoopTarget); + + if (!mResult) { + MaybeSetException(aCx); + } + + aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult); + return true; +} + +bool +StopSyncLoopRunnable::DispatchInternal() +{ + MOZ_ASSERT(mSyncLoopTarget); + + return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void +MainThreadStopSyncLoopRunnable::PostDispatch(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) +{ + MaybeReportMainThreadException(aCx, aDispatchResult); +} + +#ifdef DEBUG +WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior) +: WorkerRunnable(aWorkerPrivate, aBehavior) +{ + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount || + aBehavior == WorkerThreadUnchangedBusyCount, + "WorkerControlRunnables should not modify the busy count"); +} +#endif + +bool +WorkerControlRunnable::DispatchInternal() +{ + if (mBehavior == WorkerThreadUnchangedBusyCount) { + return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(this)); + } + + if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) { + return NS_SUCCEEDED(parent->DispatchControlRunnable(this)); + } + + nsCOMPtr mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + + return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void +MainThreadWorkerControlRunnable::PostDispatch(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) +{ + AssertIsOnMainThread(); + + if (aCx && !aDispatchResult) { + JS_ReportPendingException(aCx); + } +} + +NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable) diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h new file mode 100644 index 000000000000..84acd46f443c --- /dev/null +++ b/dom/workers/WorkerRunnable.h @@ -0,0 +1,319 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_workerrunnable_h__ +#define mozilla_dom_workers_workerrunnable_h__ + +#include "Workers.h" + +#include "nsICancelableRunnable.h" + +#include "mozilla/Atomics.h" +#include "nsISupportsImpl.h" + +class JSContext; +class nsIEventTarget; + +BEGIN_WORKERS_NAMESPACE + +class WorkerPrivate; + +// Use this runnable to communicate from the worker to its parent or vice-versa. +// The busy count must be taken into consideration and declared at construction +// time. +class WorkerRunnable : public nsICancelableRunnable +{ +public: + enum TargetAndBusyBehavior { + // Target the main thread for top-level workers, otherwise target the + // WorkerThread of the worker's parent. No change to the busy count. + ParentThreadUnchangedBusyCount, + + // Target the thread where the worker event loop runs. The busy count will + // be incremented before dispatching and decremented (asynchronously) after + // running. + WorkerThreadModifyBusyCount, + + // Target the thread where the worker event loop runs. The busy count will + // not be modified in any way. Besides worker-internal runnables this is + // almost always the wrong choice. + WorkerThreadUnchangedBusyCount + }; + +protected: + // The WorkerPrivate that this runnable is associated with. + WorkerPrivate* mWorkerPrivate; + + // See above. + TargetAndBusyBehavior mBehavior; + + // It's unclear whether or not Cancel() is supposed to work when called on any + // thread. To be safe we're using an atomic but it's likely overkill. + Atomic mCanceled; + +private: + // Whether or not Cancel() is currently being called from inside the Run() + // method. Avoids infinite recursion when a subclass calls Run() from inside + // Cancel(). Only checked and modified on the target thread. + bool mCallingCancelWithinRun; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + // If you override Cancel() then you'll need to either call the base class + // Cancel() method or override IsCanceled() so that the Run() method bails out + // appropriately. + NS_DECL_NSICANCELABLERUNNABLE + + // Passing a JSContext here is required for the WorkerThreadModifyBusyCount + // behavior. It also guarantees that any failure (false return) will throw an + // exception on the given context. If a context is not passed then failures + // must be dealt with by the caller. + bool + Dispatch(JSContext* aCx); + + // See above note about Cancel(). + virtual bool + IsCanceled() const + { + return mCanceled != 0; + } + + static WorkerRunnable* + FromRunnable(nsIRunnable* aRunnable); + +protected: + WorkerRunnable(WorkerPrivate* aWorkerPrivate, TargetAndBusyBehavior aBehavior) +#ifdef DEBUG + ; +#else + : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0), + mCallingCancelWithinRun(false) + { } +#endif + + // This class is reference counted. + virtual ~WorkerRunnable() + { } + + // By default asserts that Dispatch() is being called on the right thread + // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise). + // Also increments the busy count of |mWorkerPrivate| if targeting the + // WorkerThread. + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate); + + // By default asserts that Dispatch() is being called on the right thread + // (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise). + // Also reports any Dispatch() failures as an exception on |aCx|, and + // busy count if targeting the WorkerThread and Dispatch() failed. + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult); + + // Must be implemented by subclasses. Called on the target thread. + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0; + + // By default asserts that Run() (and WorkerRun()) were called on the correct + // thread. Any failures (false return from WorkerRun) are reported on |aCx|. + // Also sends an asynchronous message to the ParentThread if the busy + // count was previously modified in PreDispatch(). + virtual void + PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult); + + virtual bool + DispatchInternal(); + + // Calling Run() directly is not supported. Just call Dispatch() and + // WorkerRun() will be called on the correct thread automatically. + NS_DECL_NSIRUNNABLE +}; + +// This runnable is used to send a message directly to a worker's sync loop. +class WorkerSyncRunnable : public WorkerRunnable +{ +protected: + nsCOMPtr mSyncLoopTarget; + + // Passing null for aSyncLoopTarget is allowed and will result in the behavior + // of a normal WorkerRunnable. + WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, + nsIEventTarget* aSyncLoopTarget); + + WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget); + + virtual ~WorkerSyncRunnable(); + +private: + virtual bool + DispatchInternal() MOZ_OVERRIDE; +}; + +// This runnable is identical to WorkerSyncRunnable except it is meant to be +// used on the main thread only. +class MainThreadWorkerSyncRunnable : public WorkerSyncRunnable +{ +protected: + // Passing null for aSyncLoopTarget is allowed and will result in the behavior + // of a normal WorkerRunnable. + MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, + nsIEventTarget* aSyncLoopTarget) + : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget) + { + AssertIsOnMainThread(); + } + + MainThreadWorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget) + : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget) + { + AssertIsOnMainThread(); + } + + virtual ~MainThreadWorkerSyncRunnable() + { } + +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + AssertIsOnMainThread(); + return true; + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE; +}; + +// This runnable is used to stop a sync loop . As sync loops keep the busy count +// incremented as long as they run this runnable does not modify the busy count +// in any way. +class StopSyncLoopRunnable : public WorkerSyncRunnable +{ + bool mResult; + +public: + // Passing null for aSyncLoopTarget is not allowed. + StopSyncLoopRunnable(WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget, + bool aResult); + + // By default StopSyncLoopRunnables cannot be canceled since they could leave + // a sync loop spinning forever. + NS_DECL_NSICANCELABLERUNNABLE + +protected: + virtual ~StopSyncLoopRunnable() + { } + + // Called on the worker thread to set an exception on the context if mResult + // is false. Override if you need an exception. + virtual void + MaybeSetException(JSContext* aCx) + { } + +private: + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; + + virtual bool + DispatchInternal() MOZ_OVERRIDE; +}; + +// This runnable is identical to StopSyncLoopRunnable except it is meant to be +// used on the main thread only. +class MainThreadStopSyncLoopRunnable : public StopSyncLoopRunnable +{ +public: + // Passing null for aSyncLoopTarget is not allowed. + MainThreadStopSyncLoopRunnable( + WorkerPrivate* aWorkerPrivate, + already_AddRefed aSyncLoopTarget, + bool aResult) + : StopSyncLoopRunnable(aWorkerPrivate, aSyncLoopTarget, aResult) + { + AssertIsOnMainThread(); + } + +protected: + virtual ~MainThreadStopSyncLoopRunnable() + { } + +private: + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + AssertIsOnMainThread(); + return true; + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE; +}; + +// This runnable is processed as soon as it is received by the worker, +// potentially running before previously queued runnables and perhaps even with +// other JS code executing on the stack. These runnables must not alter the +// state of the JS runtime and should only twiddle state values. The busy count +// is never modified. +class WorkerControlRunnable : public WorkerRunnable +{ + friend class WorkerPrivate; + +protected: + WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, + TargetAndBusyBehavior aBehavior) +#ifdef DEBUG + ; +#else + : WorkerRunnable(aWorkerPrivate, aBehavior) + { } +#endif + + virtual ~WorkerControlRunnable() + { } + +public: + NS_DECL_ISUPPORTS_INHERITED + +private: + virtual bool + DispatchInternal() MOZ_OVERRIDE; + + // Should only be called by WorkerPrivate::DoRunLoop. + using WorkerRunnable::Cancel; +}; + +// A convenience class for WorkerControlRunnables that originate on the main +// thread. +class MainThreadWorkerControlRunnable : public WorkerControlRunnable +{ +protected: + MainThreadWorkerControlRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + { } + + virtual ~MainThreadWorkerControlRunnable() + { } + + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE + { + AssertIsOnMainThread(); + return true; + } + + virtual void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) MOZ_OVERRIDE; +}; + +END_WORKERS_NAMESPACE + +#endif // mozilla_dom_workers_workerrunnable_h__ diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index 5b7c51ead740..2af5a6e3fb77 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -184,39 +184,50 @@ SuspendWorkersForWindow(nsPIDOMWindow* aWindow); void ResumeWorkersForWindow(nsPIDOMWindow* aWindow); -class WorkerTask { +class WorkerTask +{ +protected: + WorkerTask() + { } + + virtual ~WorkerTask() + { } + public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerTask) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerTask) - virtual ~WorkerTask() { } - - virtual bool RunTask(JSContext* aCx) = 0; + virtual bool + RunTask(JSContext* aCx) = 0; }; -class WorkerCrossThreadDispatcher { +class WorkerCrossThreadDispatcher +{ + friend class WorkerPrivate; + + // Must be acquired *before* the WorkerPrivate's mutex, when they're both + // held. + Mutex mMutex; + WorkerPrivate* mWorkerPrivate; + +private: + // Only created by WorkerPrivate. + WorkerCrossThreadDispatcher(WorkerPrivate* aWorkerPrivate); + + // Only called by WorkerPrivate. + void + Forget() + { + MutexAutoLock lock(mMutex); + mWorkerPrivate = nullptr; + } + public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WorkerCrossThreadDispatcher) - WorkerCrossThreadDispatcher(WorkerPrivate* aPrivate) : - mMutex("WorkerCrossThreadDispatcher"), mPrivate(aPrivate) {} - void Forget() - { - mozilla::MutexAutoLock lock(mMutex); - mPrivate = nullptr; - } - - /** - * Generically useful function for running a bit of C++ code on the worker - * thread. - */ - bool PostTask(WorkerTask* aTask); - -protected: - friend class WorkerPrivate; - - // Must be acquired *before* the WorkerPrivate's mutex, when they're both held. - mozilla::Mutex mMutex; - WorkerPrivate* mPrivate; + // Generically useful function for running a bit of C++ code on the worker + // thread. + bool + PostTask(WorkerTask* aTask); }; WorkerCrossThreadDispatcher* diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 2575efa55a63..701974945031 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -14,21 +14,21 @@ #include "nsIXPConnect.h" #include "jsfriendapi.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/Exceptions.h" +#include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsCxPusher.h" #include "nsEventDispatcher.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" -#include "mozilla/dom/Exceptions.h" #include "File.h" #include "RuntimeService.h" #include "WorkerPrivate.h" +#include "WorkerRunnable.h" #include "XMLHttpRequestUpload.h" -#include "mozilla/Attributes.h" -#include "nsComponentManagerUtils.h" - using namespace mozilla; using namespace mozilla::dom; @@ -96,6 +96,8 @@ public: // Only touched on the main thread. nsRefPtr mXHR; nsCOMPtr mXHRUpload; + nsCOMPtr mSyncLoopTarget; + nsCOMPtr mSyncEventResponseTarget; uint32_t mInnerEventStreamId; uint32_t mInnerChannelId; uint32_t mOutstandingSendCount; @@ -114,16 +116,11 @@ public: bool mSeenUploadLoadStart; // Only touched on the main thread. - uint32_t mSyncQueueKey; - uint32_t mSyncEventResponseSyncQueueKey; bool mUploadEventListenersAttached; bool mMainThreadSeenLoadStart; bool mInOpen; public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSIDOMEVENTLISTENER - Proxy(XMLHttpRequest* aXHRPrivate, bool aMozAnon, bool aMozSystem) : mWorkerPrivate(nullptr), mXMLHttpRequestPrivate(aXHRPrivate), mMozAnon(aMozAnon), mMozSystem(aMozSystem), @@ -132,67 +129,15 @@ public: mLastUploadLoaded(0), mLastUploadTotal(0), mIsSyncXHR(false), mLastLengthComputable(false), mLastUploadLengthComputable(false), mSeenLoadStart(false), mSeenUploadLoadStart(false), - mSyncQueueKey(UINT32_MAX), - mSyncEventResponseSyncQueueKey(UINT32_MAX), mUploadEventListenersAttached(false), mMainThreadSeenLoadStart(false), mInOpen(false) { } - ~Proxy() - { - NS_ASSERTION(!mXHR, "Still have an XHR object attached!"); - NS_ASSERTION(!mXHRUpload, "Still have an XHR upload object attached!"); - NS_ASSERTION(!mOutstandingSendCount, "We're dying too early!"); - } + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER bool - Init() - { - AssertIsOnMainThread(); - NS_ASSERTION(mWorkerPrivate, "Must have a worker here!"); - - if (!mXHR) { - nsPIDOMWindow* ownerWindow = mWorkerPrivate->GetWindow(); - if (ownerWindow) { - ownerWindow = ownerWindow->GetOuterWindow(); - if (!ownerWindow) { - NS_ERROR("No outer window?!"); - return false; - } - - nsPIDOMWindow* innerWindow = ownerWindow->GetCurrentInnerWindow(); - if (mWorkerPrivate->GetWindow() != innerWindow) { - NS_WARNING("Window has navigated, cannot create XHR here."); - return false; - } - } - - mXHR = new nsXMLHttpRequest(); - - nsCOMPtr global = do_QueryInterface(ownerWindow); - if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(), - mWorkerPrivate->GetScriptContext(), - global, mWorkerPrivate->GetBaseURI()))) { - mXHR = nullptr; - return false; - } - - mXHR->SetParameters(mMozAnon, mMozSystem); - - if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) { - mXHR = nullptr; - return false; - } - - if (!AddRemoveEventListeners(false, true)) { - mXHRUpload = nullptr; - mXHR = nullptr; - return false; - } - } - - return true; - } + Init(); void Teardown(); @@ -210,28 +155,28 @@ public: } } - uint32_t - GetSyncQueueKey() + already_AddRefed + GetEventTarget() { AssertIsOnMainThread(); - return mSyncEventResponseSyncQueueKey == UINT32_MAX ? - mSyncQueueKey : - mSyncEventResponseSyncQueueKey; + + nsCOMPtr target = mSyncEventResponseTarget ? + mSyncEventResponseTarget : + mSyncLoopTarget; + return target.forget(); } - bool - EventsBypassSyncQueue() +private: + ~Proxy() { - AssertIsOnMainThread(); - - return mSyncQueueKey == UINT32_MAX && - mSyncEventResponseSyncQueueKey == UINT32_MAX; + MOZ_ASSERT(!mXHR); + MOZ_ASSERT(!mXHRUpload); + MOZ_ASSERT(!mOutstandingSendCount); } }; END_WORKERS_NAMESPACE - namespace { inline void @@ -259,7 +204,7 @@ ConvertStringToResponseType(const nsAString& aString) } } - MOZ_CRASH("Don't know anything about this response type!"); + MOZ_ASSUME_UNREACHABLE("Don't know anything about this response type!"); } enum @@ -279,8 +224,8 @@ enum STRING_LAST_EVENTTARGET = STRING_timeout }; -JS_STATIC_ASSERT(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET); -JS_STATIC_ASSERT(STRING_LAST_XHR == STRING_COUNT - 1); +static_assert(STRING_LAST_XHR >= STRING_LAST_EVENTTARGET, "Bad string setup!"); +static_assert(STRING_LAST_XHR == STRING_COUNT - 1, "Bad string setup!"); const char* const sEventStrings[] = { // nsIXMLHttpRequestEventTarget event types, supported by both XHR and Upload. @@ -296,48 +241,42 @@ const char* const sEventStrings[] = { "loadend", }; -JS_STATIC_ASSERT(JS_ARRAY_LENGTH(sEventStrings) == STRING_COUNT); +static_assert(MOZ_ARRAY_LENGTH(sEventStrings) == STRING_COUNT, + "Bad string count!"); -class MainThreadProxyRunnable : public MainThreadSyncRunnable +class MainThreadProxyRunnable : public MainThreadWorkerSyncRunnable { protected: nsRefPtr mProxy; -public: - MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, - ClearingBehavior aClearingBehavior, Proxy* aProxy) - : MainThreadSyncRunnable(aWorkerPrivate, aClearingBehavior, - aProxy->GetSyncQueueKey(), - aProxy->EventsBypassSyncQueue()), + MainThreadProxyRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) + : MainThreadWorkerSyncRunnable(aWorkerPrivate, aProxy->GetEventTarget()), mProxy(aProxy) + { + MOZ_ASSERT(aProxy); + } + + virtual ~MainThreadProxyRunnable() { } }; -class XHRUnpinRunnable : public WorkerControlRunnable +class XHRUnpinRunnable MOZ_FINAL : public MainThreadWorkerControlRunnable { XMLHttpRequest* mXMLHttpRequestPrivate; public: XHRUnpinRunnable(WorkerPrivate* aWorkerPrivate, XMLHttpRequest* aXHRPrivate) - : WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), + : MainThreadWorkerControlRunnable(aWorkerPrivate), mXMLHttpRequestPrivate(aXHRPrivate) + { + MOZ_ASSERT(aXHRPrivate); + } + +private: + ~XHRUnpinRunnable() { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - AssertIsOnMainThread(); - return true; - } - - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - AssertIsOnMainThread(); - } - bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) { @@ -347,18 +286,25 @@ public: } }; -class AsyncTeardownRunnable : public nsRunnable +class AsyncTeardownRunnable MOZ_FINAL : public nsRunnable { nsRefPtr mProxy; public: AsyncTeardownRunnable(Proxy* aProxy) + : mProxy(aProxy) { - mProxy = aProxy; - NS_ASSERTION(mProxy, "Null proxy!"); + MOZ_ASSERT(aProxy); } - NS_IMETHOD Run() + NS_DECL_ISUPPORTS_INHERITED + +private: + ~AsyncTeardownRunnable() + { } + + NS_IMETHOD + Run() MOZ_OVERRIDE { AssertIsOnMainThread(); @@ -369,7 +315,7 @@ public: } }; -class LoadStartDetectionRunnable MOZ_FINAL : public nsIRunnable, +class LoadStartDetectionRunnable MOZ_FINAL : public nsRunnable, public nsIDOMEventListener { WorkerPrivate* mWorkerPrivate; @@ -377,10 +323,10 @@ class LoadStartDetectionRunnable MOZ_FINAL : public nsIRunnable, nsRefPtr mXHR; XMLHttpRequest* mXMLHttpRequestPrivate; nsString mEventType; - bool mReceivedLoadStart; uint32_t mChannelId; + bool mReceivedLoadStart; - class ProxyCompleteRunnable : public MainThreadProxyRunnable + class ProxyCompleteRunnable MOZ_FINAL : public MainThreadProxyRunnable { XMLHttpRequest* mXMLHttpRequestPrivate; uint32_t mChannelId; @@ -388,58 +334,54 @@ class LoadStartDetectionRunnable MOZ_FINAL : public nsIRunnable, public: ProxyCompleteRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, XMLHttpRequest* aXHRPrivate, uint32_t aChannelId) - : MainThreadProxyRunnable(aWorkerPrivate, RunWhenClearing, aProxy), + : MainThreadProxyRunnable(aWorkerPrivate, aProxy), mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(aChannelId) { } - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - AssertIsOnMainThread(); - return true; - } + private: + ~ProxyCompleteRunnable() + { } - void - PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - bool aDispatchResult) - { - AssertIsOnMainThread(); - } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE { if (mChannelId != mProxy->mOuterChannelId) { // Threads raced, this event is now obsolete. return true; } - if (mSyncQueueKey != UINT32_MAX) { - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true); + if (mSyncLoopTarget) { + aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, true); } mXMLHttpRequestPrivate->Unpin(); return true; } + + NS_IMETHOD + Cancel() MOZ_OVERRIDE + { + // This must run! + nsresult rv = MainThreadProxyRunnable::Cancel(); + nsresult rv2 = Run(); + return NS_FAILED(rv) ? rv : rv2; + } }; public: - NS_DECL_ISUPPORTS - LoadStartDetectionRunnable(Proxy* aProxy, XMLHttpRequest* aXHRPrivate) : mWorkerPrivate(aProxy->mWorkerPrivate), mProxy(aProxy), mXHR(aProxy->mXHR), - mXMLHttpRequestPrivate(aXHRPrivate), mReceivedLoadStart(false), - mChannelId(mProxy->mInnerChannelId) + mXMLHttpRequestPrivate(aXHRPrivate), mChannelId(mProxy->mInnerChannelId), + mReceivedLoadStart(false) { AssertIsOnMainThread(); mEventType.AssignWithConversion(sEventStrings[STRING_loadstart]); } - ~LoadStartDetectionRunnable() - { - AssertIsOnMainThread(); - } + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIDOMEVENTLISTENER bool RegisterAndDispatch() @@ -454,63 +396,14 @@ public: return NS_SUCCEEDED(NS_DispatchToCurrentThread(this)); } - NS_IMETHOD - Run() +private: + ~LoadStartDetectionRunnable() { AssertIsOnMainThread(); - - if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) { - NS_WARNING("Failed to remove event listener!"); } - - if (!mReceivedLoadStart) { - if (mProxy->mOutstandingSendCount > 1) { - mProxy->mOutstandingSendCount--; - } else if (mProxy->mOutstandingSendCount == 1) { - mProxy->Reset(); - - nsRefPtr runnable = - new ProxyCompleteRunnable(mWorkerPrivate, mProxy, - mXMLHttpRequestPrivate, - mChannelId); - if (runnable->Dispatch(nullptr)) { - mProxy->mWorkerPrivate = nullptr; - mProxy->mOutstandingSendCount--; - } - } - } - - mProxy = nullptr; - mXHR = nullptr; - mXMLHttpRequestPrivate = nullptr; - return NS_OK; - } - - NS_IMETHOD - HandleEvent(nsIDOMEvent* aEvent) - { - AssertIsOnMainThread(); - -#ifdef DEBUG - { - nsString type; - if (NS_SUCCEEDED(aEvent->GetType(type))) { - NS_ASSERTION(type == mEventType, "Unexpected event type!"); - } - else { - NS_WARNING("Failed to get event type!"); - } - } -#endif - - mReceivedLoadStart = true; - return NS_OK; - } }; -NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener) - -class EventRunnable : public MainThreadProxyRunnable +class EventRunnable MOZ_FINAL : public MainThreadProxyRunnable { nsString mType; nsString mResponseType; @@ -532,79 +425,12 @@ class EventRunnable : public MainThreadProxyRunnable nsresult mResponseResult; public: - EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType, - bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal) - : MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy), - mType(aType), mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal), - mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), - mUploadEvent(aUploadEvent), mProgressEvent(true), - mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK), - mStatusResult(NS_OK), mResponseResult(NS_OK) - { } - - EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType) - : MainThreadProxyRunnable(aProxy->mWorkerPrivate, SkipWhenClearing, aProxy), - mType(aType), mResponse(JSVAL_VOID), mLoaded(0), mTotal(0), - mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), - mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0), - mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK) - { } - - bool - PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - nsRefPtr& xhr = mProxy->mXHR; - NS_ASSERTION(xhr, "Must have an XHR here!"); - - if (NS_FAILED(xhr->GetResponseType(mResponseType))) { - NS_ERROR("This should never fail!"); - } - - mResponseTextResult = xhr->GetResponseText(mResponseText); - if (NS_SUCCEEDED(mResponseTextResult)) { - mResponseResult = mResponseTextResult; - if (mResponseText.IsVoid()) { - mResponse = JSVAL_NULL; - } - } - else { - JS::Rooted response(aCx); - mResponseResult = xhr->GetResponse(aCx, response.address()); - if (NS_SUCCEEDED(mResponseResult)) { - if (JSVAL_IS_UNIVERSAL(response)) { - mResponse = response; - } - else { - // Anything subject to GC must be cloned. - JSStructuredCloneCallbacks* callbacks = - aWorkerPrivate->IsChromeWorker() ? - ChromeWorkerStructuredCloneCallbacks(true) : - WorkerStructuredCloneCallbacks(true); - - nsTArray > clonedObjects; - - if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) { - mClonedObjects.SwapElements(clonedObjects); - } - else { - NS_WARNING("Failed to clone response!"); - mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR; - } - } - } - } - - mStatusResult = xhr->GetStatus(&mStatus); - - xhr->GetStatusText(mStatusText); - - mReadyState = xhr->ReadyState(); - - return true; - } - class StateDataAutoRooter : private JS::CustomAutoRooter { + XMLHttpRequest::StateData* mStateData; + js::SkipRoot mSkip; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + public: explicit StateDataAutoRooter(JSContext* aCx, XMLHttpRequest::StateData* aData MOZ_GUARD_OBJECT_NOTIFIER_PARAM) @@ -619,174 +445,35 @@ public: JS_CallHeapValueTracer(aTrc, &mStateData->mResponse, "XMLHttpRequest::StateData::mResponse"); } - - XMLHttpRequest::StateData* mStateData; - js::SkipRoot mSkip; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) - { - if (mEventStreamId != mProxy->mOuterEventStreamId) { - // Threads raced, this event is now obsolete. - return true; - } + EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType, + bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal) + : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType), + mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal), + mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), + mUploadEvent(aUploadEvent), mProgressEvent(true), + mLengthComputable(aLengthComputable), mResponseTextResult(NS_OK), + mStatusResult(NS_OK), mResponseResult(NS_OK) + { } - if (!mProxy->mXMLHttpRequestPrivate) { - // Object was finalized, bail. - return true; - } + EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType) + : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType), + mResponse(JSVAL_VOID), mLoaded(0), mTotal(0), + mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0), + mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0), + mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK) + { } - if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) { - if (mUploadEvent) { - mProxy->mSeenUploadLoadStart = true; - } - else { - mProxy->mSeenLoadStart = true; - } - } - else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) { - if (mUploadEvent) { - mProxy->mSeenUploadLoadStart = false; - } - else { - mProxy->mSeenLoadStart = false; - } - } - else if (mType.EqualsASCII(sEventStrings[STRING_abort])) { - if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) || - (!mUploadEvent && !mProxy->mSeenLoadStart)) { - // We've already dispatched premature abort events. - return true; - } - } - else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) { - if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) { - // We've already dispatched premature abort events. - return true; - } - } +private: + ~EventRunnable() + { } - if (mProgressEvent) { - // Cache these for premature abort events. - if (mUploadEvent) { - mProxy->mLastUploadLengthComputable = mLengthComputable; - mProxy->mLastUploadLoaded = mLoaded; - mProxy->mLastUploadTotal = mTotal; - } - else { - mProxy->mLastLengthComputable = mLengthComputable; - mProxy->mLastLoaded = mLoaded; - mProxy->mLastTotal = mTotal; - } - } + virtual bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; - nsAutoPtr state(new XMLHttpRequest::StateData()); - StateDataAutoRooter rooter(aCx, state); - - state->mResponseTextResult = mResponseTextResult; - state->mResponseText = mResponseText; - - if (NS_SUCCEEDED(mResponseTextResult)) { - MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse)); - state->mResponseResult = mResponseTextResult; - state->mResponse = mResponse; - } - else { - state->mResponseResult = mResponseResult; - - if (NS_SUCCEEDED(mResponseResult)) { - if (mResponseBuffer.data()) { - MOZ_ASSERT(JSVAL_IS_VOID(mResponse)); - - JSAutoStructuredCloneBuffer responseBuffer; - mResponseBuffer.swap(responseBuffer); - - JSStructuredCloneCallbacks* callbacks = - aWorkerPrivate->IsChromeWorker() ? - ChromeWorkerStructuredCloneCallbacks(false) : - WorkerStructuredCloneCallbacks(false); - - nsTArray > clonedObjects; - clonedObjects.SwapElements(mClonedObjects); - - JS::Rooted response(aCx); - if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { - return false; - } - - state->mResponse = response; - } - else { - state->mResponse = mResponse; - } - } - } - - state->mStatusResult = mStatusResult; - state->mStatus = mStatus; - - state->mStatusText = mStatusText; - - state->mReadyState = mReadyState; - - XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate; - xhr->UpdateState(*state); - - if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) { - return true; - } - - JS::Rooted type(aCx, JS_NewUCStringCopyN(aCx, mType.get(), mType.Length())); - if (!type) { - return false; - } - - nsXHREventTarget* target; - if (mUploadEvent) { - target = xhr->GetUploadObjectNoCreate(); - } - else { - target = xhr; - } - - MOZ_ASSERT(target); - - nsCOMPtr event; - if (mProgressEvent) { - NS_NewDOMProgressEvent(getter_AddRefs(event), target, nullptr, nullptr); - nsCOMPtr progress = do_QueryInterface(event); - - if (progress) { - progress->InitProgressEvent(mType, false, false, mLengthComputable, - mLoaded, mTotal); - } - } - else { - NS_NewDOMEvent(getter_AddRefs(event), target, nullptr, nullptr); - - if (event) { - event->InitEvent(mType, false, false); - } - } - - if (!event) { - return false; - } - - event->SetTrusted(true); - - target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); - - // After firing the event set mResponse to JSVAL_NULL for chunked response - // types. - if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) { - xhr->NullResponseText(); - } - - return true; - } + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; }; class WorkerThreadProxySyncRunnable : public nsRunnable @@ -794,109 +481,96 @@ class WorkerThreadProxySyncRunnable : public nsRunnable protected: WorkerPrivate* mWorkerPrivate; nsRefPtr mProxy; - uint32_t mSyncQueueKey; + nsCOMPtr mSyncLoopTarget; private: - class ResponseRunnable : public MainThreadProxyRunnable + class ResponseRunnable MOZ_FINAL: public MainThreadStopSyncLoopRunnable { - uint32_t mSyncQueueKey; + nsRefPtr mProxy; nsresult mErrorCode; public: ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, - uint32_t aSyncQueueKey, nsresult aErrorCode) - : MainThreadProxyRunnable(aWorkerPrivate, SkipWhenClearing, aProxy), - mSyncQueueKey(aSyncQueueKey), mErrorCode(aErrorCode) + nsresult aErrorCode) + : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(), + NS_SUCCEEDED(aErrorCode)), + mProxy(aProxy), mErrorCode(aErrorCode) { - NS_ASSERTION(aProxy, "Don't hand me a null proxy!"); + MOZ_ASSERT(aProxy); } - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) + private: + ~ResponseRunnable() + { } + + virtual void + MaybeSetException(JSContext* aCx) MOZ_OVERRIDE { - if (NS_FAILED(mErrorCode)) { + MOZ_ASSERT(NS_FAILED(mErrorCode)); + Throw(aCx, mErrorCode); - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, false); } - else { - aWorkerPrivate->StopSyncLoop(mSyncQueueKey, true); - } - - return true; - } }; public: WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) - : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy), mSyncQueueKey(0) + : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy) { - mWorkerPrivate->AssertIsOnWorkerThread(); - NS_ASSERTION(aProxy, "Don't hand me a null proxy!"); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aProxy); + aWorkerPrivate->AssertIsOnWorkerThread(); } + NS_DECL_ISUPPORTS_INHERITED + bool Dispatch(JSContext* aCx) { mWorkerPrivate->AssertIsOnWorkerThread(); AutoSyncLoopHolder syncLoop(mWorkerPrivate); - mSyncQueueKey = syncLoop.SyncQueueKey(); + mSyncLoopTarget = syncLoop.EventTarget(); if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { JS_ReportError(aCx, "Failed to dispatch to main thread!"); return false; } - return syncLoop.RunAndForget(aCx); + return syncLoop.Run(); } +protected: + virtual ~WorkerThreadProxySyncRunnable() + { } + virtual nsresult MainThreadRun() = 0; - NS_IMETHOD - Run() - { - AssertIsOnMainThread(); - - uint32_t oldSyncQueueKey = mProxy->mSyncEventResponseSyncQueueKey; - mProxy->mSyncEventResponseSyncQueueKey = mSyncQueueKey; - - nsresult rv = MainThreadRun(); - - nsRefPtr response = - new ResponseRunnable(mWorkerPrivate, mProxy, mSyncQueueKey, rv); - if (!response->Dispatch(nullptr)) { - NS_WARNING("Failed to dispatch response!"); - } - - mProxy->mSyncEventResponseSyncQueueKey = oldSyncQueueKey; - - return NS_OK; - } +private: + NS_DECL_NSIRUNNABLE }; -class SyncTeardownRunnable : public WorkerThreadProxySyncRunnable +class SyncTeardownRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { public: SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) - { - MOZ_ASSERT(aWorkerPrivate); - MOZ_ASSERT(aProxy); - } + { } + +private: + ~SyncTeardownRunnable() + { } virtual nsresult - MainThreadRun() + MainThreadRun() MOZ_OVERRIDE { - AssertIsOnMainThread(); - mProxy->Teardown(); - return NS_OK; } }; -class SetBackgroundRequestRunnable : public WorkerThreadProxySyncRunnable +class SetBackgroundRequestRunnable MOZ_FINAL : + public WorkerThreadProxySyncRunnable { bool mValue; @@ -906,14 +580,19 @@ public: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) { } - nsresult - MainThreadRun() +private: + ~SetBackgroundRequestRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { return mProxy->mXHR->SetMozBackgroundRequest(mValue); } }; -class SetWithCredentialsRunnable : public WorkerThreadProxySyncRunnable +class SetWithCredentialsRunnable MOZ_FINAL : + public WorkerThreadProxySyncRunnable { bool mValue; @@ -923,14 +602,18 @@ public: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mValue(aValue) { } - nsresult - MainThreadRun() +private: + ~SetWithCredentialsRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { return mProxy->mXHR->SetWithCredentials(mValue); } }; -class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable +class SetResponseTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { nsString mResponseType; @@ -941,8 +624,18 @@ public: mResponseType(aResponseType) { } - nsresult - MainThreadRun() + void + GetResponseType(nsAString& aResponseType) + { + aResponseType.Assign(mResponseType); + } + +private: + ~SetResponseTypeRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { nsresult rv = mProxy->mXHR->SetResponseType(mResponseType); mResponseType.Truncate(); @@ -951,57 +644,46 @@ public: } return rv; } - - void - GetResponseType(nsAString& aResponseType) { - aResponseType.Assign(mResponseType); - } }; -class SetTimeoutRunnable : public WorkerThreadProxySyncRunnable +class SetTimeoutRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { uint32_t mTimeout; public: SetTimeoutRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, uint32_t aTimeout) - : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), - mTimeout(aTimeout) + : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mTimeout(aTimeout) { } - nsresult - MainThreadRun() +private: + ~SetTimeoutRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { return mProxy->mXHR->SetTimeout(mTimeout); } }; -class AbortRunnable : public WorkerThreadProxySyncRunnable +class AbortRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { public: AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy) { } - nsresult - MainThreadRun() - { - mProxy->mInnerEventStreamId++; +private: + ~AbortRunnable() + { } - WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; - mProxy->mWorkerPrivate = mWorkerPrivate; - - mProxy->mXHR->Abort(); - - mProxy->mWorkerPrivate = oldWorker; - - mProxy->Reset(); - - return NS_OK; - } + virtual nsresult + MainThreadRun() MOZ_OVERRIDE; }; -class GetAllResponseHeadersRunnable : public WorkerThreadProxySyncRunnable +class GetAllResponseHeadersRunnable MOZ_FINAL : + public WorkerThreadProxySyncRunnable { nsCString& mResponseHeaders; @@ -1012,15 +694,19 @@ public: mResponseHeaders(aResponseHeaders) { } - nsresult - MainThreadRun() +private: + ~GetAllResponseHeadersRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { mProxy->mXHR->GetAllResponseHeaders(mResponseHeaders); return NS_OK; } }; -class GetResponseHeaderRunnable : public WorkerThreadProxySyncRunnable +class GetResponseHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { const nsCString mHeader; nsCString& mValue; @@ -1032,14 +718,18 @@ public: mValue(aValue) { } - nsresult - MainThreadRun() +private: + ~GetResponseHeaderRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { return mProxy->mXHR->GetResponseHeader(mHeader, mValue); } }; -class OpenRunnable : public WorkerThreadProxySyncRunnable +class OpenRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { nsCString mMethod; nsString mURL; @@ -1059,9 +749,8 @@ public: bool aBackgroundRequest, bool aWithCredentials, uint32_t aTimeout) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMethod(aMethod), - mURL(aURL), - mBackgroundRequest(aBackgroundRequest), mWithCredentials(aWithCredentials), - mTimeout(aTimeout) + mURL(aURL), mBackgroundRequest(aBackgroundRequest), + mWithCredentials(aWithCredentials), mTimeout(aTimeout) { if (aUser.WasPassed()) { mUserStr = aUser.Value(); @@ -1073,8 +762,12 @@ public: } } - nsresult - MainThreadRun() +private: + ~OpenRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; mProxy->mWorkerPrivate = mWorkerPrivate; @@ -1086,148 +779,39 @@ public: } nsresult - MainThreadRunInternal() - { - if (!mProxy->Init()) { - return NS_ERROR_DOM_INVALID_STATE_ERR; - } - - nsresult rv; - - if (mBackgroundRequest) { - rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest); - NS_ENSURE_SUCCESS(rv, rv); - } - - if (mWithCredentials) { - rv = mProxy->mXHR->SetWithCredentials(mWithCredentials); - NS_ENSURE_SUCCESS(rv, rv); - } - - if (mTimeout) { - rv = mProxy->mXHR->SetTimeout(mTimeout); - NS_ENSURE_SUCCESS(rv, rv); - } - - NS_ASSERTION(!mProxy->mInOpen, "Reentrancy is bad!"); - mProxy->mInOpen = true; - - ErrorResult rv2; - mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2); - - NS_ASSERTION(mProxy->mInOpen, "Reentrancy is bad!"); - mProxy->mInOpen = false; - - if (rv2.Failed()) { - return rv2.ErrorCode(); - } - - rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text")); - - return rv; - } + MainThreadRunInternal(); }; -class SendRunnable : public WorkerThreadProxySyncRunnable +class SendRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { nsString mStringBody; JSAutoStructuredCloneBuffer mBody; nsTArray > mClonedObjects; - uint32_t mSyncQueueKey; + nsCOMPtr mSyncLoopTarget; bool mHasUploadListeners; public: SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy, const nsAString& aStringBody, JSAutoStructuredCloneBuffer& aBody, nsTArray >& aClonedObjects, - uint32_t aSyncQueueKey, bool aHasUploadListeners) + nsIEventTarget* aSyncLoopTarget, bool aHasUploadListeners) : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), - mStringBody(aStringBody), mSyncQueueKey(aSyncQueueKey), + mStringBody(aStringBody), mSyncLoopTarget(aSyncLoopTarget), mHasUploadListeners(aHasUploadListeners) { mBody.swap(aBody); mClonedObjects.SwapElements(aClonedObjects); } - nsresult - MainThreadRun() - { - nsCOMPtr variant; +private: + ~SendRunnable() + { } - if (mBody.data()) { - AutoSafeJSContext cx; - JSAutoRequest ar(cx); - nsIXPConnect* xpc = nsContentUtils::XPConnect(); - NS_ASSERTION(xpc, "This should never be null!"); - - nsresult rv = NS_OK; - - JSStructuredCloneCallbacks* callbacks = - mWorkerPrivate->IsChromeWorker() ? - ChromeWorkerStructuredCloneCallbacks(true) : - WorkerStructuredCloneCallbacks(true); - - JS::Rooted body(cx); - if (mBody.read(cx, &body, callbacks, &mClonedObjects)) { - if (NS_FAILED(xpc->JSValToVariant(cx, body.address(), - getter_AddRefs(variant)))) { - rv = NS_ERROR_DOM_INVALID_STATE_ERR; - } - } - else { - rv = NS_ERROR_DOM_DATA_CLONE_ERR; - } - - mBody.clear(); - mClonedObjects.Clear(); - - NS_ENSURE_SUCCESS(rv, rv); - } - else { - nsCOMPtr wvariant = - do_CreateInstance(NS_VARIANT_CONTRACTID); - NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED); - - if (NS_FAILED(wvariant->SetAsAString(mStringBody))) { - NS_ERROR("This should never fail!"); - } - - variant = wvariant; - } - - NS_ASSERTION(!mProxy->mWorkerPrivate, "Should be null!"); - mProxy->mWorkerPrivate = mWorkerPrivate; - - NS_ASSERTION(mProxy->mSyncQueueKey == UINT32_MAX, "Should be unset!"); - mProxy->mSyncQueueKey = mSyncQueueKey; - - if (mHasUploadListeners) { - NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); - if (!mProxy->AddRemoveEventListeners(true, true)) { - NS_ERROR("This should never fail!"); - } - } - - mProxy->mInnerChannelId++; - - nsresult rv = mProxy->mXHR->Send(variant); - - if (NS_SUCCEEDED(rv)) { - mProxy->mOutstandingSendCount++; - - if (!mHasUploadListeners) { - NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); - if (!mProxy->AddRemoveEventListeners(true, true)) { - NS_ERROR("This should never fail!"); - } - } - } - - return rv; - } + virtual nsresult + MainThreadRun() MOZ_OVERRIDE; }; -class SetRequestHeaderRunnable : public WorkerThreadProxySyncRunnable +class SetRequestHeaderRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { nsCString mHeader; nsCString mValue; @@ -1239,14 +823,18 @@ public: mValue(aValue) { } - nsresult - MainThreadRun() +private: + ~SetRequestHeaderRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { return mProxy->mXHR->SetRequestHeader(mHeader, mValue); } }; -class OverrideMimeTypeRunnable : public WorkerThreadProxySyncRunnable +class OverrideMimeTypeRunnable MOZ_FINAL : public WorkerThreadProxySyncRunnable { nsString mMimeType; @@ -1256,8 +844,12 @@ public: : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy), mMimeType(aMimeType) { } - nsresult - MainThreadRun() +private: + ~OverrideMimeTypeRunnable() + { } + + virtual nsresult + MainThreadRun() MOZ_OVERRIDE { mProxy->mXHR->OverrideMimeType(mMimeType); return NS_OK; @@ -1266,6 +858,8 @@ public: class AutoUnpinXHR { + XMLHttpRequest* mXMLHttpRequestPrivate; + public: AutoUnpinXHR(XMLHttpRequest* aXMLHttpRequestPrivate) : mXMLHttpRequestPrivate(aXMLHttpRequestPrivate) @@ -1284,13 +878,61 @@ public: { mXMLHttpRequestPrivate = nullptr; } - -private: - XMLHttpRequest* mXMLHttpRequestPrivate; }; } // anonymous namespace +bool +Proxy::Init() +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mWorkerPrivate); + + if (mXHR) { + return true; + } + + nsPIDOMWindow* ownerWindow = mWorkerPrivate->GetWindow(); + if (ownerWindow) { + ownerWindow = ownerWindow->GetOuterWindow(); + if (!ownerWindow) { + NS_ERROR("No outer window?!"); + return false; + } + + nsPIDOMWindow* innerWindow = ownerWindow->GetCurrentInnerWindow(); + if (mWorkerPrivate->GetWindow() != innerWindow) { + NS_WARNING("Window has navigated, cannot create XHR here."); + return false; + } + } + + mXHR = new nsXMLHttpRequest(); + + nsCOMPtr global = do_QueryInterface(ownerWindow); + if (NS_FAILED(mXHR->Init(mWorkerPrivate->GetPrincipal(), + mWorkerPrivate->GetScriptContext(), + global, mWorkerPrivate->GetBaseURI()))) { + mXHR = nullptr; + return false; + } + + mXHR->SetParameters(mMozAnon, mMozSystem); + + if (NS_FAILED(mXHR->GetUpload(getter_AddRefs(mXHRUpload)))) { + mXHR = nullptr; + return false; + } + + if (!AddRemoveEventListeners(false, true)) { + mXHRUpload = nullptr; + mXHR = nullptr; + return false; + } + + return true; +} + void Proxy::Teardown() { @@ -1437,6 +1079,443 @@ Proxy::HandleEvent(nsIDOMEvent* aEvent) return NS_OK; } +NS_IMPL_ISUPPORTS_INHERITED0(WorkerThreadProxySyncRunnable, nsRunnable) + +NS_IMPL_ISUPPORTS_INHERITED0(AsyncTeardownRunnable, nsRunnable) + +NS_IMPL_ISUPPORTS_INHERITED1(LoadStartDetectionRunnable, nsRunnable, + nsIDOMEventListener) + +NS_IMETHODIMP +LoadStartDetectionRunnable::Run() +{ + AssertIsOnMainThread(); + + if (NS_FAILED(mXHR->RemoveEventListener(mEventType, this, false))) { + NS_WARNING("Failed to remove event listener!"); + } + + if (!mReceivedLoadStart) { + if (mProxy->mOutstandingSendCount > 1) { + mProxy->mOutstandingSendCount--; + } else if (mProxy->mOutstandingSendCount == 1) { + mProxy->Reset(); + + nsRefPtr runnable = + new ProxyCompleteRunnable(mWorkerPrivate, mProxy, + mXMLHttpRequestPrivate, mChannelId); + if (runnable->Dispatch(nullptr)) { + mProxy->mWorkerPrivate = nullptr; + mProxy->mOutstandingSendCount--; + } + } + } + + mProxy = nullptr; + mXHR = nullptr; + mXMLHttpRequestPrivate = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +LoadStartDetectionRunnable::HandleEvent(nsIDOMEvent* aEvent) +{ + AssertIsOnMainThread(); + +#ifdef DEBUG + { + nsString type; + if (NS_SUCCEEDED(aEvent->GetType(type))) { + MOZ_ASSERT(type == mEventType); + } + else { + NS_WARNING("Failed to get event type!"); + } + } +#endif + + mReceivedLoadStart = true; + return NS_OK; +} + +bool +EventRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +{ + AssertIsOnMainThread(); + + nsRefPtr& xhr = mProxy->mXHR; + MOZ_ASSERT(xhr); + + if (NS_FAILED(xhr->GetResponseType(mResponseType))) { + MOZ_ASSERT(false, "This should never fail!"); + } + + mResponseTextResult = xhr->GetResponseText(mResponseText); + if (NS_SUCCEEDED(mResponseTextResult)) { + mResponseResult = mResponseTextResult; + if (mResponseText.IsVoid()) { + mResponse = JSVAL_NULL; + } + } + else { + JS::Rooted response(aCx); + mResponseResult = xhr->GetResponse(aCx, response.address()); + if (NS_SUCCEEDED(mResponseResult)) { + if (JSVAL_IS_UNIVERSAL(response)) { + mResponse = response; + } + else { + // Anything subject to GC must be cloned. + JSStructuredCloneCallbacks* callbacks = + aWorkerPrivate->IsChromeWorker() ? + ChromeWorkerStructuredCloneCallbacks(true) : + WorkerStructuredCloneCallbacks(true); + + nsTArray > clonedObjects; + + if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) { + mClonedObjects.SwapElements(clonedObjects); + } + else { + NS_WARNING("Failed to clone response!"); + mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR; + } + } + } + } + + mStatusResult = xhr->GetStatus(&mStatus); + + xhr->GetStatusText(mStatusText); + + mReadyState = xhr->ReadyState(); + + return true; +} + +bool +EventRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +{ + if (mEventStreamId != mProxy->mOuterEventStreamId) { + // Threads raced, this event is now obsolete. + return true; + } + + if (!mProxy->mXMLHttpRequestPrivate) { + // Object was finalized, bail. + return true; + } + + if (mType.EqualsASCII(sEventStrings[STRING_loadstart])) { + if (mUploadEvent) { + mProxy->mSeenUploadLoadStart = true; + } + else { + mProxy->mSeenLoadStart = true; + } + } + else if (mType.EqualsASCII(sEventStrings[STRING_loadend])) { + if (mUploadEvent) { + mProxy->mSeenUploadLoadStart = false; + } + else { + mProxy->mSeenLoadStart = false; + } + } + else if (mType.EqualsASCII(sEventStrings[STRING_abort])) { + if ((mUploadEvent && !mProxy->mSeenUploadLoadStart) || + (!mUploadEvent && !mProxy->mSeenLoadStart)) { + // We've already dispatched premature abort events. + return true; + } + } + else if (mType.EqualsASCII(sEventStrings[STRING_readystatechange])) { + if (mReadyState == 4 && !mUploadEvent && !mProxy->mSeenLoadStart) { + // We've already dispatched premature abort events. + return true; + } + } + + if (mProgressEvent) { + // Cache these for premature abort events. + if (mUploadEvent) { + mProxy->mLastUploadLengthComputable = mLengthComputable; + mProxy->mLastUploadLoaded = mLoaded; + mProxy->mLastUploadTotal = mTotal; + } + else { + mProxy->mLastLengthComputable = mLengthComputable; + mProxy->mLastLoaded = mLoaded; + mProxy->mLastTotal = mTotal; + } + } + + nsAutoPtr state(new XMLHttpRequest::StateData()); + StateDataAutoRooter rooter(aCx, state); + + state->mResponseTextResult = mResponseTextResult; + state->mResponseText = mResponseText; + + if (NS_SUCCEEDED(mResponseTextResult)) { + MOZ_ASSERT(JSVAL_IS_VOID(mResponse) || JSVAL_IS_NULL(mResponse)); + state->mResponseResult = mResponseTextResult; + state->mResponse = mResponse; + } + else { + state->mResponseResult = mResponseResult; + + if (NS_SUCCEEDED(mResponseResult)) { + if (mResponseBuffer.data()) { + MOZ_ASSERT(JSVAL_IS_VOID(mResponse)); + + JSAutoStructuredCloneBuffer responseBuffer; + mResponseBuffer.swap(responseBuffer); + + JSStructuredCloneCallbacks* callbacks = + aWorkerPrivate->IsChromeWorker() ? + ChromeWorkerStructuredCloneCallbacks(false) : + WorkerStructuredCloneCallbacks(false); + + nsTArray > clonedObjects; + clonedObjects.SwapElements(mClonedObjects); + + JS::Rooted response(aCx); + if (!responseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { + return false; + } + + state->mResponse = response; + } + else { + state->mResponse = mResponse; + } + } + } + + state->mStatusResult = mStatusResult; + state->mStatus = mStatus; + + state->mStatusText = mStatusText; + + state->mReadyState = mReadyState; + + XMLHttpRequest* xhr = mProxy->mXMLHttpRequestPrivate; + xhr->UpdateState(*state); + + if (mUploadEvent && !xhr->GetUploadObjectNoCreate()) { + return true; + } + + JS::Rooted type(aCx, + JS_NewUCStringCopyN(aCx, mType.get(), mType.Length())); + if (!type) { + return false; + } + + nsXHREventTarget* target; + if (mUploadEvent) { + target = xhr->GetUploadObjectNoCreate(); + } + else { + target = xhr; + } + + MOZ_ASSERT(target); + + nsCOMPtr event; + if (mProgressEvent) { + NS_NewDOMProgressEvent(getter_AddRefs(event), target, nullptr, nullptr); + nsCOMPtr progress = do_QueryInterface(event); + + if (progress) { + progress->InitProgressEvent(mType, false, false, mLengthComputable, + mLoaded, mTotal); + } + } + else { + NS_NewDOMEvent(getter_AddRefs(event), target, nullptr, nullptr); + + if (event) { + event->InitEvent(mType, false, false); + } + } + + if (!event) { + return false; + } + + event->SetTrusted(true); + + target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); + + // After firing the event set mResponse to JSVAL_NULL for chunked response + // types. + if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) { + xhr->NullResponseText(); + } + + return true; +} + +NS_IMETHODIMP +WorkerThreadProxySyncRunnable::Run() +{ + AssertIsOnMainThread(); + + nsCOMPtr tempTarget; + mSyncLoopTarget.swap(tempTarget); + + mProxy->mSyncEventResponseTarget.swap(tempTarget); + + nsresult rv = MainThreadRun(); + + nsRefPtr response = + new ResponseRunnable(mWorkerPrivate, mProxy, rv); + if (!response->Dispatch(nullptr)) { + MOZ_ASSERT(false, "Failed to dispatch response!"); + } + + mProxy->mSyncEventResponseTarget.swap(tempTarget); + + return NS_OK; +} + +nsresult +AbortRunnable::MainThreadRun() +{ + mProxy->mInnerEventStreamId++; + + WorkerPrivate* oldWorker = mProxy->mWorkerPrivate; + mProxy->mWorkerPrivate = mWorkerPrivate; + + mProxy->mXHR->Abort(); + + mProxy->mWorkerPrivate = oldWorker; + + mProxy->Reset(); + + return NS_OK; +} + +nsresult +OpenRunnable::MainThreadRunInternal() +{ + if (!mProxy->Init()) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsresult rv; + + if (mBackgroundRequest) { + rv = mProxy->mXHR->SetMozBackgroundRequest(mBackgroundRequest); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mWithCredentials) { + rv = mProxy->mXHR->SetWithCredentials(mWithCredentials); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mTimeout) { + rv = mProxy->mXHR->SetTimeout(mTimeout); + NS_ENSURE_SUCCESS(rv, rv); + } + + MOZ_ASSERT(!mProxy->mInOpen); + mProxy->mInOpen = true; + + ErrorResult rv2; + mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, rv2); + + MOZ_ASSERT(mProxy->mInOpen); + mProxy->mInOpen = false; + + if (rv2.Failed()) { + return rv2.ErrorCode(); + } + + return mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text")); +} + + +nsresult +SendRunnable::MainThreadRun() +{ + nsCOMPtr variant; + + if (mBody.data()) { + AutoSafeJSContext cx; + JSAutoRequest ar(cx); + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + MOZ_ASSERT(xpc); + + nsresult rv = NS_OK; + + JSStructuredCloneCallbacks* callbacks = + mWorkerPrivate->IsChromeWorker() ? + ChromeWorkerStructuredCloneCallbacks(true) : + WorkerStructuredCloneCallbacks(true); + + JS::Rooted body(cx); + if (mBody.read(cx, &body, callbacks, &mClonedObjects)) { + if (NS_FAILED(xpc->JSValToVariant(cx, body.address(), + getter_AddRefs(variant)))) { + rv = NS_ERROR_DOM_INVALID_STATE_ERR; + } + } + else { + rv = NS_ERROR_DOM_DATA_CLONE_ERR; + } + + mBody.clear(); + mClonedObjects.Clear(); + + NS_ENSURE_SUCCESS(rv, rv); + } + else { + nsCOMPtr wvariant = + do_CreateInstance(NS_VARIANT_CONTRACTID); + NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED); + + if (NS_FAILED(wvariant->SetAsAString(mStringBody))) { + MOZ_ASSERT(false, "This should never fail!"); + } + + variant = wvariant; + } + + MOZ_ASSERT(!mProxy->mWorkerPrivate); + mProxy->mWorkerPrivate = mWorkerPrivate; + + MOZ_ASSERT(!mProxy->mSyncLoopTarget); + mProxy->mSyncLoopTarget.swap(mSyncLoopTarget); + + if (mHasUploadListeners) { + NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); + if (!mProxy->AddRemoveEventListeners(true, true)) { + MOZ_ASSERT(false, "This should never fail!"); + } + } + + mProxy->mInnerChannelId++; + + nsresult rv = mProxy->mXHR->Send(variant); + + if (NS_SUCCEEDED(rv)) { + mProxy->mOutstandingSendCount++; + + if (!mHasUploadListeners) { + NS_ASSERTION(!mProxy->mUploadEventListenersAttached, "Huh?!"); + if (!mProxy->AddRemoveEventListeners(true, true)) { + MOZ_ASSERT(false, "This should never fail!"); + } + } + } + + return rv; +} + XMLHttpRequest::XMLHttpRequest(WorkerPrivate* aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate), mResponseType(XMLHttpRequestResponseType::Text), mTimeout(0), @@ -1703,11 +1782,11 @@ XMLHttpRequest::SendInternal(const nsAString& aStringBody, AutoUnpinXHR autoUnpin(this); Maybe autoSyncLoop; - uint32_t syncQueueKey = UINT32_MAX; + nsCOMPtr syncLoopTarget; bool isSyncXHR = mProxy->mIsSyncXHR; if (isSyncXHR) { autoSyncLoop.construct(mWorkerPrivate); - syncQueueKey = autoSyncLoop.ref().SyncQueueKey(); + syncLoopTarget = autoSyncLoop.ref().EventTarget(); } mProxy->mOuterChannelId++; @@ -1716,7 +1795,7 @@ XMLHttpRequest::SendInternal(const nsAString& aStringBody, nsRefPtr runnable = new SendRunnable(mWorkerPrivate, mProxy, aStringBody, aBody, - aClonedObjects, syncQueueKey, hasUploadListeners); + aClonedObjects, syncLoopTarget, hasUploadListeners); if (!runnable->Dispatch(cx)) { aRv.Throw(NS_ERROR_FAILURE); return; @@ -1738,7 +1817,7 @@ XMLHttpRequest::SendInternal(const nsAString& aStringBody, autoUnpin.Clear(); - if (!autoSyncLoop.ref().RunAndForget(cx)) { + if (!autoSyncLoop.ref().Run()) { aRv.Throw(NS_ERROR_FAILURE); } } diff --git a/dom/workers/XMLHttpRequest.h b/dom/workers/XMLHttpRequest.h index c3aff3b8ae3a..8206c337178a 100644 --- a/dom/workers/XMLHttpRequest.h +++ b/dom/workers/XMLHttpRequest.h @@ -22,8 +22,8 @@ class Proxy; class XMLHttpRequestUpload; class WorkerPrivate; -class XMLHttpRequest : public nsXHREventTarget, - public WorkerFeature +class XMLHttpRequest MOZ_FINAL: public nsXHREventTarget, + public WorkerFeature { public: struct StateData @@ -61,10 +61,6 @@ private: bool mMozAnon; bool mMozSystem; -protected: - XMLHttpRequest(WorkerPrivate* aWorkerPrivate); - virtual ~XMLHttpRequest(); - public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_OVERRIDE; @@ -272,6 +268,9 @@ public: } private: + XMLHttpRequest(WorkerPrivate* aWorkerPrivate); + ~XMLHttpRequest(); + enum ReleaseType { Default, XHRIsGoingAway, WorkerIsGoingAway }; void diff --git a/dom/workers/moz.build b/dom/workers/moz.build index bc43618166e8..584291d8fadd 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -9,6 +9,7 @@ TEST_DIRS += ['test'] # Public stuff. EXPORTS.mozilla.dom += [ 'WorkerPrivate.h', + 'WorkerRunnable.h', 'WorkerScope.h', ] @@ -43,6 +44,7 @@ SOURCES += [ 'SharedWorker.cpp', 'URL.cpp', 'WorkerPrivate.cpp', + 'WorkerRunnable.cpp', 'WorkerScope.cpp', 'XMLHttpRequest.cpp', 'XMLHttpRequestUpload.cpp', @@ -58,6 +60,7 @@ LOCAL_INCLUDES += [ '/content/base/src', '/content/events/src', '/xpcom/build', + '/xpcom/threads', ] include('/ipc/chromium/chromium-config.mozbuild') diff --git a/gfx/2d/StackArray.h b/gfx/2d/StackArray.h new file mode 100644 index 000000000000..e3c2684a0bf2 --- /dev/null +++ b/gfx/2d/StackArray.h @@ -0,0 +1,30 @@ +/* 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/. */ + +/* A handy class that will allocate data for size*T objects on the stack and + * otherwise allocate them on the heap. It is similar in purpose to nsAutoTArray */ + +template +class StackArray +{ +public: + StackArray(size_t count) { + if (count > size) { + mData = new T[count]; + } else { + mData = mStackData; + } + } + ~StackArray() { + if (mData != mStackData) { + delete[] mData; + } + } + T& operator[](size_t n) { return mData[n]; } + const T& operator[](size_t n) const { return mData[n]; } + T* data() { return mData; }; +private: + T mStackData[size]; + T* mData; +}; diff --git a/gfx/layers/YCbCrImageDataSerializer.cpp b/gfx/layers/YCbCrImageDataSerializer.cpp index ed171224f016..99fb2e7f852d 100644 --- a/gfx/layers/YCbCrImageDataSerializer.cpp +++ b/gfx/layers/YCbCrImageDataSerializer.cpp @@ -153,17 +153,18 @@ YCbCrImageDataSerializer::ComputeMinBufferSize(uint32_t aSize) } void -YCbCrImageDataSerializer::InitializeBufferInfo(const gfx::IntSize& aYSize, +YCbCrImageDataSerializer::InitializeBufferInfo(uint32_t aYOffset, + uint32_t aCbOffset, + uint32_t aCrOffset, + const gfx::IntSize& aYSize, const gfx::IntSize& aCbCrSize, StereoMode aStereoMode) { YCbCrBufferInfo* info = GetYCbCrBufferInfo(mData); - info->mYOffset = MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo)); - info->mCbOffset = info->mYOffset - + MOZ_ALIGN_WORD(aYSize.width * aYSize.height); - info->mCrOffset = info->mCbOffset - + MOZ_ALIGN_WORD(aCbCrSize.width * aCbCrSize.height); - + uint32_t info_size = MOZ_ALIGN_WORD(sizeof(YCbCrBufferInfo)); + info->mYOffset = info_size + aYOffset; + info->mCbOffset = info_size + aCbOffset; + info->mCrOffset = info_size + aCrOffset; info->mYWidth = aYSize.width; info->mYHeight = aYSize.height; info->mCbCrWidth = aCbCrSize.width; @@ -171,6 +172,17 @@ YCbCrImageDataSerializer::InitializeBufferInfo(const gfx::IntSize& aYSize, info->mStereoMode = aStereoMode; } +void +YCbCrImageDataSerializer::InitializeBufferInfo(const gfx::IntSize& aYSize, + const gfx::IntSize& aCbCrSize, + StereoMode aStereoMode) +{ + uint32_t yOffset = 0; + uint32_t cbOffset = yOffset + MOZ_ALIGN_WORD(aYSize.width * aYSize.height); + uint32_t crOffset = cbOffset + MOZ_ALIGN_WORD(aCbCrSize.width * aCbCrSize.height); + return InitializeBufferInfo(yOffset, cbOffset, crOffset, aYSize, aCbCrSize, aStereoMode); +} + void YCbCrImageDataSerializer::InitializeBufferInfo(const gfxIntSize& aYSize, const gfxIntSize& aCbCrSize, diff --git a/gfx/layers/YCbCrImageDataSerializer.h b/gfx/layers/YCbCrImageDataSerializer.h index 1ec52ed00ee7..e6b14cdeebf6 100644 --- a/gfx/layers/YCbCrImageDataSerializer.h +++ b/gfx/layers/YCbCrImageDataSerializer.h @@ -114,6 +114,12 @@ public: * The provided pointer should point to the beginning of the (chunk of) * buffer on which we want to store the image. */ + void InitializeBufferInfo(uint32_t aYOffset, + uint32_t aCbOffset, + uint32_t aCrOffset, + const gfx::IntSize& aYSize, + const gfx::IntSize& aCbCrSize, + StereoMode aStereoMode); void InitializeBufferInfo(const gfx::IntSize& aYSize, const gfx::IntSize& aCbCrSize, StereoMode aStereoMode); diff --git a/gfx/layers/ipc/AsyncPanZoomController.cpp b/gfx/layers/ipc/AsyncPanZoomController.cpp index bd0a4c608a86..8226ca658db5 100644 --- a/gfx/layers/ipc/AsyncPanZoomController.cpp +++ b/gfx/layers/ipc/AsyncPanZoomController.cpp @@ -1213,21 +1213,6 @@ const CSSRect AsyncPanZoomController::CalculatePendingDisplayPort( scrollOffset.y = scrollableRect.y; } - // FIXME/bug 936500: Make sure the displayport contains the composition - // bounds. This is to work around a layout bug that means if a display item's - // corresponding displayport doesn't contain its frame's bounds, it may get - // optimised out and the layer won't get created. - if (displayPort.x + displayPort.width < compositionBounds.width) { - displayPort.x = -(displayPort.width - compositionBounds.width); - } else if (displayPort.x > 0) { - displayPort.x = 0; - } - if (displayPort.y + displayPort.height < compositionBounds.height) { - displayPort.y = -(displayPort.height - compositionBounds.height); - } else if (displayPort.y > 0) { - displayPort.y = 0; - } - CSSRect shiftedDisplayPort = displayPort + scrollOffset; return scrollableRect.ClampRect(shiftedDisplayPort) - scrollOffset; } diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp index f90226221072..a4b7ebe1c5e6 100644 --- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp @@ -143,8 +143,21 @@ SharedPlanarYCbCrImage::SetDataNoCopy(const Data &aData) { mData = aData; mSize = aData.mPicSize; + /* SetDataNoCopy is used to update YUV plane offsets without (re)allocating + * memory previously allocated with AllocateAndGetNewBuffer(). + * serializer.GetData() returns the address of the memory previously allocated + * with AllocateAndGetNewBuffer(), that we subtract from the Y, Cb, Cr + * channels to compute 0-based offsets to pass to InitializeBufferInfo. + */ YCbCrImageDataSerializer serializer(mTextureClient->GetBuffer()); - serializer.InitializeBufferInfo(aData.mYSize, + uint8_t *base = serializer.GetData(); + uint32_t yOffset = aData.mYChannel - base; + uint32_t cbOffset = aData.mCbChannel - base; + uint32_t crOffset = aData.mCrChannel - base; + serializer.InitializeBufferInfo(yOffset, + cbOffset, + crOffset, + aData.mYSize, aData.mCbCrSize, aData.mStereoMode); } diff --git a/image/decoders/EXIF.cpp b/image/decoders/EXIF.cpp index 1c7abd237f94..42296cfb3c92 100644 --- a/image/decoders/EXIF.cpp +++ b/image/decoders/EXIF.cpp @@ -78,16 +78,13 @@ bool EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut) { // Determine byte order. - if (MatchString("MM", 2)) + if (MatchString("MM\0*", 4)) mByteOrder = ByteOrder::BigEndian; - else if (MatchString("II", 2)) + else if (MatchString("II*\0", 4)) mByteOrder = ByteOrder::LittleEndian; else return false; - if (!MatchString("\0*", 2)) - return false; - // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which // is the maximum size of the entry APP1 segment.) uint32_t ifd0Offset; diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index da65a0eba838..c4b9b602ad08 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -397,7 +397,7 @@ IntlInitialize(JSContext *cx, HandleObject obj, Handle initialize HandleValue locales, HandleValue options) { RootedValue initializerValue(cx); - if (!cx->global()->getIntrinsicValue(cx, initializer, &initializerValue)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), initializer, &initializerValue)) return false; JS_ASSERT(initializerValue.isObject()); JS_ASSERT(initializerValue.toObject().is()); @@ -463,7 +463,7 @@ static bool GetInternals(JSContext *cx, HandleObject obj, MutableHandleObject internals) { RootedValue getInternalsValue(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().getInternals, &getInternalsValue)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().getInternals, &getInternalsValue)) return false; JS_ASSERT(getInternalsValue.isObject()); JS_ASSERT(getInternalsValue.toObject().is()); @@ -690,7 +690,7 @@ InitCollatorClass(JSContext *cx, HandleObject Intl, Handle global * passing to methods like Array.prototype.sort). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().CollatorCompareGet, &getter)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().CollatorCompareGet, &getter)) return nullptr; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().compare, undefinedValue, @@ -1178,7 +1178,7 @@ InitNumberFormatClass(JSContext *cx, HandleObject Intl, Handle gl * for passing to methods like Array.prototype.map). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().NumberFormatFormatGet, &getter)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().NumberFormatFormatGet, &getter)) return nullptr; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue, @@ -1635,7 +1635,7 @@ InitDateTimeFormatClass(JSContext *cx, HandleObject Intl, Handle * (suitable for passing to methods like Array.prototype.map). */ RootedValue getter(cx); - if (!cx->global()->getIntrinsicValue(cx, cx->names().DateTimeFormatFormatGet, &getter)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter)) return nullptr; RootedValue undefinedValue(cx, UndefinedValue()); if (!JSObject::defineProperty(cx, proto, cx->names().format, undefinedValue, diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 7108177f385b..51f39034c3e5 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -709,18 +709,18 @@ AllLocalsAliased(StaticBlockObject &obj) #endif static bool -ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, StaticBlockObject &blockObj) +ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle blockObj) { - uint32_t depthPlusFixed = blockObj.stackDepth(); + uint32_t depthPlusFixed = blockObj->stackDepth(); if (!AdjustBlockSlot(cx, bce, &depthPlusFixed)) return false; - for (unsigned i = 0; i < blockObj.slotCount(); i++) { - Definition *dn = blockObj.maybeDefinitionParseNode(i); + for (unsigned i = 0; i < blockObj->slotCount(); i++) { + Definition *dn = blockObj->maybeDefinitionParseNode(i); /* Beware the empty destructuring dummy. */ if (!dn) { - blockObj.setAliased(i, bce->sc->allLocalsAliased()); + blockObj->setAliased(i, bce->sc->allLocalsAliased()); continue; } @@ -738,10 +738,10 @@ ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, StaticBlockObjec } #endif - blockObj.setAliased(i, bce->isAliasedName(dn)); + blockObj->setAliased(i, bce->isAliasedName(dn)); } - JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(blockObj)); + JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(*blockObj)); return true; } @@ -809,18 +809,18 @@ EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, O parent = stmt->blockScopeIndex; } - StaticBlockObject &blockObj = objbox->object->as(); + Rooted blockObj(cx, &objbox->object->as()); uint32_t scopeObjectIndex = bce->objectList.add(objbox); - int depth = bce->stackDepth - (blockObj.slotCount() + extraSlots); + int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots); JS_ASSERT(depth >= 0); - blockObj.setStackDepth(depth); + blockObj->setStackDepth(depth); if (!ComputeAliasedSlots(cx, bce, blockObj)) return false; - if (blockObj.needsClone()) { + if (blockObj->needsClone()) { if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce)) return false; } @@ -830,8 +830,8 @@ EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, O return false; PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset()); - blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce)); - FinishPushBlockScope(bce, stmt, blockObj); + blockObj->initEnclosingStaticScope(EnclosingStaticScope(bce)); + FinishPushBlockScope(bce, stmt, *blockObj); JS_ASSERT(stmt->isBlockScope); @@ -2384,7 +2384,7 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) * If there are hoisted let declarations, their stack slots go under the * discriminant's value so push their slots now and enter the block later. */ - StaticBlockObject *blockObj = nullptr; + Rooted blockObj(cx, nullptr); if (pn2->isKind(PNK_LEXICALSCOPE)) { blockObj = &pn2->pn_objbox->object->as(); for (uint32_t i = 0; i < blockObj->slotCount(); ++i) { @@ -2804,7 +2804,13 @@ frontend::EmitFunctionScript(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNo /* Initialize fun->script() so that the debugger has a valid fun->script(). */ RootedFunction fun(cx, bce->script->function()); JS_ASSERT(fun->isInterpreted()); - fun->setScript(bce->script); + + if (fun->isInterpretedLazy()) { + AutoLockForCompilation lock(cx); + fun->setUnlazifiedScript(bce->script); + } else { + fun->setScript(bce->script); + } bce->tellDebuggerAboutCompiledScript(cx); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 41971fe98211..2fbf9361e175 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2853,17 +2853,18 @@ Parser::bindVarOrConst(BindData *data, if (pc->sc->isFunctionBox()) { FunctionBox *funbox = pc->sc->asFunctionBox(); funbox->setMightAliasLocals(); - - /* - * This definition isn't being added to the parse context's - * declarations, so make sure to indicate the need to deoptimize - * the script's arguments object. Mark the function as if it - * contained a debugger statement, which will deoptimize arguments - * as much as possible. - */ - if (name == cx->names().arguments) - funbox->setHasDebuggerStatement(); } + + /* + * This definition isn't being added to the parse context's + * declarations, so make sure to indicate the need to deoptimize + * the script's arguments object. Mark the function as if it + * contained a debugger statement, which will deoptimize arguments + * as much as possible. + */ + if (name == cx->names().arguments) + pc->sc->setHasDebuggerStatement(); + return true; } diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index d0e3bde29e8a..2b1393e0b22e 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -1129,23 +1129,17 @@ ScanTypeObject(GCMarker *gcmarker, types::TypeObject *type) PushMarkStack(gcmarker, JSID_TO_STRING(prop->id)); } - if (TaggedProto(type->proto).isObject()) - PushMarkStack(gcmarker, type->proto); + if (type->proto().isObject()) + PushMarkStack(gcmarker, type->proto().toObject()); if (type->singleton && !type->lazy()) PushMarkStack(gcmarker, type->singleton); - if (type->addendum) { - switch (type->addendum->kind) { - case types::TypeObjectAddendum::NewScript: - PushMarkStack(gcmarker, type->newScript()->fun); - PushMarkStack(gcmarker, type->newScript()->templateObject); - break; - - case types::TypeObjectAddendum::TypedObject: - PushMarkStack(gcmarker, type->typedObject()->typeRepr->ownerObject()); - break; - } + if (type->hasNewScript()) { + PushMarkStack(gcmarker, type->newScript()->fun); + PushMarkStack(gcmarker, type->newScript()->templateObject); + } else if (type->hasTypedObject()) { + PushMarkStack(gcmarker, type->typedObject()->typeRepr->ownerObject()); } if (type->interpretedFunction) @@ -1162,23 +1156,17 @@ gc::MarkChildren(JSTracer *trc, types::TypeObject *type) MarkId(trc, &prop->id, "type_prop"); } - if (TaggedProto(type->proto).isObject()) - MarkObject(trc, &type->proto, "type_proto"); + if (type->proto().isObject()) + MarkObject(trc, &type->protoRaw(), "type_proto"); if (type->singleton && !type->lazy()) MarkObject(trc, &type->singleton, "type_singleton"); - if (type->addendum) { - switch (type->addendum->kind) { - case types::TypeObjectAddendum::NewScript: - MarkObject(trc, &type->newScript()->fun, "type_new_function"); - MarkObject(trc, &type->newScript()->templateObject, "type_new_template"); - break; - - case types::TypeObjectAddendum::TypedObject: - type->typedObject()->typeRepr->mark(trc); - break; - } + if (type->hasNewScript()) { + MarkObject(trc, &type->newScript()->fun, "type_new_function"); + MarkObject(trc, &type->newScript()->templateObject, "type_new_template"); + } else if (type->hasTypedObject()) { + type->typedObject()->typeRepr->mark(trc); } if (type->interpretedFunction) @@ -1441,7 +1429,7 @@ GCMarker::processMarkStackTop(SliceBudget &budget) PushMarkStack(this, shape); /* Call the trace hook if necessary. */ - const Class *clasp = type->clasp; + const Class *clasp = type->clasp(); if (clasp->trace) { JS_ASSERT_IF(runtime->gcMode() == JSGC_MODE_INCREMENTAL && runtime->gcIncrementalEnabled, diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 541ad84d996c..40843d6cff40 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -47,7 +47,7 @@ MarkExactStackRoot(JSTracer *trc, Rooted *rooter, ThingRootKind kind) if (IsNullTaggedPointer(*addr)) return; - if (kind == THING_ROOT_OBJECT && *addr == Proxy::LazyProto) + if (kind == THING_ROOT_OBJECT && *addr == TaggedProto::LazyProto) return; switch (kind) { diff --git a/js/src/jit-test/tests/asm.js/testBasic.js b/js/src/jit-test/tests/asm.js/testBasic.js index 716985703cb8..c9847fc61bcf 100644 --- a/js/src/jit-test/tests/asm.js/testBasic.js +++ b/js/src/jit-test/tests/asm.js/testBasic.js @@ -107,38 +107,6 @@ assertTypeFailInEval('function f(global, {imports}) { "use asm"; function g() {} assertTypeFailInEval('function f(g = 2) { "use asm"; function g() {} return g }'); assertTypeFailInEval('function *f() { "use asm"; function g() {} return g }'); -function assertLinkFailInEval(str) -{ - if (!isAsmJSCompilationAvailable()) - return; - - var caught = false; - var oldOpts = options("werror"); - assertEq(oldOpts.indexOf("werror"), -1); - try { - eval(str); - } catch (e) { - assertEq((''+e).indexOf(ASM_OK_STRING) == -1, false); - caught = true; - } - assertEq(caught, true); - options("werror"); - - var code = eval(str); - - var caught = false; - var oldOpts = options("werror"); - assertEq(oldOpts.indexOf("werror"), -1); - try { - code.apply(null, Array.slice(arguments, 1)); - } catch (e) { - caught = true; - } - assertEq(caught, true); - options("werror"); -} -assertLinkFailInEval('(function(global) { "use asm"; var im=global.Math.imul; function g() {} return g })'); - assertThrowsInstanceOf(function() { new Function(USE_ASM + 'var)') }, SyntaxError); assertThrowsInstanceOf(function() { new Function(USE_ASM + 'return)') }, SyntaxError); assertThrowsInstanceOf(function() { new Function(USE_ASM + 'var z=-2w') }, SyntaxError); diff --git a/js/src/jit-test/tests/basic/bug937089.js b/js/src/jit-test/tests/basic/bug937089.js new file mode 100644 index 000000000000..09eedbe0307a --- /dev/null +++ b/js/src/jit-test/tests/basic/bug937089.js @@ -0,0 +1,10 @@ + +function test1() { + eval("with (arguments) var arguments = 0;"); +} +test1(); + +function test2() { + eval("eval('with (arguments) var arguments = 0;')"); +} +test2(); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 38e2bc69b9b6..fa39e21b24ea 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -186,10 +186,8 @@ BaselineCompiler::compile() baselineScript->setMethod(code); baselineScript->setTemplateScope(templateScope); - script->setBaselineScript(baselineScript); - IonSpew(IonSpew_BaselineScripts, "Created BaselineScript %p (raw %p) for %s:%d", - (void *) script->baselineScript(), (void *) code->raw(), + (void *) baselineScript, (void *) code->raw(), script->filename(), script->lineno()); #ifdef JS_ION_PERF @@ -254,6 +252,8 @@ BaselineCompiler::compile() if (script->compartment()->debugMode()) baselineScript->setDebugMode(); + script->setBaselineScript(cx, baselineScript); + return Method_Compiled; } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 0f7e235058c8..0caa47353c9b 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -130,12 +130,16 @@ ICStubIterator::operator++() } void -ICStubIterator::unlink(Zone *zone) +ICStubIterator::unlink(JSContext *cx) { JS_ASSERT(currentStub_->next() != nullptr); JS_ASSERT(currentStub_ != fallbackStub_); JS_ASSERT(!unlinked_); - fallbackStub_->unlinkStub(zone, previousStub_, currentStub_); + + { + AutoLockForCompilation lock(cx); + fallbackStub_->unlinkStub(cx->zone(), previousStub_, currentStub_); + } // Mark the current iterator position as unlinked, so operator++ works properly. unlinked_ = true; @@ -172,14 +176,14 @@ ICStub::trace(JSTracer *trc) ICTypeMonitor_Fallback *lastMonStub = toMonitoredFallbackStub()->fallbackMonitorStub(); for (ICStubConstIterator iter = lastMonStub->firstMonitorStub(); !iter.atEnd(); iter++) { JS_ASSERT_IF(iter->next() == nullptr, *iter == lastMonStub); - iter->markCode(trc, "baseline-monitor-stub-ioncode"); + iter->trace(trc); } } if (isUpdated()) { for (ICStubConstIterator iter = toUpdatedStub()->firstUpdateStub(); !iter.atEnd(); iter++) { JS_ASSERT_IF(iter->next() == nullptr, iter->isTypeUpdate_Fallback()); - iter->markCode(trc, "baseline-update-stub-ioncode"); + iter->trace(trc); } } @@ -485,7 +489,7 @@ ICFallbackStub::unlinkStubsWithKind(JSContext *cx, ICStub::Kind kind) { for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) { if (iter->kind() == kind) - iter.unlink(cx->zone()); + iter.unlink(cx); } } @@ -1061,7 +1065,7 @@ DoProfilerFallback(JSContext *cx, BaselineFrame *frame, ICProfiler_Fallback *stu ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); if (!optStub) return false; - stub->addNewStub(optStub); + stub->addNewStub(cx, optStub); return true; } @@ -1146,8 +1150,10 @@ ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext *cx, HandleScript scrip ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, existingStub, type); ICStub *stub = existingStub ? compiler.updateStub() : compiler.getStub(compiler.getStubSpace(script)); - if (!stub) + if (!stub) { + js_ReportOutOfMemory(cx); return false; + } IonSpew(IonSpew_BaselineIC, " %s TypeMonitor stub %p for primitive type %d", existingStub ? "Modified existing" : "Created new", stub, type); @@ -1171,8 +1177,10 @@ ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext *cx, HandleScript scrip ICTypeMonitor_SingleObject::Compiler compiler(cx, obj); ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); - if (!stub) + if (!stub) { + js_ReportOutOfMemory(cx); return false; + } IonSpew(IonSpew_BaselineIC, " Added TypeMonitor stub %p for singleton %p", stub, obj.get()); @@ -1193,8 +1201,10 @@ ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext *cx, HandleScript scrip ICTypeMonitor_TypeObject::Compiler compiler(cx, type); ICStub *stub = compiler.getStub(compiler.getStubSpace(script)); - if (!stub) + if (!stub) { + js_ReportOutOfMemory(cx); return false; + } IonSpew(IonSpew_BaselineIC, " Added TypeMonitor stub %p for TypeObject %p", stub, type.get()); @@ -1785,7 +1795,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!int32Stub) return false; - stub->addNewStub(int32Stub); + stub->addNewStub(cx, int32Stub); return true; } @@ -1803,7 +1813,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!doubleStub) return false; - stub->addNewStub(doubleStub); + stub->addNewStub(cx, doubleStub); return true; } @@ -1818,7 +1828,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!doubleStub) return false; - stub->addNewStub(doubleStub); + stub->addNewStub(cx, doubleStub); return true; } @@ -1829,7 +1839,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!booleanStub) return false; - stub->addNewStub(booleanStub); + stub->addNewStub(cx, booleanStub); return true; } @@ -1842,7 +1852,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!optStub) return false; - stub->addNewStub(optStub); + stub->addNewStub(cx, optStub); return true; } @@ -1854,7 +1864,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!stringStub) return false; - stub->addNewStub(stringStub); + stub->addNewStub(cx, stringStub); return true; } @@ -1866,7 +1876,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!objectStub) return false; - stub->addNewStub(objectStub); + stub->addNewStub(cx, objectStub); return true; } @@ -1884,7 +1894,7 @@ DoCompareFallback(JSContext *cx, BaselineFrame *frame, ICCompare_Fallback *stub, if (!objectStub) return false; - stub->addNewStub(objectStub); + stub->addNewStub(cx, objectStub); return true; } } @@ -2090,7 +2100,7 @@ ICCompare_ObjectWithUndefined::Compiler::generateStubCode(MacroAssembler &masm) Label emulatesUndefined; Register obj = masm.extractObject(objectOperand, ExtractTemp0); masm.loadPtr(Address(obj, JSObject::offsetOfType()), obj); - masm.loadPtr(Address(obj, offsetof(types::TypeObject, clasp)), obj); + masm.loadPtr(Address(obj, types::TypeObject::offsetOfClasp()), obj); masm.branchTest32(Assembler::NonZero, Address(obj, Class::offsetOfFlags()), Imm32(JSCLASS_EMULATES_UNDEFINED), @@ -2196,7 +2206,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H if (!int32Stub) return false; - stub->addNewStub(int32Stub); + stub->addNewStub(cx, int32Stub); return true; } @@ -2207,7 +2217,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H if (!doubleStub) return false; - stub->addNewStub(doubleStub); + stub->addNewStub(cx, doubleStub); return true; } @@ -2218,7 +2228,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H if (!stringStub) return false; - stub->addNewStub(stringStub); + stub->addNewStub(cx, stringStub); return true; } @@ -2228,7 +2238,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H if (!nilStub) return false; - stub->addNewStub(nilStub); + stub->addNewStub(cx, nilStub); return true; } @@ -2239,7 +2249,7 @@ DoToBoolFallback(JSContext *cx, BaselineFrame *frame, ICToBool_Fallback *stub, H if (!objStub) return false; - stub->addNewStub(objStub); + stub->addNewStub(cx, objStub); return true; } @@ -2531,11 +2541,11 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac } if (ret.isDouble()) - stub->setSawDoubleResult(); + stub->setSawDoubleResult(cx); // Check to see if a new stub should be generated. if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) { - stub->noteUnoptimizableOperands(); + stub->noteUnoptimizableOperands(cx); return true; } @@ -2548,7 +2558,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script)); if (!strcatStub) return false; - stub->addNewStub(strcatStub); + stub->addNewStub(cx, strcatStub); return true; } @@ -2561,7 +2571,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *strcatStub = compiler.getStub(compiler.getStubSpace(script)); if (!strcatStub) return false; - stub->addNewStub(strcatStub); + stub->addNewStub(cx, strcatStub); return true; } } @@ -2577,13 +2587,13 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *arithStub = compiler.getStub(compiler.getStubSpace(script)); if (!arithStub) return false; - stub->addNewStub(arithStub); + stub->addNewStub(cx, arithStub); return true; } // Handle only int32 or double. if (!lhs.isNumber() || !rhs.isNumber()) { - stub->noteUnoptimizableOperands(); + stub->noteUnoptimizableOperands(cx); return true; } @@ -2607,7 +2617,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); if (!doubleStub) return false; - stub->addNewStub(doubleStub); + stub->addNewStub(cx, doubleStub); return true; } default: @@ -2625,7 +2635,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *int32Stub = compilerInt32.getStub(compilerInt32.getStubSpace(script)); if (!int32Stub) return false; - stub->addNewStub(int32Stub); + stub->addNewStub(cx, int32Stub); return true; } @@ -2644,7 +2654,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); if (!optStub) return false; - stub->addNewStub(optStub); + stub->addNewStub(cx, optStub); return true; } default: @@ -2652,7 +2662,7 @@ DoBinaryArithFallback(JSContext *cx, BaselineFrame *frame, ICBinaryArith_Fallbac } } - stub->noteUnoptimizableOperands(); + stub->noteUnoptimizableOperands(cx); return true; } #if defined(_MSC_VER) @@ -3047,7 +3057,7 @@ DoUnaryArithFallback(JSContext *cx, BaselineFrame *frame, ICUnaryArith_Fallback ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script)); if (!int32Stub) return false; - stub->addNewStub(int32Stub); + stub->addNewStub(cx, int32Stub); return true; } @@ -3061,7 +3071,7 @@ DoUnaryArithFallback(JSContext *cx, BaselineFrame *frame, ICUnaryArith_Fallback ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script)); if (!doubleStub) return false; - stub->addNewStub(doubleStub); + stub->addNewStub(cx, doubleStub); return true; } @@ -3629,7 +3639,7 @@ RemoveExistingGetElemNativeStubs(JSContext *cx, ICGetElem_Fallback *stub, Handle // If the holder matches, but the holder's lastProperty doesn't match, then // this stub is invalid anyway. Unlink it. if (holder->lastProperty() != protoStub->holderShape()) { - iter.unlink(cx->zone()); + iter.unlink(cx); continue; } } else { @@ -3645,7 +3655,7 @@ RemoveExistingGetElemNativeStubs(JSContext *cx, ICGetElem_Fallback *stub, Handle // If the holder matches, but the holder's lastProperty doesn't match, then // this stub is invalid anyway. Unlink it. if (holder->lastProperty() != protoStub->holderShape()) { - iter.unlink(cx->zone()); + iter.unlink(cx); continue; } } @@ -3654,7 +3664,7 @@ RemoveExistingGetElemNativeStubs(JSContext *cx, ICGetElem_Fallback *stub, Handle // If the new stub needs atomization, and the old stub doesn't atomize, then // remove the old stub. if (needsAtomize && !getElemNativeStub->needsAtomize()) { - iter.unlink(cx->zone()); + iter.unlink(cx); continue; } @@ -3745,7 +3755,7 @@ static bool TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, jsbyt if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -3797,7 +3807,7 @@ static bool TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, jsbyt if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -3831,7 +3841,7 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl if (!stringStub) return false; - stub->addNewStub(stringStub); + stub->addNewStub(cx, stringStub); return true; } @@ -3849,7 +3859,7 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl if (!argsStub) return false; - stub->addNewStub(argsStub); + stub->addNewStub(cx, argsStub); return true; } @@ -3871,7 +3881,7 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl if (!argsStub) return false; - stub->addNewStub(argsStub); + stub->addNewStub(cx, argsStub); return true; } } @@ -3886,7 +3896,7 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl if (!denseStub) return false; - stub->addNewStub(denseStub); + stub->addNewStub(cx, denseStub); return true; } @@ -3920,7 +3930,7 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl if (!typedArrayStub) return false; - stub->addNewStub(typedArrayStub); + stub->addNewStub(cx, typedArrayStub); return true; } @@ -3928,13 +3938,13 @@ TryAttachGetElemStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetEl // be cached by either Baseline or Ion. Indicate this in the cache so that // Ion does not generate a cache for this op. if (!obj->isNative() && !obj->is()) - stub->noteNonNativeAccess(); + stub->noteNonNativeAccess(cx); // GetElem operations which could access negative indexes generally can't // be optimized without the potential for bailouts, as we can't statically // determine that an object has no properties on such indexes. if (rhs.isNumber() && rhs.toNumber() < 0) - stub->noteNegativeIndex(); + stub->noteNegativeIndex(cx); return true; } @@ -4844,7 +4854,7 @@ RemoveExistingTypedArraySetElemStub(JSContext *cx, ICSetElem_Fallback *stub, Han // TypedArraySetElem stubs are only removed using this procedure if // being replaced with one that expects out of bounds index. JS_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds()); - iter.unlink(cx->zone()); + iter.unlink(cx); return true; } return false; @@ -5001,7 +5011,7 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub, if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs)) return false; - stub->addNewStub(denseStub); + stub->addNewStub(cx, denseStub); } else if (!addingCase && !DenseSetElemStubExists(cx, ICStub::SetElem_Dense, stub, obj)) { @@ -5015,7 +5025,7 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub, if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs)) return false; - stub->addNewStub(denseStub); + stub->addNewStub(cx, denseStub); } } @@ -5048,7 +5058,7 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub, if (!typedArrayStub) return false; - stub->addNewStub(typedArrayStub); + stub->addNewStub(cx, typedArrayStub); return true; } } @@ -5098,13 +5108,13 @@ ICSetElem_Fallback::Compiler::generateStubCode(MacroAssembler &masm) } void -BaselineScript::noteArrayWriteHole(uint32_t pcOffset) +BaselineScript::noteArrayWriteHole(JSContext *cx, uint32_t pcOffset) { ICEntry &entry = icEntryFromPCOffset(pcOffset); ICFallbackStub *stub = entry.fallbackStub(); if (stub->isSetElem_Fallback()) - stub->toSetElem_Fallback()->noteArrayWriteHole(); + stub->toSetElem_Fallback()->noteArrayWriteHole(cx); } // @@ -5626,7 +5636,7 @@ TryAttachGlobalNameStub(JSContext *cx, HandleScript script, ICGetName_Fallback * if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -5716,7 +5726,7 @@ TryAttachScopeNameStub(JSContext *cx, HandleScript script, ICGetName_Fallback *s if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -5923,7 +5933,7 @@ DoGetIntrinsicFallback(JSContext *cx, BaselineFrame *frame, ICGetIntrinsic_Fallb if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -5971,7 +5981,7 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return false; *attached = true; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -5983,7 +5993,7 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return false; *attached = true; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -6000,7 +6010,7 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return false; *attached = true; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } if (obj->is()) { @@ -6012,7 +6022,7 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return false; *attached = true; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -6028,7 +6038,7 @@ TryAttachLengthStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub return false; *attached = true; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -6111,7 +6121,7 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -6142,7 +6152,7 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -6189,7 +6199,7 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, } if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -6209,7 +6219,7 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -6263,7 +6273,7 @@ TryAttachPrimitiveGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -6349,7 +6359,7 @@ DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub, } JS_ASSERT(!attached); - stub->noteUnoptimizableAccess(); + stub->noteUnoptimizableAccess(cx); return true; } @@ -7094,13 +7104,13 @@ ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler &masm) } void -BaselineScript::noteAccessedGetter(uint32_t pcOffset) +BaselineScript::noteAccessedGetter(JSContext *cx, uint32_t pcOffset) { ICEntry &entry = icEntryFromPCOffset(pcOffset); ICFallbackStub *stub = entry.fallbackStub(); if (stub->isGetProp_Fallback()) - stub->toGetProp_Fallback()->noteAccessedGetter(); + stub->toGetProp_Fallback()->noteAccessedGetter(cx); } // @@ -7141,7 +7151,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -7159,7 +7169,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -7181,7 +7191,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -7200,7 +7210,7 @@ TryAttachSetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetPr if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); *attached = true; return true; } @@ -7271,7 +7281,7 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub, return true; JS_ASSERT(!attached); - stub->noteUnoptimizableAccess(); + stub->noteUnoptimizableAccess(cx); return true; } @@ -7775,7 +7785,7 @@ TryAttachFunApplyStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -7792,7 +7802,7 @@ TryAttachFunApplyStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } } @@ -7933,7 +7943,7 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsb stub->unlinkStubsWithKind(cx, ICStub::Call_Scripted); // Add new generalized stub. - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -7962,7 +7972,7 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsb if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -7999,7 +8009,7 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsb if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); return true; } @@ -9123,7 +9133,7 @@ DoIteratorMoreFallback(JSContext *cx, BaselineFrame *frame, ICIteratorMore_Fallb ICStub *newStub = compiler.getStub(compiler.getStubSpace(frame->script())); if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); } return true; @@ -9197,7 +9207,7 @@ DoIteratorNextFallback(JSContext *cx, BaselineFrame *frame, ICIteratorNext_Fallb return false; if (!res.isString() && !stub->hasNonStringResult()) - stub->setHasNonStringResult(); + stub->setHasNonStringResult(cx); if (iteratorObject->is() && !stub->hasStub(ICStub::IteratorNext_Native)) @@ -9206,7 +9216,7 @@ DoIteratorNextFallback(JSContext *cx, BaselineFrame *frame, ICIteratorNext_Fallb ICStub *newStub = compiler.getStub(compiler.getStubSpace(frame->script())); if (!newStub) return false; - stub->addNewStub(newStub); + stub->addNewStub(cx, newStub); } return true; @@ -9370,7 +9380,7 @@ DoTypeOfFallback(JSContext *cx, BaselineFrame *frame, ICTypeOf_Fallback *stub, H ICStub *typeOfStub = compiler.getStub(compiler.getStubSpace(frame->script())); if (!typeOfStub) return false; - stub->addNewStub(typeOfStub); + stub->addNewStub(cx, typeOfStub); } return true; @@ -9457,7 +9467,7 @@ DoRetSubFallback(JSContext *cx, BaselineFrame *frame, ICRetSub_Fallback *stub, if (!optStub) return false; - stub->addNewStub(optStub); + stub->addNewStub(cx, optStub); return true; } diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index c4c223551cdc..18a205b91ef2 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -516,7 +516,7 @@ class ICStubIterator return currentStub_ == (ICStub *) fallbackStub_; } - void unlink(Zone *zone); + void unlink(JSContext *cx); }; // @@ -687,6 +687,8 @@ class ICStub } inline void setNext(ICStub *stub) { + // Note: next_ only needs to be changed under the compilation lock for + // non-type-monitor/update ICs. next_ = stub; } @@ -833,7 +835,8 @@ class ICFallbackStub : public ICStub } // Add a new stub to the IC chain terminated by this fallback stub. - void addNewStub(ICStub *stub) { + void addNewStub(JSContext *cx, ICStub *stub) { + AutoLockForCompilation lock(cx); JS_ASSERT(*lastStubPtrAddr_ == this); JS_ASSERT(stub->next() == nullptr); stub->setNext(this); @@ -2439,13 +2442,15 @@ class ICBinaryArith_Fallback : public ICFallbackStub bool sawDoubleResult() const { return extra_ & SAW_DOUBLE_RESULT_BIT; } - void setSawDoubleResult() { + void setSawDoubleResult(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= SAW_DOUBLE_RESULT_BIT; } bool hadUnoptimizableOperands() const { return extra_ & UNOPTIMIZABLE_OPERANDS_BIT; } - void noteUnoptimizableOperands() { + void noteUnoptimizableOperands(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= UNOPTIMIZABLE_OPERANDS_BIT; } @@ -2846,14 +2851,16 @@ class ICGetElem_Fallback : public ICMonitoredFallbackStub return space->allocate(code); } - void noteNonNativeAccess() { + void noteNonNativeAccess(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= EXTRA_NON_NATIVE; } bool hasNonNativeAccess() const { return extra_ & EXTRA_NON_NATIVE; } - void noteNegativeIndex() { + void noteNegativeIndex(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= EXTRA_NEGATIVE_INDEX; } bool hasNegativeIndex() const { @@ -3441,7 +3448,8 @@ class ICSetElem_Fallback : public ICFallbackStub return space->allocate(code); } - void noteArrayWriteHole() { + void noteArrayWriteHole(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ = 1; } bool hasArrayWriteHole() const { @@ -4016,14 +4024,16 @@ class ICGetProp_Fallback : public ICMonitoredFallbackStub static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0; static const size_t ACCESSED_GETTER_BIT = 1; - void noteUnoptimizableAccess() { + void noteUnoptimizableAccess(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT); } bool hadUnoptimizableAccess() const { return extra_ & (1u << UNOPTIMIZABLE_ACCESS_BIT); } - void noteAccessedGetter() { + void noteAccessedGetter(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= (1u << ACCESSED_GETTER_BIT); } bool hasAccessedGetter() const { @@ -4830,7 +4840,8 @@ class ICSetProp_Fallback : public ICFallbackStub } static const size_t UNOPTIMIZABLE_ACCESS_BIT = 0; - void noteUnoptimizableAccess() { + void noteUnoptimizableAccess(JSContext *cx) { + AutoLockForCompilation lock(cx); extra_ |= (1u << UNOPTIMIZABLE_ACCESS_BIT); } bool hadUnoptimizableAccess() const { @@ -5721,7 +5732,8 @@ class ICIteratorNext_Fallback : public ICFallbackStub return space->allocate(code); } - void setHasNonStringResult() { + void setHasNonStringResult(JSContext *cx) { + AutoLockForCompilation lock(cx); JS_ASSERT(extra_ == 0); extra_ = 1; } diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index f7e4433326f5..720d6ef36894 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -18,6 +18,8 @@ using mozilla::DebugOnly; bool SetElemICInspector::sawOOBDenseWrite() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!icEntry_) return false; @@ -38,6 +40,8 @@ SetElemICInspector::sawOOBDenseWrite() const bool SetElemICInspector::sawOOBTypedArrayWrite() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!icEntry_) return false; @@ -54,6 +58,8 @@ SetElemICInspector::sawOOBTypedArrayWrite() const bool SetElemICInspector::sawDenseWrite() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!icEntry_) return false; @@ -68,6 +74,8 @@ SetElemICInspector::sawDenseWrite() const bool SetElemICInspector::sawTypedArrayWrite() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!icEntry_) return false; @@ -82,6 +90,8 @@ SetElemICInspector::sawTypedArrayWrite() const bool BaselineInspector::maybeShapesForPropertyOp(jsbytecode *pc, ShapeVector &shapes) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + // Return a list of shapes seen by the baseline IC for the current op. // An empty list indicates no shapes are known, or there was an uncacheable // access. @@ -139,6 +149,8 @@ BaselineInspector::maybeShapesForPropertyOp(jsbytecode *pc, ShapeVector &shapes) ICStub * BaselineInspector::monomorphicStub(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return nullptr; @@ -156,6 +168,8 @@ BaselineInspector::monomorphicStub(jsbytecode *pc) bool BaselineInspector::dimorphicStub(jsbytecode *pc, ICStub **pfirst, ICStub **psecond) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return false; @@ -176,6 +190,8 @@ BaselineInspector::dimorphicStub(jsbytecode *pc, ICStub **pfirst, ICStub **pseco MIRType BaselineInspector::expectedResultType(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + // Look at the IC entries for this op to guess what type it will produce, // returning MIRType_None otherwise. @@ -222,6 +238,8 @@ CanUseInt32Compare(ICStub::Kind kind) MCompare::CompareType BaselineInspector::expectedCompareType(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + ICStub *first = monomorphicStub(pc), *second = nullptr; if (!first && !dimorphicStub(pc, &first, &second)) return MCompare::Compare_Unknown; @@ -304,6 +322,8 @@ TryToSpecializeBinaryArithOp(ICStub **stubs, MIRType BaselineInspector::expectedBinaryArithSpecialization(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + MIRType result; ICStub *stubs[2]; @@ -332,6 +352,8 @@ BaselineInspector::expectedBinaryArithSpecialization(jsbytecode *pc) bool BaselineInspector::hasSeenNonNativeGetElement(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return false; @@ -346,6 +368,8 @@ BaselineInspector::hasSeenNonNativeGetElement(jsbytecode *pc) bool BaselineInspector::hasSeenNegativeIndexGetElement(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return false; @@ -360,6 +384,8 @@ BaselineInspector::hasSeenNegativeIndexGetElement(jsbytecode *pc) bool BaselineInspector::hasSeenAccessedGetter(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return false; @@ -374,6 +400,8 @@ BaselineInspector::hasSeenAccessedGetter(jsbytecode *pc) bool BaselineInspector::hasSeenNonStringIterNext(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + JS_ASSERT(JSOp(*pc) == JSOP_ITERNEXT); if (!hasBaselineScript()) @@ -388,6 +416,8 @@ BaselineInspector::hasSeenNonStringIterNext(jsbytecode *pc) bool BaselineInspector::hasSeenDoubleResult(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return false; @@ -407,6 +437,8 @@ BaselineInspector::hasSeenDoubleResult(jsbytecode *pc) JSObject * BaselineInspector::getTemplateObject(jsbytecode *pc) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return nullptr; @@ -434,6 +466,8 @@ BaselineInspector::getTemplateObject(jsbytecode *pc) JSObject * BaselineInspector::getTemplateObjectForNative(jsbytecode *pc, Native native) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + if (!hasBaselineScript()) return nullptr; @@ -467,6 +501,8 @@ BaselineInspector::templateCallObject() JSObject * BaselineInspector::commonGetPropFunction(jsbytecode *pc, Shape **lastProperty, JSFunction **commonGetter) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + const ICEntry &entry = icEntryFromPC(pc); for (ICStub *stub = entry.firstStub(); stub; stub = stub->next()) { if (stub->isGetProp_CallScripted() || stub->isGetProp_CallNative()) { @@ -482,6 +518,8 @@ BaselineInspector::commonGetPropFunction(jsbytecode *pc, Shape **lastProperty, J JSObject * BaselineInspector::commonSetPropFunction(jsbytecode *pc, Shape **lastProperty, JSFunction **commonSetter) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + const ICEntry &entry = icEntryFromPC(pc); for (ICStub *stub = entry.firstStub(); stub; stub = stub->next()) { if (stub->isSetProp_CallScripted() || stub->isSetProp_CallNative()) { diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 36e2867a888a..833a8ca0aaaf 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -237,7 +237,7 @@ jit::BaselineCompile(JSContext *cx, HandleScript script) JS_ASSERT_IF(status != Method_Compiled, !script->hasBaselineScript()); if (status == Method_CantCompile) - script->setBaselineScript(BASELINE_DISABLED_SCRIPT); + script->setBaselineScript(cx, BASELINE_DISABLED_SCRIPT); return status; } @@ -650,7 +650,7 @@ BaselineScript::copyPCMappingIndexEntries(const PCMappingIndexEntry *entries) uint8_t * BaselineScript::nativeCodeForPC(JSScript *script, jsbytecode *pc, PCMappingSlotInfo *slotInfo) { - JS_ASSERT(script->baselineScript() == this); + JS_ASSERT_IF(script->hasBaselineScript(), script->baselineScript() == this); uint32_t pcOffset = script->pcToOffset(pc); @@ -892,7 +892,7 @@ jit::FinishDiscardBaselineScript(FreeOp *fop, JSScript *script) } BaselineScript *baseline = script->baselineScript(); - script->setBaselineScript(nullptr); + script->setBaselineScript(nullptr, nullptr); BaselineScript::Destroy(fop, baseline); } diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 473edf187112..a9a6dc409e91 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -289,8 +289,8 @@ struct BaselineScript void toggleSPS(bool enable); - void noteAccessedGetter(uint32_t pcOffset); - void noteArrayWriteHole(uint32_t pcOffset); + void noteAccessedGetter(JSContext *cx, uint32_t pcOffset); + void noteArrayWriteHole(JSContext *cx, uint32_t pcOffset); static size_t offsetOfFlags() { return offsetof(BaselineScript, flags_); diff --git a/js/src/jit/BitSet.cpp b/js/src/jit/BitSet.cpp index df5fe4846eae..6e393725823c 100644 --- a/js/src/jit/BitSet.cpp +++ b/js/src/jit/BitSet.cpp @@ -36,8 +36,9 @@ bool BitSet::empty() const { JS_ASSERT(bits_); - for (unsigned int i = 0; i < numWords(); i++) { - if (bits_[i]) + const uint32_t *bits = bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) { + if (bits[i]) return false; } return true; @@ -50,8 +51,10 @@ BitSet::insertAll(const BitSet *other) JS_ASSERT(other->numBits_ == numBits_); JS_ASSERT(other->bits_); - for (unsigned int i = 0; i < numWords(); i++) - bits_[i] |= other->bits_[i]; + uint32_t *bits = bits_; + const uint32_t *otherBits = other->bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) + bits[i] |= otherBits[i]; } void @@ -61,8 +64,10 @@ BitSet::removeAll(const BitSet *other) JS_ASSERT(other->numBits_ == numBits_); JS_ASSERT(other->bits_); - for (unsigned int i = 0; i < numWords(); i++) - bits_[i] &= ~other->bits_[i]; + uint32_t *bits = bits_; + const uint32_t *otherBits = other->bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) + bits[i] &= ~otherBits[i]; } void @@ -72,8 +77,10 @@ BitSet::intersect(const BitSet *other) JS_ASSERT(other->numBits_ == numBits_); JS_ASSERT(other->bits_); - for (unsigned int i = 0; i < numWords(); i++) - bits_[i] &= other->bits_[i]; + uint32_t *bits = bits_; + const uint32_t *otherBits = other->bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) + bits[i] &= otherBits[i]; } // returns true if the intersection caused the contents of the set to change. @@ -86,11 +93,13 @@ BitSet::fixedPointIntersect(const BitSet *other) bool changed = false; - for (unsigned int i = 0; i < numWords(); i++) { - uint32_t old = bits_[i]; - bits_[i] &= other->bits_[i]; + uint32_t *bits = bits_; + const uint32_t *otherBits = other->bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) { + uint32_t old = bits[i]; + bits[i] &= otherBits[i]; - if (!changed && old != bits_[i]) + if (!changed && old != bits[i]) changed = true; } return changed; @@ -100,14 +109,16 @@ void BitSet::complement() { JS_ASSERT(bits_); - for (unsigned int i = 0; i < numWords(); i++) - bits_[i] = ~bits_[i]; + uint32_t *bits = bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) + bits[i] = ~bits[i]; } void BitSet::clear() { JS_ASSERT(bits_); - for (unsigned int i = 0; i < numWords(); i++) - bits_[i] = 0; + uint32_t *bits = bits_; + for (unsigned int i = 0, e = numWords(); i < e; i++) + bits[i] = 0; } diff --git a/js/src/jit/BitSet.h b/js/src/jit/BitSet.h index b602b4285222..b6bab6ec06bb 100644 --- a/js/src/jit/BitSet.h +++ b/js/src/jit/BitSet.h @@ -29,11 +29,11 @@ class BitSet : private TempObject private: BitSet(unsigned int numBits) : - numBits_(numBits), - bits_(nullptr) {} + bits_(nullptr), + numBits_(numBits) {} - unsigned int numBits_; uint32_t *bits_; + const unsigned int numBits_; static inline uint32_t bitForValue(unsigned int value) { return 1l << uint32_t(value % BitsPerWord); @@ -120,6 +120,29 @@ class BitSet::Iterator unsigned word_; uint32_t value_; + void skipEmpty() { + // Skip words containing only zeros. + unsigned numWords = set_.numWords(); + const uint32_t *bits = set_.bits_; + while (value_ == 0) { + word_++; + if (word_ == numWords) + return; + + JS_STATIC_ASSERT(sizeof(value_) * 8 == BitSet::BitsPerWord); + index_ = word_ * sizeof(value_) * 8; + value_ = bits[word_]; + } + + // Be careful: the result of CountTrailingZeroes32 is undefined if the + // input is 0. + int numZeros = mozilla::CountTrailingZeroes32(value_); + index_ += numZeros; + value_ >>= numZeros; + + JS_ASSERT_IF(index_ < set_.numBits_, set_.contains(index_)); + } + public: Iterator(BitSet &set) : set_(set), @@ -127,8 +150,7 @@ class BitSet::Iterator word_(0), value_(set.bits_[0]) { - if (!set_.contains(index_)) - (*this)++; + skipEmpty(); } inline bool more() const { @@ -145,23 +167,7 @@ class BitSet::Iterator index_++; value_ >>= 1; - // Skip words containing only zeros. - while (value_ == 0) { - word_++; - if (!more()) - return *this; - - index_ = word_ * sizeof(value_) * 8; - value_ = set_.bits_[word_]; - } - - // Be careful: the result of CountTrailingZeroes32 is undefined if the - // input is 0. - int numZeros = mozilla::CountTrailingZeroes32(value_); - index_ += numZeros; - value_ >>= numZeros; - - JS_ASSERT_IF(index_ < set_.numBits_, set_.contains(index_)); + skipEmpty(); return *this; } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 8dbe1e277efa..aeffad793ec0 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -3389,14 +3389,14 @@ CodeGenerator::visitNewCallObject(LNewCallObject *lir) ool = oolCallVM(NewCallObjectInfo, lir, (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->hasLazyType() ? nullptr : templateObj->type()), + ImmGCPtr(templateObj->hasSingletonType() ? nullptr : templateObj->type()), ToRegister(lir->slots())), StoreRegisterTo(obj)); } else { ool = oolCallVM(NewCallObjectInfo, lir, (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->hasLazyType() ? nullptr : templateObj->type()), + ImmGCPtr(templateObj->hasSingletonType() ? nullptr : templateObj->type()), ImmPtr(nullptr)), StoreRegisterTo(obj)); } @@ -7409,8 +7409,7 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, JSObject *prototypeObject) // out of the loop on Proxy::LazyProto. // Load the lhs's prototype. - masm.loadPtr(Address(objReg, JSObject::offsetOfType()), output); - masm.loadPtr(Address(output, offsetof(types::TypeObject, proto)), output); + masm.loadObjProto(objReg, output); Label testLazy; { @@ -7424,14 +7423,13 @@ CodeGenerator::emitInstanceOf(LInstruction *ins, JSObject *prototypeObject) masm.jump(&done); masm.bind(¬PrototypeObject); - JS_ASSERT(uintptr_t(Proxy::LazyProto) == 1); + JS_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1); // Test for nullptr or Proxy::LazyProto masm.branchPtr(Assembler::BelowOrEqual, output, ImmWord(1), &testLazy); // Load the current object's prototype. - masm.loadPtr(Address(output, JSObject::offsetOfType()), output); - masm.loadPtr(Address(output, offsetof(types::TypeObject, proto)), output); + masm.loadObjProto(output, output); masm.jump(&loopPrototypeChain); } diff --git a/js/src/jit/CompileInfo.h b/js/src/jit/CompileInfo.h index 874ad1487ebf..622a41b5d99c 100644 --- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -45,9 +45,9 @@ class CompileInfo { public: CompileInfo(JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing, - ExecutionMode executionMode) + ExecutionMode executionMode, bool scriptNeedsArgsObj) : script_(script), fun_(fun), osrPc_(osrPc), constructing_(constructing), - executionMode_(executionMode) + executionMode_(executionMode), scriptNeedsArgsObj_(scriptNeedsArgsObj) { JS_ASSERT_IF(osrPc, JSOp(*osrPc) == JSOP_LOOPENTRY); @@ -68,7 +68,7 @@ class CompileInfo CompileInfo(unsigned nlocals, ExecutionMode executionMode) : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false), - executionMode_(executionMode) + executionMode_(executionMode), scriptNeedsArgsObj_(false) { nimplicit_ = 0; nargs_ = 0; @@ -250,10 +250,10 @@ class CompileInfo return script()->argumentsAliasesFormals(); } bool needsArgsObj() const { - return script()->needsArgsObj(); + return scriptNeedsArgsObj_; } bool argsObjAliasesFormals() const { - return script()->argsObjAliasesFormals(); + return scriptNeedsArgsObj_ && !script()->strict(); } ExecutionMode executionMode() const { @@ -275,6 +275,11 @@ class CompileInfo jsbytecode *osrPc_; bool constructing_; ExecutionMode executionMode_; + + // Whether a script needs an arguments object is unstable over compilation + // since the arguments optimization could be marked as failed on the main + // thread, so cache a value here and use it throughout for consistency. + bool scriptNeedsArgsObj_; }; } // namespace jit diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 58aaacf4a932..439bab359935 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -125,11 +125,13 @@ CompileRuntime::positiveInfinityValue() return runtime()->positiveInfinityValue; } +#ifdef DEBUG bool CompileRuntime::isInsideNursery(gc::Cell *cell) { return UninlinedIsInsideNursery(runtime(), cell); } +#endif const DOMCallbacks * CompileRuntime::DOMcallbacks() @@ -228,3 +230,10 @@ CompileCompartment::hasObjectMetadataCallback() { return compartment()->hasObjectMetadataCallback(); } + +AutoLockForCompilation::AutoLockForCompilation(CompileCompartment *compartment + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + init(compartment->compartment()->runtimeFromAnyThread()); +} diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index 487e063ea516..7da0253f9083 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -66,7 +66,9 @@ class CompileRuntime const Value &NaNValue(); const Value &positiveInfinityValue(); +#ifdef DEBUG bool isInsideNursery(gc::Cell *cell); +#endif // DOM callbacks must be threadsafe (and will hopefully be removed soon). const DOMCallbacks *DOMcallbacks(); @@ -96,6 +98,8 @@ class CompileCompartment { JSCompartment *compartment(); + friend class js::AutoLockForCompilation; + public: static CompileCompartment *get(JSCompartment *comp); diff --git a/js/src/jit/FixedList.h b/js/src/jit/FixedList.h index b5da5e553785..4d336f7d0930 100644 --- a/js/src/jit/FixedList.h +++ b/js/src/jit/FixedList.h @@ -19,8 +19,8 @@ namespace jit { template class FixedList { - size_t length_; T *list_; + size_t length_; private: FixedList(const FixedList&); // no copy definition. diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 547c5363c848..733f4a11067a 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -49,6 +49,8 @@ using namespace js; using namespace js::jit; +using mozilla::Maybe; + // Global variables. IonOptions jit::js_IonOptions; @@ -516,6 +518,8 @@ JitCompartment::ensureIonStubsExist(JSContext *cx) void jit::FinishOffThreadBuilder(IonBuilder *builder) { + builder->script()->runtimeFromMainThread()->removeCompilationThread(); + ExecutionMode executionMode = builder->info().executionMode(); // Clear the recompiling flag if it would have failed. @@ -1563,6 +1567,9 @@ AttachFinishedCompilations(JSContext *cx) // operation callback and can't propagate failures. cx->clearPendingException(); } + } else { + if (builder->abortReason() == AbortReason_Disable) + SetIonScript(builder->script(), builder->info().executionMode(), ION_DISABLED_SCRIPT); } FinishOffThreadBuilder(builder); @@ -1663,11 +1670,13 @@ IonCompile(JSContext *cx, JSScript *script, return AbortReason_Alloc; CompileInfo *info = alloc->new_(script, script->function(), osrPc, constructing, - executionMode); + executionMode, script->needsArgsObj()); if (!info) return AbortReason_Alloc; - BaselineInspector inspector(script); + BaselineInspector *inspector = alloc->new_(script); + if (!inspector) + return AbortReason_Alloc; BaselineFrameInspector *baselineFrameInspector = nullptr; if (baselineFrame) { @@ -1686,7 +1695,7 @@ IonCompile(JSContext *cx, JSScript *script, IonBuilder *builder = alloc->new_((JSContext *) nullptr, CompileCompartment::get(cx->compartment()), temp, graph, constraints, - &inspector, info, baselineFrameInspector); + inspector, info, baselineFrameInspector); if (!builder) return AbortReason_Alloc; @@ -1696,28 +1705,6 @@ IonCompile(JSContext *cx, JSScript *script, RootedScript builderScript(cx, builder->script()); IonSpewNewFunction(graph, builderScript); - mozilla::Maybe protect; - if (js_IonOptions.checkThreadSafety && - cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL && - !cx->runtime()->profilingScripts && - !cx->runtime()->spsProfiler.enabled()) - { - protect.construct(cx->runtime()); - } - - bool succeeded = builder->build(); - builder->clearForBackEnd(); - - if (!succeeded) { - if (cx->isExceptionPending()) { - IonSpew(IonSpew_Abort, "Builder raised exception."); - return AbortReason_Error; - } - - IonSpew(IonSpew_Abort, "Builder failed to build."); - return builder->abortReason(); - } - // If possible, compile the script off thread. if (OffThreadCompilationAvailable(cx)) { if (recompile) { @@ -1739,6 +1726,24 @@ IonCompile(JSContext *cx, JSScript *script, return AbortReason_NoAbort; } + Maybe ionCompiling; + ionCompiling.construct(); + + Maybe protect; + if (js_IonOptions.checkThreadSafety && + cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL && + !cx->runtime()->profilingScripts && + !cx->runtime()->spsProfiler.enabled()) + { + protect.construct(cx->runtime()); + } + + bool succeeded = builder->build(); + builder->clearForBackEnd(); + + if (!succeeded) + return builder->abortReason(); + ScopedJSDeletePtr codegen(CompileBackEnd(builder)); if (!codegen) { IonSpew(IonSpew_Abort, "Failed during back-end compilation."); @@ -1747,6 +1752,7 @@ IonCompile(JSContext *cx, JSScript *script, if (!protect.empty()) protect.destroy(); + ionCompiling.destroy(); bool success = codegen->link(cx, builder->constraints()); diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 049c9de5c5c6..d70f534998bf 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -222,11 +222,10 @@ IsPhiObservable(MPhi *phi, Observability observe) if (fun && slot == info.thisSlot()) return true; - // If the function is heavyweight, and the Phi is of the |scopeChain| - // value, and the function may need an arguments object, then make sure - // to preserve the scope chain, because it may be needed to construct the - // arguments object during bailout. - if (fun && fun->isHeavyweight() && info.hasArguments() && slot == info.scopeChainSlot()) + // If the function may need an arguments object, then make sure to preserve + // the scope chain, because it may be needed to construct the arguments + // object during bailout. + if (fun && info.hasArguments() && slot == info.scopeChainSlot()) return true; // If the Phi is one of the formal argument, and we are using an argument @@ -2177,7 +2176,8 @@ jit::AnalyzeNewScriptProperties(JSContext *cx, JSFunction *fun, MIRGraph graph(&temp); CompileInfo info(script, fun, /* osrPc = */ nullptr, /* constructing = */ false, - DefinitePropertiesAnalysis); + DefinitePropertiesAnalysis, + script->needsArgsObj()); AutoTempAllocatorRooter root(cx, &temp); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 934654267982..27d57b3609b2 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -135,16 +135,24 @@ IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp, Tem lazyArguments_(nullptr), inlineCallInfo_(nullptr) { - script_.init(info->script()); + script_ = info->script(); pc = info->startPC(); +#ifdef DEBUG + lock(); JS_ASSERT(script()->hasBaselineScript()); + unlock(); +#endif JS_ASSERT(!!analysisContext == (info->executionMode() == DefinitePropertiesAnalysis)); } void IonBuilder::clearForBackEnd() { + // This case should only be hit if there was a failure while building. + if (!lock_.empty()) + lock_.destroy(); + JS_ASSERT(!analysisContext); baselineFrame_ = nullptr; @@ -581,12 +589,16 @@ IonBuilder::pushLoop(CFGState::State initial, jsbytecode *stopAt, MBasicBlock *e bool IonBuilder::init() { + lock(); + if (!types::TypeScript::FreezeTypeSets(constraints(), script(), &thisTypes, &argTypes, &typeArray)) { return false; } + unlock(); + if (!analysis().init(alloc(), gsn)) return false; @@ -694,6 +706,8 @@ IonBuilder::build() if (!traverseBytecode()) return false; + unlock(); + if (!maybeAddOsrTypeBarriers()) return false; @@ -851,6 +865,7 @@ IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoi if (!traverseBytecode()) return false; + unlock(); return true; } @@ -905,6 +920,9 @@ IonBuilder::initParameters() // interpreter and didn't accumulate type information, try to use that OSR // frame to determine possible initial types for 'this' and parameters. + // For unknownProperties() tests under addType. + lock(); + if (thisTypes->empty() && baselineFrame_) { if (!thisTypes->addType(baselineFrame_->thisType, alloc_->lifoAlloc())) return false; @@ -928,6 +946,8 @@ IonBuilder::initParameters() current->initSlot(info().argSlotUnchecked(i), param); } + unlock(); + return true; } @@ -950,6 +970,8 @@ IonBuilder::initScopeChain(MDefinition *callee) if (!script()->compileAndGo()) return abort("non-CNG global scripts are not supported"); + lock(); + if (JSFunction *fun = info().fun()) { if (!callee) { MCallee *calleeIns = MCallee::New(alloc()); @@ -975,6 +997,8 @@ IonBuilder::initScopeChain(MDefinition *callee) scope = constant(ObjectValue(script()->global())); } + unlock(); + current->setScopeChain(scope); return true; } @@ -1168,6 +1192,14 @@ IonBuilder::maybeAddOsrTypeBarriers() bool IonBuilder::traverseBytecode() { + // Always hold the compilation lock when traversing bytecode, though release + // it before reacquiring it every few opcodes so that the main thread does not + // block for long when updating compilation data. + lock(); + + size_t lockOpcodeCount = 0; + static const size_t LOCK_OPCODE_GRANULARITY = 5; + for (;;) { JS_ASSERT(pc < info().limitPC()); @@ -1242,6 +1274,12 @@ IonBuilder::traverseBytecode() if (!inspectOpcode(op)) return false; + if (++lockOpcodeCount == LOCK_OPCODE_GRANULARITY) { + unlock(); + lock(); + lockOpcodeCount = 0; + } + #ifdef DEBUG for (size_t i = 0; i < popped.length(); i++) { // Call instructions can discard PassArg instructions. Ignore them. @@ -3845,13 +3883,16 @@ IonBuilder::inlineScriptedCall(CallInfo &callInfo, JSFunction *target) LifoAlloc *lifoAlloc = alloc_->lifoAlloc(); CompileInfo *info = lifoAlloc->new_(calleeScript, target, (jsbytecode *)nullptr, callInfo.constructing(), - this->info().executionMode()); + this->info().executionMode(), + /* needsArgsObj = */ false); if (!info) return false; MIRGraphReturns returns(alloc()); AutoAccumulateReturns aar(graph(), returns); + unlock(); + // Build the graph. IonBuilder inlineBuilder(analysisContext, compartment, &alloc(), &graph(), constraints(), &inspector, info, nullptr, @@ -3875,6 +3916,8 @@ IonBuilder::inlineScriptedCall(CallInfo &callInfo, JSFunction *target) return false; } + lock(); + // Create return block. jsbytecode *postCall = GetNextPc(pc); MBasicBlock *returnBlock = newBlock(nullptr, postCall); @@ -4698,7 +4741,7 @@ IonBuilder::createThisScriptedSingleton(JSFunction *target, MDefinition *callee) JSObject *templateObject = inspector->getTemplateObject(pc); if (!templateObject || !templateObject->is()) return nullptr; - if (templateObject->getProto() != proto) + if (!templateObject->hasTenuredProto() || templateObject->getProto() != proto) return nullptr; if (!target->nonLazyScript()->types) @@ -5096,6 +5139,8 @@ IonBuilder::testShouldDOMCall(types::TypeSet *inTypes, if (!curType) continue; + if (!curType->hasTenuredProto()) + return false; JSObject *proto = curType->proto().toObjectOrNull(); if (!instanceChecker(proto, jinfo->protoID, jinfo->depth)) return false; @@ -6031,6 +6076,8 @@ IonBuilder::testSingletonProperty(JSObject *obj, PropertyName *name) if (ClassHasResolveHook(compartment, obj->getClass(), name)) return nullptr; + if (!obj->hasTenuredProto()) + return nullptr; obj = obj->getProto(); } @@ -6104,6 +6151,8 @@ IonBuilder::testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, Pr if (property.isOwnProperty(constraints())) return false; + if (!object->hasTenuredProto()) + return false; if (JSObject *proto = object->proto().toObjectOrNull()) { // Test this type. if (testSingletonProperty(proto, name) != singleton) @@ -7897,6 +7946,8 @@ IonBuilder::objectsHaveCommonPrototype(types::TemporaryTypeSet *types, PropertyN return false; } + if (!type->hasTenuredProto()) + return false; JSObject *proto = type->proto().toObjectOrNull(); if (proto == foundProto) break; @@ -7996,7 +8047,7 @@ IonBuilder::annotateGetPropertyCache(MDefinition *obj, MGetPropertyCache *getPro if (!baseTypeObj) continue; types::TypeObjectKey *typeObj = types::TypeObjectKey::get(baseTypeObj); - if (typeObj->unknownProperties() || !typeObj->proto().isObject()) + if (typeObj->unknownProperties() || !typeObj->hasTenuredProto() || !typeObj->proto().isObject()) continue; const Class *clasp = typeObj->clasp(); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index b3094b74e461..6839e1d9d958 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -724,7 +724,7 @@ class IonBuilder : public MIRGenerator } // A builder is inextricably tied to a particular script. - HeapPtrScript script_; + JSScript *script_; // If off thread compilation is successful, the final code generator is // attached here. Code has been generated, but not linked (there is not yet @@ -735,7 +735,7 @@ class IonBuilder : public MIRGenerator public: void clearForBackEnd(); - JSScript *script() const { return script_.get(); } + JSScript *script() const { return script_; } CodeGenerator *backgroundCodegen() const { return backgroundCodegen_; } void setBackgroundCodegen(CodeGenerator *codegen) { backgroundCodegen_ = codegen; } @@ -765,6 +765,17 @@ class IonBuilder : public MIRGenerator // Constraints for recording dependencies on type information. types::CompilerConstraintList *constraints_; + mozilla::Maybe lock_; + + void lock() { + if (!analysisContext) + lock_.construct(compartment); + } + void unlock() { + if (!analysisContext) + lock_.destroy(); + } + // Basic analysis information about the script. BytecodeAnalysis analysis_; BytecodeAnalysis &analysis() { diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 188053a3a0b3..de66d8abf91c 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -468,7 +468,7 @@ GeneratePrototypeGuards(JSContext *cx, IonScript *ion, MacroAssembler &masm, JSO // Note: objectReg and scratchReg may be the same register, so we cannot // use objectReg in the rest of this function. masm.loadPtr(Address(objectReg, JSObject::offsetOfType()), scratchReg); - Address proto(scratchReg, offsetof(types::TypeObject, proto)); + Address proto(scratchReg, types::TypeObject::offsetOfProto()); masm.branchNurseryPtr(Assembler::NotEqual, proto, ImmMaybeNurseryPtr(obj->getProto()), failures); } @@ -796,11 +796,7 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, Register lastReg = object; JS_ASSERT(scratchReg != object); while (proto) { - Address addrType(lastReg, JSObject::offsetOfType()); - masm.loadPtr(addrType, scratchReg); - Address addrProto(scratchReg, offsetof(types::TypeObject, proto)); - masm.loadPtr(addrProto, scratchReg); - Address addrShape(scratchReg, JSObject::offsetOfShape()); + masm.loadObjProto(lastReg, scratchReg); // Guard the shape of the current prototype. masm.branchPtr(Assembler::NotEqual, @@ -2584,8 +2580,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att Shape *protoShape = proto->lastProperty(); // load next prototype - masm.loadPtr(Address(protoReg, JSObject::offsetOfType()), protoReg); - masm.loadPtr(Address(protoReg, offsetof(types::TypeObject, proto)), protoReg); + masm.loadObjProto(protoReg, protoReg); // Ensure that its shape matches. masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, &failuresPopObject); diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index 35c9aaeb8fbe..2d5d85fc43d1 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -292,12 +292,12 @@ class MacroAssembler : public MacroAssemblerSpecific } void loadObjClass(Register objReg, Register dest) { loadPtr(Address(objReg, JSObject::offsetOfType()), dest); - loadPtr(Address(dest, offsetof(types::TypeObject, clasp)), dest); + loadPtr(Address(dest, types::TypeObject::offsetOfClasp()), dest); } void branchTestObjClass(Condition cond, Register obj, Register scratch, const js::Class *clasp, Label *label) { loadPtr(Address(obj, JSObject::offsetOfType()), scratch); - branchPtr(cond, Address(scratch, offsetof(types::TypeObject, clasp)), ImmPtr(clasp), label); + branchPtr(cond, Address(scratch, types::TypeObject::offsetOfClasp()), ImmPtr(clasp), label); } void branchTestObjShape(Condition cond, Register obj, const Shape *shape, Label *label) { branchPtr(cond, Address(obj, JSObject::offsetOfShape()), ImmGCPtr(shape), label); @@ -353,7 +353,7 @@ class MacroAssembler : public MacroAssemblerSpecific void loadObjProto(Register obj, Register dest) { loadPtr(Address(obj, JSObject::offsetOfType()), dest); - loadPtr(Address(dest, offsetof(types::TypeObject, proto)), dest); + loadPtr(Address(dest, types::TypeObject::offsetOfProto()), dest); } void loadStringLength(Register str, Register dest) { diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 6c3efded51fe..92cff151ffb4 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2929,7 +2929,13 @@ jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx, // If this access has never executed, try to add types to the observed set // according to any property which exists on the object or its prototype. if (updateObserved && observed->empty() && name) { - JSObject *obj = object->singleton() ? object->singleton() : object->proto().toObjectOrNull(); + JSObject *obj; + if (object->singleton()) + obj = object->singleton(); + else if (object->hasTenuredProto()) + obj = object->proto().toObjectOrNull(); + else + obj = nullptr; while (obj) { if (!obj->getClass()->isNative()) @@ -2953,6 +2959,8 @@ jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx, } } + if (!obj->hasTenuredProto()) + break; obj = obj->getProto(); } } @@ -3004,7 +3012,11 @@ jit::PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *cons types::TypeObjectKey *object = types->getObject(i); if (!object) continue; - while (object->proto().isObject()) { + while (true) { + if (!object->hasTenuredProto()) + return true; + if (!object->proto().isObject()) + break; object = types::TypeObjectKey::get(object->proto().toObject()); if (PropertyReadNeedsTypeBarrier(constraints, object, name, observed)) return true; diff --git a/js/src/jit/Safepoints.cpp b/js/src/jit/Safepoints.cpp index c4f0c7906ecf..520fc575755b 100644 --- a/js/src/jit/Safepoints.cpp +++ b/js/src/jit/Safepoints.cpp @@ -106,7 +106,7 @@ MapSlotsToBitset(BitSet *set, CompactBufferWriter &stream, uint32_t nslots, uint } size_t count = set->rawLength(); - uint32_t *words = set->raw(); + const uint32_t *words = set->raw(); for (size_t i = 0; i < count; i++) stream.writeUnsigned(words[i]); } diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 717a2703df7a..4f87d172c995 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -552,7 +552,7 @@ OperatorInI(JSContext *cx, uint32_t index, HandleObject obj, bool *out) bool GetIntrinsicValue(JSContext *cx, HandlePropertyName name, MutableHandleValue rval) { - if (!cx->global()->getIntrinsicValue(cx, name, rval)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, rval)) return false; // This function is called when we try to compile a cold getintrinsic @@ -946,7 +946,7 @@ AssertValidObjectPtr(JSContext *cx, JSObject *obj) JS_ASSERT(obj->runtimeFromMainThread() == cx->runtime()); JS_ASSERT_IF(!obj->hasLazyType(), - obj->type()->clasp == obj->lastProperty()->getObjectClass()); + obj->type()->clasp() == obj->lastProperty()->getObjectClass()); if (obj->isTenured()) { JS_ASSERT(obj->isAligned()); diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 2a563f422cac..2163c0e7cb97 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -1725,7 +1725,7 @@ CodeGeneratorX86Shared::visitGuardClass(LGuardClass *guard) Register tmp = ToRegister(guard->tempInt()); masm.loadPtr(Address(obj, JSObject::offsetOfType()), tmp); - masm.cmpPtr(Operand(tmp, offsetof(types::TypeObject, clasp)), ImmPtr(guard->mir()->getClass())); + masm.cmpPtr(Operand(tmp, types::TypeObject::offsetOfClasp()), ImmPtr(guard->mir()->getClass())); if (!bailoutIf(Assembler::NotEqual, guard->snapshot())) return false; return true; diff --git a/js/src/js.msg b/js/src/js.msg index 75e0dce894c4..ba232ffccc93 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -391,7 +391,7 @@ MSG_DEF(JSMSG_DATE_NOT_FINITE, 337, 0, JSEXN_RANGEERR, "date value is not MSG_DEF(JSMSG_USE_ASM_DIRECTIVE_FAIL, 338, 0, JSEXN_SYNTAXERR, "\"use asm\" is only meaningful in the Directive Prologue of a function body") MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 339, 1, JSEXN_TYPEERR, "asm.js type error: {0}") MSG_DEF(JSMSG_USE_ASM_LINK_FAIL, 340, 1, JSEXN_TYPEERR, "asm.js link error: {0}") -MSG_DEF(JSMSG_USE_ASM_TYPE_OK, 341, 1, JSEXN_ERR, "successfully compiled asm.js code ({0})") +MSG_DEF(JSMSG_USE_ASM_TYPE_OK, 341, 1, JSEXN_NONE, "Successfully compiled asm.js code ({0})") MSG_DEF(JSMSG_BAD_ARROW_ARGS, 342, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") MSG_DEF(JSMSG_YIELD_IN_ARROW, 343, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield") MSG_DEF(JSMSG_WRONG_VALUE, 344, 2, JSEXN_ERR, "expected {0} but found {1}") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 9281c001ce61..722ec4ceb930 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -700,9 +700,7 @@ StartRequest(JSContext *cx) } else { /* Indicate that a request is running. */ rt->requestDepth = 1; - - if (rt->activityCallback) - rt->activityCallback(rt->activityCallbackArg, true); + rt->triggerActivityCallback(true); } } @@ -718,9 +716,7 @@ StopRequest(JSContext *cx) } else { rt->conservativeGC.updateForRequestEnd(); rt->requestDepth = 0; - - if (rt->activityCallback) - rt->activityCallback(rt->activityCallbackArg, false); + rt->triggerActivityCallback(false); } } #endif /* JS_THREADSAFE */ diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 602bac144815..cbc4567c0dd0 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -116,6 +116,7 @@ js::ExistingCloneFunctionAtCallsite(const CallsiteCloneTable &table, JSFunction JS_ASSERT(fun->nonLazyScript()->shouldCloneAtCallsite()); JS_ASSERT(!fun->nonLazyScript()->enclosingStaticScope()); JS_ASSERT(types::UseNewTypeForClone(fun)); + JS_ASSERT(CurrentThreadCanReadCompilationData()); /* * If we start allocating function objects in the nursery, then the callsite @@ -126,7 +127,7 @@ js::ExistingCloneFunctionAtCallsite(const CallsiteCloneTable &table, JSFunction if (!table.initialized()) return nullptr; - CallsiteCloneTable::Ptr p = table.lookup(CallsiteCloneKey(fun, script, script->pcToOffset(pc))); + CallsiteCloneTable::Ptr p = table.readonlyThreadsafeLookup(CallsiteCloneKey(fun, script, script->pcToOffset(pc))); if (p) return p->value(); @@ -153,6 +154,8 @@ js::CloneFunctionAtCallsite(JSContext *cx, HandleFunction fun, HandleScript scri typedef CallsiteCloneKey Key; typedef CallsiteCloneTable Table; + AutoLockForCompilation lock(cx); + Table &table = cx->compartment()->callsiteClones; if (!table.initialized() && !table.init()) return nullptr; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 691e0a0c26f7..3aee93c84a20 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -32,7 +32,10 @@ js_ReportOverRecursed(js::ThreadSafeContext *cx); namespace js { -namespace jit { class IonContext; } +namespace jit { +class IonContext; +class CompileCompartment; +} struct CallsiteCloneKey { /* The original function that we are cloning. */ @@ -1038,7 +1041,9 @@ class AutoLockForExclusiveAccess if (runtime->numExclusiveThreads) { runtime->assertCanLock(JSRuntime::ExclusiveAccessLock); PR_Lock(runtime->exclusiveAccessLock); +#ifdef DEBUG runtime->exclusiveAccessOwner = PR_GetCurrentThread(); +#endif } else { JS_ASSERT(!runtime->mainThreadHasExclusiveAccess); runtime->mainThreadHasExclusiveAccess = true; @@ -1057,9 +1062,7 @@ class AutoLockForExclusiveAccess ~AutoLockForExclusiveAccess() { if (runtime->numExclusiveThreads) { JS_ASSERT(runtime->exclusiveAccessOwner == PR_GetCurrentThread()); -#ifdef DEBUG runtime->exclusiveAccessOwner = nullptr; -#endif PR_Unlock(runtime->exclusiveAccessLock); } else { JS_ASSERT(runtime->mainThreadHasExclusiveAccess); @@ -1083,6 +1086,69 @@ class AutoLockForExclusiveAccess MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; +class AutoLockForCompilation +{ +#ifdef JS_WORKER_THREADS + JSRuntime *runtime; + + void init(JSRuntime *rt) { + runtime = rt; + if (runtime->numCompilationThreads) { + runtime->assertCanLock(JSRuntime::CompilationLock); + PR_Lock(runtime->compilationLock); +#ifdef DEBUG + runtime->compilationLockOwner = PR_GetCurrentThread(); +#endif + } else { +#ifdef DEBUG + JS_ASSERT(!runtime->mainThreadHasCompilationLock); + runtime->mainThreadHasCompilationLock = true; +#endif + } + } + + public: + AutoLockForCompilation(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + if (cx->isJSContext()) + init(cx->asJSContext()->runtime()); + else + runtime = nullptr; + } + AutoLockForCompilation(jit::CompileCompartment *compartment MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoLockForCompilation() { + if (runtime) { + if (runtime->numCompilationThreads) { + JS_ASSERT(runtime->compilationLockOwner == PR_GetCurrentThread()); +#ifdef DEBUG + runtime->compilationLockOwner = nullptr; +#endif + PR_Unlock(runtime->compilationLock); + } else { +#ifdef DEBUG + JS_ASSERT(runtime->mainThreadHasCompilationLock); + runtime->mainThreadHasCompilationLock = false; +#endif + } + } + } +#else // JS_WORKER_THREADS + public: + AutoLockForCompilation(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + AutoLockForCompilation(jit::CompileCompartment *compartment MOZ_GUARD_OBJECT_NOTIFIER_PARAM) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + ~AutoLockForCompilation() { + // An empty destructor is needed to avoid warnings from clang about + // unused local variables of this type. + } +#endif // JS_WORKER_THREADS + + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + } /* namespace js */ #ifdef _MSC_VER diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 84f911528a8e..326593b23ef6 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -338,7 +338,7 @@ JSCompartment::wrap(JSContext *cx, MutableHandleObject obj, HandleObject existin return true; } - RootedObject proto(cx, Proxy::LazyProto); + RootedObject proto(cx, TaggedProto::LazyProto); RootedObject existing(cx, existingArg); if (existing) { /* Is it possible to reuse |existing|? */ diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 9d1624cb3e43..2eb8d021ae7f 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1124,25 +1124,21 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti // THING_ROOT_LAZY_SCRIPT). AutoSuppressGC suppressGC(cx); - fun->flags_ &= ~INTERPRETED_LAZY; - fun->flags_ |= INTERPRETED; - RootedScript script(cx, lazy->maybeScript()); if (script) { - fun->initScript(script); + AutoLockForCompilation lock(cx); + fun->setUnlazifiedScript(script); return true; } - fun->initScript(nullptr); - if (fun != lazy->function()) { script = lazy->function()->getOrCreateScript(cx); - if (!script) { - fun->initLazyScript(lazy); + if (!script) return false; - } - fun->initScript(script); + + AutoLockForCompilation lock(cx); + fun->setUnlazifiedScript(script); return true; } @@ -1162,17 +1158,19 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti if (script) { RootedObject enclosingScope(cx, lazy->enclosingScope()); RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, fun, script)); - if (!clonedScript) { - fun->initLazyScript(lazy); + if (!clonedScript) return false; - } clonedScript->setSourceObject(lazy->sourceObject()); fun->initAtom(script->function()->displayAtom()); - fun->initScript(clonedScript); clonedScript->setFunction(fun); + { + AutoLockForCompilation lock(cx); + fun->setUnlazifiedScript(clonedScript); + } + CallNewScriptHook(cx, clonedScript, fun); lazy->initScript(clonedScript); @@ -1184,18 +1182,14 @@ JSFunction::createScriptForLazilyInterpretedFunction(JSContext *cx, HandleFuncti // Parse and compile the script from source. SourceDataCache::AutoSuppressPurge asp(cx); const jschar *chars = lazy->source()->chars(cx, asp); - if (!chars) { - fun->initLazyScript(lazy); + if (!chars) return false; - } const jschar *lazyStart = chars + lazy->begin(); size_t lazyLength = lazy->end() - lazy->begin(); - if (!frontend::CompileLazyFunction(cx, lazy, lazyStart, lazyLength)) { - fun->initLazyScript(lazy); + if (!frontend::CompileLazyFunction(cx, lazy, lazyStart, lazyLength)) return false; - } script = fun->nonLazyScript(); diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 9638848e4aad..f0df951a511f 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -123,8 +123,6 @@ class JSFunction : public JSObject /* Possible attributes of an interpreted function: */ bool isFunctionPrototype() const { return flags() & IS_FUN_PROTO; } - bool isInterpretedLazy() const { return flags() & INTERPRETED_LAZY; } - bool hasScript() const { return flags() & INTERPRETED; } bool isExprClosure() const { return flags() & EXPR_CLOSURE; } bool hasGuessedAtom() const { return flags() & HAS_GUESSED_ATOM; } bool isLambda() const { return flags() & LAMBDA; } @@ -136,6 +134,17 @@ class JSFunction : public JSObject return flags() & SH_WRAPPABLE; } + // Functions can change between being lazily interpreted and having scripts + // when under the compilation lock. + bool isInterpretedLazy() const { + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); + return flags() & INTERPRETED_LAZY; + } + bool hasScript() const { + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); + return flags() & INTERPRETED; + } + bool hasJITCode() const { if (!hasScript()) return false; @@ -321,6 +330,7 @@ class JSFunction : public JSObject JSScript *nonLazyScript() const { JS_ASSERT(hasScript()); + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); return u.i.s.script_; } @@ -331,11 +341,13 @@ class JSFunction : public JSObject js::LazyScript *lazyScript() const { JS_ASSERT(isInterpretedLazy() && u.i.s.lazy_); + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); return u.i.s.lazy_; } js::LazyScript *lazyScriptOrNull() const { JS_ASSERT(isInterpretedLazy()); + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); return u.i.s.lazy_; } @@ -357,15 +369,25 @@ class JSFunction : public JSObject bool isStarGenerator() const { return generatorKind() == js::StarGenerator; } void setScript(JSScript *script_) { - JS_ASSERT(isInterpreted()); + JS_ASSERT(hasScript()); mutableScript() = script_; } void initScript(JSScript *script_) { - JS_ASSERT(isInterpreted()); + JS_ASSERT(hasScript()); mutableScript().init(script_); } + void setUnlazifiedScript(JSScript *script) { + // Note: createScriptForLazilyInterpretedFunction triggers a barrier on + // lazy script before it is overwritten here. + JS_ASSERT(js::CurrentThreadCanWriteCompilationData()); + JS_ASSERT(isInterpretedLazy()); + flags_ &= ~INTERPRETED_LAZY; + flags_ |= INTERPRETED; + initScript(script); + } + void initLazyScript(js::LazyScript *lazy) { JS_ASSERT(isInterpreted()); flags_ &= ~INTERPRETED; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 80d2b3fafd45..b7177ef208e9 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -5520,6 +5520,12 @@ AutoSuppressGC::AutoSuppressGC(JSCompartment *comp) suppressGC_++; } +AutoSuppressGC::AutoSuppressGC(JSRuntime *rt) + : suppressGC_(rt->mainThread.suppressGC) +{ + suppressGC_++; +} + bool js::UninlinedIsInsideNursery(JSRuntime *rt, const void *thing) { diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 9159f31ec024..9304c2dae940 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1398,6 +1398,7 @@ class AutoSuppressGC public: AutoSuppressGC(ExclusiveContext *cx); AutoSuppressGC(JSCompartment *comp); + AutoSuppressGC(JSRuntime *rt); ~AutoSuppressGC() { diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 7fe019b6902e..5bfec3ee4129 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -685,6 +685,8 @@ TypeScript::FreezeTypeSets(CompilerConstraintList *constraints, JSScript *script TemporaryTypeSet **pArgTypes, TemporaryTypeSet **pBytecodeTypes) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + LifoAlloc *alloc = constraints->alloc(); StackTypeSet *existing = script->types->typeArray(); @@ -791,13 +793,26 @@ CompilerConstraintInstance::generateTypeConstraint(JSContext *cx, RecompileIn const Class * TypeObjectKey::clasp() { - return isTypeObject() ? asTypeObject()->clasp : asSingleObject()->getClass(); + return isTypeObject() ? asTypeObject()->clasp() : asSingleObject()->getClass(); } TaggedProto TypeObjectKey::proto() { - return isTypeObject() ? TaggedProto(asTypeObject()->proto) : asSingleObject()->getTaggedProto(); + JS_ASSERT(hasTenuredProto()); + return isTypeObject() ? asTypeObject()->proto() : asSingleObject()->getTaggedProto(); +} + +bool +ObjectImpl::hasTenuredProto() const +{ + return type_->hasTenuredProto(); +} + +bool +TypeObjectKey::hasTenuredProto() +{ + return isTypeObject() ? asTypeObject()->hasTenuredProto() : asSingleObject()->hasTenuredProto(); } JSObject * @@ -836,6 +851,7 @@ HeapTypeSetKey TypeObjectKey::property(jsid id) { JS_ASSERT(!unknownProperties()); + JS_ASSERT(CurrentThreadCanReadCompilationData()); HeapTypeSetKey property; property.object_ = this; @@ -1429,8 +1445,10 @@ ObjectStateChange(ExclusiveContext *cxArg, TypeObject *object, bool markingUnkno HeapTypeSet *types = object->maybeGetProperty(JSID_EMPTY); /* Mark as unknown after getting the types, to avoid assertion. */ - if (markingUnknown) - object->flags |= OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES; + if (markingUnknown) { + AutoLockForCompilation lock(cxArg); + object->addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); + } if (types) { if (JSContext *cx = cxArg->maybeJSContext()) { @@ -1678,6 +1696,9 @@ TemporaryTypeSet::getCommonPrototype() if (!object) continue; + if (!object->hasTenuredProto()) + return nullptr; + TaggedProto nproto = object->proto(); if (proto) { if (nproto != proto) @@ -1737,18 +1758,24 @@ TypeZone::init(JSContext *cx) } TypeObject * -TypeCompartment::newTypeObject(ExclusiveContext *cx, const Class *clasp, Handle proto, bool unknown) +TypeCompartment::newTypeObject(ExclusiveContext *cx, const Class *clasp, Handle proto, + TypeObjectFlags initialFlags) { JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); + if (!cx->typeInferenceEnabled()) + initialFlags |= OBJECT_FLAG_UNKNOWN_MASK; + + if (cx->isJSContext()) { + if (proto.isObject() && IsInsideNursery(cx->asJSContext()->runtime(), proto.toObject())) + initialFlags |= OBJECT_FLAG_NURSERY_PROTO; + } + TypeObject *object = gc::NewGCThing(cx, gc::FINALIZE_TYPE_OBJECT, sizeof(TypeObject), gc::TenuredHeap); if (!object) return nullptr; - new(object) TypeObject(clasp, proto, unknown); - - if (!cx->typeInferenceEnabled()) - object->flags |= OBJECT_FLAG_UNKNOWN_MASK; + new(object) TypeObject(clasp, proto, initialFlags); return object; } @@ -1871,12 +1898,11 @@ TypeCompartment::addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey ke return nullptr; Rooted tagged(cx, TaggedProto(proto)); - res = newTypeObject(cx, GetClassForProtoKey(key.kind), tagged); + res = newTypeObject(cx, GetClassForProtoKey(key.kind), tagged, OBJECT_FLAG_FROM_ALLOCATION_SITE); if (!res) { cx->compartment()->types.setPendingNukeTypes(cx); return nullptr; } - res->flags |= OBJECT_FLAG_FROM_ALLOCATION_SITE; key.script = keyScript; } @@ -2010,6 +2036,8 @@ PrototypeHasIndexedProperty(CompilerConstraintList *constraints, JSObject *obj) HeapTypeSetKey index = type->property(JSID_VOID); if (index.configured(constraints) || index.isOwnProperty(constraints)) return true; + if (!obj->hasTenuredProto()) + return true; obj = obj->getProto(); } while (obj); @@ -2175,10 +2203,9 @@ void TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) { JS_ASSERT(this == &cx->compartment()->types); - JS_ASSERT(!(target->flags & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); + JS_ASSERT(!(target->flags() & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); JS_ASSERT(!target->singleton); JS_ASSERT(target->unknownProperties()); - target->flags |= OBJECT_FLAG_SETS_MARKED_UNKNOWN; AutoEnterAnalysis enter(cx); @@ -2219,6 +2246,9 @@ TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) } } } + + AutoLockForCompilation lock(cx); + target->addFlags(OBJECT_FLAG_SETS_MARKED_UNKNOWN); } void @@ -2511,7 +2541,7 @@ TypeCompartment::fixObjectType(ExclusiveContext *cx, JSObject *obj) ObjectTypeTable::AddPtr p = objectTypeTable->lookupForAdd(lookup); if (p) { - JS_ASSERT(obj->getProto() == p->value().object->proto); + JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); JS_ASSERT(obj->lastProperty() == p->value().shape); UpdateObjectTableEntryTypes(cx, p->value(), properties.begin(), properties.length()); @@ -2614,7 +2644,7 @@ TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t n cx->clearPendingException(); return nullptr; } - JS_ASSERT(obj->getProto() == p->value().object->proto); + JS_ASSERT(obj->getProto() == p->value().object->proto().toObject()); RootedShape shape(cx, p->value().shape); if (!JSObject::setLastProperty(cx, obj, shape)) { @@ -2635,6 +2665,36 @@ TypeCompartment::newTypedObject(JSContext *cx, IdValuePair *properties, size_t n // TypeObject ///////////////////////////////////////////////////////////////////// +#ifdef DEBUG +void +TypeObject::assertCanAccessProto() +{ + // The proto pointer for type objects representing singletons may move. + JS_ASSERT_IF(singleton, CurrentThreadCanReadCompilationData()); + + // Any proto pointer which is in the nursery may be moved, and may not be + // accessed during off thread compilation. +#if defined(JSGC_GENERATIONAL) && defined(JS_WORKER_THREADS) + PerThreadData *pt = TlsPerThreadData.get(); + TaggedProto proto(proto_); + JS_ASSERT_IF(proto.isObject() && !proto.toObject()->isTenured(), + !pt || !pt->ionCompiling); +#endif +} +#endif // DEBUG + +void +TypeObject::setProto(JSContext *cx, TaggedProto proto) +{ + JS_ASSERT(CurrentThreadCanWriteCompilationData()); + JS_ASSERT(singleton); + + if (proto.isObject() && IsInsideNursery(cx->runtime(), proto.toObject())) + addFlags(OBJECT_FLAG_NURSERY_PROTO); + + setProtoUnchecked(proto); +} + static inline void UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shape *shape, bool indexed) @@ -2644,7 +2704,8 @@ UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shap if (shape->hasGetterValue() || shape->hasSetterValue()) { types->setConfiguredProperty(cx); - types->addType(cx, Type::UnknownType()); + if (!types->TypeSet::addType(Type::UnknownType(), &cx->typeLifoAlloc())) + cx->compartment()->types.setPendingNukeTypes(cx); } else if (shape->hasDefaultGetter() && shape->hasSlot()) { if (!indexed && types->canSetDefinite(shape->slot())) types->setDefinite(shape->slot()); @@ -2658,7 +2719,8 @@ UpdatePropertyType(ExclusiveContext *cx, HeapTypeSet *types, JSObject *obj, Shap */ if (indexed || !value.isUndefined() || !CanHaveEmptyPropertyTypesForOwnProperty(obj)) { Type type = GetValueType(value); - types->addType(cx, type); + if (!types->TypeSet::addType(type, &cx->typeLifoAlloc())) + cx->compartment()->types.setPendingNukeTypes(cx); } } } @@ -2696,7 +2758,8 @@ TypeObject::addProperty(ExclusiveContext *cx, jsid id, Property **pprop) const Value &value = singleton->getDenseElement(i); if (!value.isMagic(JS_ELEMENTS_HOLE)) { Type type = GetValueType(value); - base->types.addType(cx, type); + if (!base->types.TypeSet::addType(type, &cx->typeLifoAlloc())) + cx->compartment()->types.setPendingNukeTypes(cx); } } } else if (!JSID_IS_EMPTY(id)) { @@ -2871,7 +2934,7 @@ TypeObject::markStateChange(ExclusiveContext *cxArg) void TypeObject::setFlags(ExclusiveContext *cx, TypeObjectFlags flags) { - if ((this->flags & flags) == flags) + if (hasAllFlags(flags)) return; AutoEnterAnalysis enter(cx); @@ -2882,7 +2945,10 @@ TypeObject::setFlags(ExclusiveContext *cx, TypeObjectFlags flags) singleton->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)); } - this->flags |= flags; + { + AutoLockForCompilation lock(cx); + addFlags(flags); + } InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeObjectString(this), flags); @@ -2897,7 +2963,7 @@ TypeObject::markUnknown(ExclusiveContext *cx) JS_ASSERT(cx->compartment()->activeAnalysis); JS_ASSERT(!unknownProperties()); - if (!(flags & OBJECT_FLAG_ADDENDUM_CLEARED)) + if (!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) clearAddendum(cx); InferSpew(ISpewOps, "UnknownProperties: %s", TypeObjectString(this)); @@ -2926,8 +2992,11 @@ TypeObject::markUnknown(ExclusiveContext *cx) void TypeObject::clearAddendum(ExclusiveContext *cx) { - JS_ASSERT(!(flags & OBJECT_FLAG_ADDENDUM_CLEARED)); - flags |= OBJECT_FLAG_ADDENDUM_CLEARED; + JS_ASSERT(!(flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); + { + AutoLockForCompilation lock(cx); + addFlags(OBJECT_FLAG_ADDENDUM_CLEARED); + } /* * It is possible for the object to not have a new script or other @@ -3072,11 +3141,11 @@ TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx) void TypeObject::print() { - TaggedProto tagged(proto); + TaggedProto tagged(proto()); fprintf(stderr, "%s : %s", - TypeObjectString(this), - tagged.isObject() ? TypeString(Type::ObjectType(proto)) - : (tagged.isLazy() ? "(lazy)" : "(null)")); + TypeObjectString(this), + tagged.isObject() ? TypeString(Type::ObjectType(tagged.toObject())) + : (tagged.isLazy() ? "(lazy)" : "(null)")); if (unknownProperties()) { fprintf(stderr, " unknown"); @@ -3142,7 +3211,7 @@ class TypeConstraintClearDefiniteGetterSetter : public TypeConstraint * non-writable, both of which are indicated by the source type set * being marked as configured. */ - if (!(object->flags & OBJECT_FLAG_ADDENDUM_CLEARED) && source->configuredProperty()) + if (!(object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) && source->configuredProperty()) object->clearAddendum(cx); } @@ -3166,7 +3235,7 @@ types::AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, TypeObject * * a permanent property in any transitive prototype, the definite * properties get cleared from the type. */ - RootedObject parent(cx, type->proto); + RootedObject parent(cx, type->proto().toObjectOrNull()); while (parent) { TypeObject *parentObject = parent->getType(cx); if (!parentObject || parentObject->unknownProperties()) @@ -3196,7 +3265,7 @@ class TypeConstraintClearDefiniteSingle : public TypeConstraint const char *kind() { return "clearDefiniteSingle"; } void newType(JSContext *cx, TypeSet *source, Type type) { - if (object->flags & OBJECT_FLAG_ADDENDUM_CLEARED) + if (object->flags() & OBJECT_FLAG_ADDENDUM_CLEARED) return; if (source->baseFlags() || source->getObjectCount() > 1) @@ -3279,9 +3348,9 @@ CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) } if (baseobj->slotSpan() == 0 || - !!(type->flags & OBJECT_FLAG_ADDENDUM_CLEARED)) + !!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)) { - if (type->addendum) + if (type->hasNewScript()) type->clearAddendum(cx); return; } @@ -3296,8 +3365,8 @@ CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) type->clearAddendum(cx); return; } - JS_ASSERT(!type->addendum); - JS_ASSERT(!(type->flags & OBJECT_FLAG_ADDENDUM_CLEARED)); + JS_ASSERT(!type->hasNewScript()); + JS_ASSERT(!(type->flags() & OBJECT_FLAG_ADDENDUM_CLEARED)); gc::AllocKind kind = gc::GetGCObjectKind(baseobj->slotSpan()); @@ -3336,7 +3405,11 @@ CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) newScript = (TypeNewScript *) cx->calloc_(numBytes); #endif new (newScript) TypeNewScript(); - type->addendum = newScript; + + { + AutoLockForCompilation lock(cx); + type->setAddendum(newScript); + } if (!newScript) { cx->compartment()->types.setPendingNukeTypes(cx); @@ -3633,8 +3706,11 @@ JSObject::splicePrototype(JSContext *cx, const Class *clasp, Handle return true; } - type->clasp = clasp; - type->proto = proto.raw(); + { + AutoLockForCompilation lock(cx); + type->setClasp(clasp); + type->setProto(cx, proto); + } return true; } @@ -3651,8 +3727,22 @@ JSObject::makeLazyType(JSContext *cx, HandleObject obj) if (!fun->getOrCreateScript(cx)) return nullptr; } + + // Find flags which need to be specified immediately on the object. + // Don't track whether singletons are packed. + TypeObjectFlags initialFlags = OBJECT_FLAG_NON_PACKED; + + if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)) + initialFlags |= OBJECT_FLAG_ITERATED; + + if (obj->isIndexed()) + initialFlags |= OBJECT_FLAG_SPARSE_INDEXES; + + if (obj->is() && obj->as().length() > INT32_MAX) + initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW; + Rooted proto(cx, obj->getTaggedProto()); - TypeObject *type = cx->compartment()->types.newTypeObject(cx, obj->getClass(), proto); + TypeObject *type = cx->compartment()->types.newTypeObject(cx, obj->getClass(), proto, initialFlags); if (!type) { if (cx->typeInferenceEnabled()) cx->compartment()->types.setPendingNukeTypes(cx); @@ -3674,24 +3764,10 @@ JSObject::makeLazyType(JSContext *cx, HandleObject obj) if (obj->is() && obj->as().isInterpreted()) type->interpretedFunction = &obj->as(); - if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON)) - type->flags |= OBJECT_FLAG_ITERATED; - - /* - * Adjust flags for objects which will have the wrong flags set by just - * looking at the class prototype key. - */ - - /* Don't track whether singletons are packed. */ - type->flags |= OBJECT_FLAG_NON_PACKED; - - if (obj->isIndexed()) - type->flags |= OBJECT_FLAG_SPARSE_INDEXES; - - if (obj->is() && obj->as().length() > INT32_MAX) - type->flags |= OBJECT_FLAG_LENGTH_OVERFLOW; - - obj->type_ = type; + { + AutoLockForCompilation lock(cx); + obj->type_ = type; + } return type; } @@ -3707,8 +3783,8 @@ TypeObjectWithNewScriptEntry::hash(const Lookup &lookup) /* static */ inline bool TypeObjectWithNewScriptEntry::match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup) { - return key.object->proto == lookup.matchProto.raw() && - key.object->clasp == lookup.clasp && + return key.object->proto() == lookup.matchProto && + key.object->clasp() == lookup.clasp && key.newFunction == lookup.newFunction; } @@ -3805,8 +3881,8 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction * newTypeObjects.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, fun)); if (p) { TypeObject *type = p->object; - JS_ASSERT(type->clasp == clasp); - JS_ASSERT(type->proto.get() == proto.raw()); + JS_ASSERT(type->clasp() == clasp); + JS_ASSERT(type->proto() == proto); JS_ASSERT_IF(type->hasNewScript(), type->newScript()->fun == fun); return type; } @@ -3816,13 +3892,19 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction * if (proto.isObject() && !proto.toObject()->setDelegate(this)) return nullptr; - bool markUnknown = - proto.isObject() - ? proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN) - : true; + TypeObjectFlags initialFlags = 0; + if (!proto.isObject() || proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)) { + // The new type is not present in any type sets, so mark the object as + // unknown in all type sets it appears in. This allows the prototype of + // such objects to mutate freely without triggering an expensive walk of + // the compartment's type sets. (While scripts normally don't mutate + // __proto__, the browser will for proxies and such, and we need to + // accommodate this behavior). + initialFlags = OBJECT_FLAG_UNKNOWN_MASK | OBJECT_FLAG_SETS_MARKED_UNKNOWN; + } Rooted protoRoot(this, proto); - TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, markUnknown); + TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, initialFlags); if (!type) return nullptr; @@ -3872,17 +3954,6 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction * } } - /* - * The new type is not present in any type sets, so mark the object as - * unknown in all type sets it appears in. This allows the prototype of - * such objects to mutate freely without triggering an expensive walk of - * the compartment's type sets. (While scripts normally don't mutate - * __proto__, the browser will for proxies and such, and we need to - * accommodate this behavior). - */ - if (type->unknownProperties()) - type->flags |= OBJECT_FLAG_SETS_MARKED_UNKNOWN; - return type; } @@ -3907,7 +3978,7 @@ ExclusiveContext::getLazyType(const Class *clasp, TaggedProto proto) } Rooted protoRoot(this, proto); - TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, false); + TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot); if (!type) return nullptr; @@ -4159,8 +4230,8 @@ JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) { e.removeFront(); } else if (entry.object != e.front().object) { - TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp, - entry.object->proto.get(), + TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp(), + entry.object->proto(), entry.newFunction); e.rekeyFront(lookup, entry); } @@ -4428,7 +4499,7 @@ TypeObject::addTypedObjectAddendum(JSContext *cx, JS_ASSERT(repr); - if (flags & OBJECT_FLAG_ADDENDUM_CLEARED) + if (flags() & OBJECT_FLAG_ADDENDUM_CLEARED) return true; JS_ASSERT(!unknownProperties()); diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index e5d8ac01cfc3..ff7c27970b00 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -25,20 +25,38 @@ namespace js { +#ifdef DEBUG +bool CurrentThreadCanWriteCompilationData(); +bool CurrentThreadCanReadCompilationData(); +#endif + class TypeRepresentation; class TaggedProto { public: + static JSObject * const LazyProto; + TaggedProto() : proto(nullptr) {} TaggedProto(JSObject *proto) : proto(proto) {} uintptr_t toWord() const { return uintptr_t(proto); } - inline bool isLazy() const; - inline bool isObject() const; - inline JSObject *toObject() const; - inline JSObject *toObjectOrNull() const; + bool isLazy() const { + return proto == LazyProto; + } + bool isObject() const { + /* Skip nullptr and LazyProto. */ + return uintptr_t(proto) > uintptr_t(TaggedProto::LazyProto); + } + JSObject *toObject() const { + JS_ASSERT(isObject()); + return proto; + } + JSObject *toObjectOrNull() const { + JS_ASSERT(!proto || isObject()); + return proto; + } JSObject *raw() const { return proto; } bool operator ==(const TaggedProto &other) { return proto == other.proto; } @@ -77,10 +95,10 @@ class TaggedProtoOperations public: uintptr_t toWord() const { return value()->toWord(); } - inline bool isLazy() const; - inline bool isObject() const; - inline JSObject *toObject() const; - inline JSObject *toObjectOrNull() const; + inline bool isLazy() const { return value()->isLazy(); } + inline bool isObject() const { return value()->isObject(); } + inline JSObject *toObject() const { return value()->toObject(); } + inline JSObject *toObjectOrNull() const { return value()->toObjectOrNull(); } JSObject *raw() const { return value()->raw(); } }; @@ -383,6 +401,12 @@ enum MOZ_ENUM_TYPE(uint32_t) { /* If set, addendum information should not be installed on this object. */ OBJECT_FLAG_ADDENDUM_CLEARED = 0x2, + /* + * If set, the object's prototype might be in the nursery and can't be + * used during Ion compilation (which may be occurring off thread). + */ + OBJECT_FLAG_NURSERY_PROTO = 0x4, + /* * Whether we have ensured all type sets in the compartment contain * ANYOBJECT instead of this object. @@ -503,7 +527,7 @@ class TypeSet static TemporaryTypeSet *unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc); /* Add a type to this set using the specified allocator. */ - inline bool addType(Type type, LifoAlloc *alloc, bool *padded = nullptr); + inline bool addType(Type type, LifoAlloc *alloc); /* Get a list of all types in this set. */ typedef Vector TypeList; @@ -857,11 +881,45 @@ struct TypeTypedObject : public TypeObjectAddendum /* Type information about an object accessed by a script. */ struct TypeObject : gc::BarrieredCell { - /* Class shared by objects using this type. */ - const Class *clasp; + private: + /* Class shared by object using this type. */ + const Class *clasp_; /* Prototype shared by objects using this type. */ - HeapPtrObject proto; + HeapPtrObject proto_; + +#ifdef DEBUG + void assertCanAccessProto(); +#else + void assertCanAccessProto() {} +#endif + + public: + + const Class *clasp() { + return clasp_; + } + + void setClasp(const Class *clasp) { + JS_ASSERT(CurrentThreadCanWriteCompilationData()); + JS_ASSERT(singleton); + clasp_ = clasp; + } + + TaggedProto proto() { + assertCanAccessProto(); + return TaggedProto(proto_); + } + + HeapPtrObject &protoRaw() { + // For use during marking, don't call otherwise. + return proto_; + } + + void setProto(JSContext *cx, TaggedProto proto); + void setProtoUnchecked(TaggedProto proto) { + proto_ = proto.raw(); + } /* * Whether there is a singleton JS object with this type. That JS object @@ -877,8 +935,9 @@ struct TypeObject : gc::BarrieredCell static const size_t LAZY_SINGLETON = 1; bool lazy() const { return singleton == (JSObject *) LAZY_SINGLETON; } + private: /* Flags for this object. */ - TypeObjectFlags flags; + TypeObjectFlags flags_; /* * This field allows various special classes of objects to attach @@ -891,23 +950,48 @@ struct TypeObject : gc::BarrieredCell * before the object escapes. */ HeapPtr addendum; + public: + + TypeObjectFlags flags() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); + return flags_; + } + + void addFlags(TypeObjectFlags flags) { + JS_ASSERT(CurrentThreadCanWriteCompilationData()); + flags_ |= flags; + } + + void clearFlags(TypeObjectFlags flags) { + JS_ASSERT(CurrentThreadCanWriteCompilationData()); + flags_ &= ~flags; + } bool hasNewScript() const { + JS_ASSERT(CurrentThreadCanReadCompilationData()); return addendum && addendum->isNewScript(); } TypeNewScript *newScript() { + JS_ASSERT(CurrentThreadCanReadCompilationData()); return addendum->asNewScript(); } bool hasTypedObject() { + JS_ASSERT(CurrentThreadCanReadCompilationData()); return addendum && addendum->isTypedObject(); } TypeTypedObject *typedObject() { + JS_ASSERT(CurrentThreadCanReadCompilationData()); return addendum->asTypedObject(); } + void setAddendum(TypeObjectAddendum *addendum) { + JS_ASSERT(CurrentThreadCanWriteCompilationData()); + this->addendum = addendum; + } + /* * Tag the type object for a binary data type descriptor, instance, * or handle with the type representation of the data it points at. @@ -919,6 +1003,7 @@ struct TypeObject : gc::BarrieredCell TypeTypedObject::Kind kind , TypeRepresentation *repr); + private: /* * Properties of this object. This may contain JSID_VOID, representing the * types of all integer indexes of the object, and/or JSID_EMPTY, holding @@ -953,6 +1038,7 @@ struct TypeObject : gc::BarrieredCell * might update the property with a new type. */ Property **propertySet; + public: /* If this is an interpreted function, the function object. */ HeapPtrFunction interpretedFunction; @@ -961,27 +1047,31 @@ struct TypeObject : gc::BarrieredCell uint32_t padding; #endif - inline TypeObject(const Class *clasp, TaggedProto proto, bool unknown); + inline TypeObject(const Class *clasp, TaggedProto proto, TypeObjectFlags initialFlags); bool hasAnyFlags(TypeObjectFlags flags) { JS_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags); - return !!(this->flags & flags); + return !!(this->flags() & flags); } bool hasAllFlags(TypeObjectFlags flags) { JS_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags); - return (this->flags & flags) == flags; + return (this->flags() & flags) == flags; } bool unknownProperties() { - JS_ASSERT_IF(flags & OBJECT_FLAG_UNKNOWN_PROPERTIES, + JS_ASSERT_IF(flags() & OBJECT_FLAG_UNKNOWN_PROPERTIES, hasAllFlags(OBJECT_FLAG_DYNAMIC_MASK)); - return !!(flags & OBJECT_FLAG_UNKNOWN_PROPERTIES); + return !!(flags() & OBJECT_FLAG_UNKNOWN_PROPERTIES); } bool shouldPreTenure() { return hasAnyFlags(OBJECT_FLAG_PRE_TENURE) && !unknownProperties(); } + bool hasTenuredProto() const { + return !(flags() & OBJECT_FLAG_NURSERY_PROTO); + } + gc::InitialHeap initialHeap(CompilerConstraintList *constraints); bool canPreTenure() { @@ -991,7 +1081,7 @@ struct TypeObject : gc::BarrieredCell // this bit reliably. if (unknownProperties()) return false; - return (flags & OBJECT_FLAG_FROM_ALLOCATION_SITE) || hasNewScript(); + return (flags() & OBJECT_FLAG_FROM_ALLOCATION_SITE) || hasNewScript(); } void setShouldPreTenure(ExclusiveContext *cx) { @@ -1046,12 +1136,20 @@ struct TypeObject : gc::BarrieredCell static inline ThingRootKind rootKind() { return THING_ROOT_TYPE_OBJECT; } + static inline uint32_t offsetOfClasp() { + return offsetof(TypeObject, clasp_); + } + + static inline uint32_t offsetOfProto() { + return offsetof(TypeObject, proto_); + } + private: inline uint32_t basePropertyCount() const; inline void setBasePropertyCount(uint32_t count); static void staticAsserts() { - JS_STATIC_ASSERT(offsetof(TypeObject, proto) == offsetof(js::shadow::TypeObject, proto)); + JS_STATIC_ASSERT(offsetof(TypeObject, proto_) == offsetof(js::shadow::TypeObject, proto)); } }; @@ -1247,6 +1345,7 @@ struct TypeObjectKey const Class *clasp(); TaggedProto proto(); + bool hasTenuredProto(); JSObject *singleton(); TypeNewScript *newScript(); @@ -1416,7 +1515,7 @@ struct TypeCompartment * js_ObjectClass). */ TypeObject *newTypeObject(ExclusiveContext *cx, const Class *clasp, Handle proto, - bool unknown = false); + TypeObjectFlags initialFlags = 0); /* Get or make an object for an allocation site, and add to the allocation site table. */ TypeObject *addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey key); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index 5d6f7b38f608..ab4186e83ef9 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -25,61 +25,6 @@ #include "jsanalyzeinlines.h" #include "jscntxtinlines.h" -inline bool -js::TaggedProto::isObject() const -{ - /* Skip nullptr and Proxy::LazyProto. */ - return uintptr_t(proto) > uintptr_t(Proxy::LazyProto); -} - -inline bool -js::TaggedProto::isLazy() const -{ - return proto == Proxy::LazyProto; -} - -inline JSObject * -js::TaggedProto::toObject() const -{ - JS_ASSERT(isObject()); - return proto; -} - -inline JSObject * -js::TaggedProto::toObjectOrNull() const -{ - JS_ASSERT(!proto || isObject()); - return proto; -} - -template -inline bool -js::TaggedProtoOperations::isLazy() const -{ - return value()->isLazy(); -} - -template -inline bool -js::TaggedProtoOperations::isObject() const -{ - return value()->isObject(); -} - -template -inline JSObject * -js::TaggedProtoOperations::toObject() const -{ - return value()->toObject(); -} - -template -inline JSObject * -js::TaggedProtoOperations::toObjectOrNull() const -{ - return value()->toObjectOrNull(); -} - namespace js { namespace types { @@ -534,7 +479,7 @@ MarkTypeObjectUnknownProperties(JSContext *cx, TypeObject *obj, if (cx->typeInferenceEnabled()) { if (!obj->unknownProperties()) obj->markUnknown(cx); - if (markSetsUnknown && !(obj->flags & OBJECT_FLAG_SETS_MARKED_UNKNOWN)) + if (markSetsUnknown && !(obj->flags() & OBJECT_FLAG_SETS_MARKED_UNKNOWN)) cx->compartment()->types.markSetsUnknown(cx, obj); } } @@ -605,7 +550,8 @@ TypeScript::NumTypeSets(JSScript *script) /* static */ inline StackTypeSet * TypeScript::ThisTypes(JSScript *script) { - return script->types->typeArray() + script->nTypeSets() + js::analyze::ThisSlot(); + JS_ASSERT(CurrentThreadCanReadCompilationData()); + return script->types->typeArray() + script->nTypeSets() + analyze::ThisSlot(); } /* @@ -618,7 +564,8 @@ TypeScript::ThisTypes(JSScript *script) TypeScript::ArgTypes(JSScript *script, unsigned i) { JS_ASSERT(i < script->function()->nargs()); - return script->types->typeArray() + script->nTypeSets() + js::analyze::ArgSlot(i); + JS_ASSERT(CurrentThreadCanReadCompilationData()); + return script->types->typeArray() + script->nTypeSets() + analyze::ArgSlot(i); } template @@ -1089,10 +1036,8 @@ TypeSet::clearObjects() } bool -TypeSet::addType(Type type, LifoAlloc *alloc, bool *padded) +TypeSet::addType(Type type, LifoAlloc *alloc) { - JS_ASSERT_IF(padded, !*padded); - if (unknown()) return true; @@ -1100,8 +1045,6 @@ TypeSet::addType(Type type, LifoAlloc *alloc, bool *padded) flags |= TYPE_FLAG_BASE_MASK; clearObjects(); JS_ASSERT(unknown()); - if (padded) - *padded = true; return true; } @@ -1115,8 +1058,6 @@ TypeSet::addType(Type type, LifoAlloc *alloc, bool *padded) flag |= TYPE_FLAG_INT32; flags |= flag; - if (padded) - *padded = true; return true; } @@ -1156,8 +1097,6 @@ TypeSet::addType(Type type, LifoAlloc *alloc, bool *padded) clearObjects(); } - if (padded) - *padded = true; return true; } @@ -1166,13 +1105,16 @@ ConstraintTypeSet::addType(ExclusiveContext *cxArg, Type type) { JS_ASSERT(cxArg->compartment()->activeAnalysis); - bool added = false; - if (!TypeSet::addType(type, &cxArg->typeLifoAlloc(), &added)) { - cxArg->compartment()->types.setPendingNukeTypes(cxArg); + if (hasType(type)) return; + + { + AutoLockForCompilation lock(cxArg); + if (!TypeSet::addType(type, &cxArg->typeLifoAlloc())) { + cxArg->compartment()->types.setPendingNukeTypes(cxArg); + return; + } } - if (!added) - return; InferSpew(ISpewOps, "addType: %sT%p%s %s", InferSpewColor(this), this, InferSpewColorReset(), @@ -1275,7 +1217,7 @@ TypeSet::getObjectClass(unsigned i) const if (JSObject *object = getSingleObject(i)) return object->getClass(); if (TypeObject *object = getTypeObject(i)) - return object->clasp; + return object->clasp(); return nullptr; } @@ -1283,18 +1225,16 @@ TypeSet::getObjectClass(unsigned i) const // TypeObject ///////////////////////////////////////////////////////////////////// -inline TypeObject::TypeObject(const Class *clasp, TaggedProto proto, bool unknown) +inline TypeObject::TypeObject(const Class *clasp, TaggedProto proto, TypeObjectFlags initialFlags) { mozilla::PodZero(this); /* Inner objects may not appear on prototype chains. */ JS_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject); - this->clasp = clasp; - this->proto = proto.raw(); - - if (unknown) - flags |= OBJECT_FLAG_UNKNOWN_MASK; + this->clasp_ = clasp; + this->proto_ = proto.raw(); + this->flags_ = initialFlags; InferSpew(ISpewOps, "newObject: %s", TypeObjectString(this)); } @@ -1302,15 +1242,17 @@ inline TypeObject::TypeObject(const Class *clasp, TaggedProto proto, bool unknow inline uint32_t TypeObject::basePropertyCount() const { - return (flags & OBJECT_FLAG_PROPERTY_COUNT_MASK) >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT; + JS_ASSERT(CurrentThreadCanReadCompilationData()); + return (flags() & OBJECT_FLAG_PROPERTY_COUNT_MASK) >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT; } inline void TypeObject::setBasePropertyCount(uint32_t count) { + // Note: Callers must ensure they are performing threadsafe operations. JS_ASSERT(count <= OBJECT_FLAG_PROPERTY_COUNT_LIMIT); - flags = (flags & ~OBJECT_FLAG_PROPERTY_COUNT_MASK) - | (count << OBJECT_FLAG_PROPERTY_COUNT_SHIFT); + flags_ = (flags() & ~OBJECT_FLAG_PROPERTY_COUNT_MASK) + | (count << OBJECT_FLAG_PROPERTY_COUNT_SHIFT); } inline HeapTypeSet * @@ -1322,36 +1264,46 @@ TypeObject::getProperty(ExclusiveContext *cx, jsid id) JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == IdToTypeId(id)); JS_ASSERT(!unknownProperties()); - uint32_t propertyCount = basePropertyCount(); - Property **pprop = HashSetInsert - (cx->typeLifoAlloc(), propertySet, propertyCount, id); - if (!pprop) { - cx->compartment()->types.setPendingNukeTypes(cx); - return nullptr; - } + if (HeapTypeSet *types = maybeGetProperty(id)) + return types; + + uint32_t propertyCount; + Property **pprop; + { + AutoLockForCompilation lock(cx); + + propertyCount = basePropertyCount(); + pprop = HashSetInsert + (cx->typeLifoAlloc(), propertySet, propertyCount, id); + if (!pprop) { + cx->compartment()->types.setPendingNukeTypes(cx); + return nullptr; + } + + JS_ASSERT(!*pprop); - if (!*pprop) { setBasePropertyCount(propertyCount); if (!addProperty(cx, id, pprop)) { setBasePropertyCount(0); propertySet = nullptr; return nullptr; } - if (propertyCount == OBJECT_FLAG_PROPERTY_COUNT_LIMIT) { - markUnknown(cx); + } - /* - * Return an arbitrary property in the object, as all have unknown - * type and are treated as configured. - */ - unsigned count = getPropertyCount(); - for (unsigned i = 0; i < count; i++) { - if (Property *prop = getProperty(i)) - return &prop->types; - } + if (propertyCount == OBJECT_FLAG_PROPERTY_COUNT_LIMIT) { + markUnknown(cx); - MOZ_ASSUME_UNREACHABLE("Missing property"); + /* + * Return an arbitrary property in the object, as all have unknown + * type and are treated as configured. + */ + unsigned count = getPropertyCount(); + for (unsigned i = 0; i < count; i++) { + if (Property *prop = getProperty(i)) + return &prop->types; } + + MOZ_ASSUME_UNREACHABLE("Missing property"); } return &(*pprop)->types; @@ -1363,6 +1315,7 @@ TypeObject::maybeGetProperty(jsid id) JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id)); JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == IdToTypeId(id)); JS_ASSERT(!unknownProperties()); + JS_ASSERT(CurrentThreadCanReadCompilationData()); Property *prop = HashSetLookup (propertySet, basePropertyCount(), id); diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 00a2ebc25cc8..d45705cdf3c1 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -1937,6 +1937,7 @@ GlobalObject::initIteratorClasses(JSContext *cx, Handle global) if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto)) return false; + AutoLockForCompilation lock(cx); global->setSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto)); global->setConstructor(JSProto_GeneratorFunction, ObjectValue(*genFunction)); global->setPrototype(JSProto_GeneratorFunction, ObjectValue(*genFunctionProto)); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index a9515a889c35..61ff5de58012 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1273,7 +1273,7 @@ NewObject(ExclusiveContext *cx, const Class *clasp, types::TypeObject *type_, JS if (!NewObjectMetadata(cx, &metadata)) return nullptr; - RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(type->proto), + RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, type->proto(), parent, metadata, kind)); if (!shape) return nullptr; @@ -1457,7 +1457,7 @@ js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc NewObjectCache &cache = cx->runtime()->newObjectCache; NewObjectCache::EntryIndex entry = -1; - if (parent == type->proto->getParent() && + if (parent == type->proto().toObject()->getParent() && newKind == GenericObject && !cx->compartment()->hasObjectMetadataCallback()) { @@ -1985,9 +1985,12 @@ JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved & * Swap the object's types, to restore their initial type information. * The prototypes and classes of the objects were swapped in ReserveForTradeGuts. */ - TypeObject *tmp = a->type_; - a->type_ = b->type_; - b->type_ = tmp; + { + AutoLockForCompilation lock(cx); + TypeObject *tmp = a->type_; + a->type_ = b->type_; + b->type_ = tmp; + } /* Don't try to swap a JSFunction for a plain function JSObject. */ JS_ASSERT_IF(a->is(), a->tenuredSizeOfThis() == b->tenuredSizeOfThis()); @@ -2019,9 +2022,12 @@ JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved & char tmp[mozilla::tl::Max::value]; JS_ASSERT(size <= sizeof(tmp)); - js_memcpy(tmp, a, size); - js_memcpy(a, b, size); - js_memcpy(b, tmp, size); + { + AutoLockForCompilation lock(cx); + js_memcpy(tmp, a, size); + js_memcpy(a, b, size); + js_memcpy(b, tmp, size); + } #ifdef JSGC_GENERATIONAL /* @@ -2059,9 +2065,12 @@ JSObject::TradeGuts(JSContext *cx, JSObject *a, JSObject *b, TradeGutsReserved & void *bpriv = b->hasPrivate() ? b->getPrivate() : nullptr; char tmp[sizeof(JSObject)]; - js_memcpy(&tmp, a, sizeof tmp); - js_memcpy(a, b, sizeof tmp); - js_memcpy(b, &tmp, sizeof tmp); + { + AutoLockForCompilation lock(cx); + js_memcpy(&tmp, a, sizeof tmp); + js_memcpy(a, b, sizeof tmp); + js_memcpy(b, &tmp, sizeof tmp); + } if (a->isNative()) a->shape_->setNumFixedSlots(reserved.newafixed); @@ -2967,7 +2976,11 @@ js::SetClassAndProto(JSContext *cx, HandleObject obj, MarkTypeObjectUnknownProperties(cx, obj->type(), true); MarkTypeObjectUnknownProperties(cx, type, true); - obj->setType(type); + { + AutoLockForCompilation lock(cx); + obj->setType(type); + } + *succeeded = true; return true; } @@ -4030,7 +4043,7 @@ NativeGetInline(JSContext *cx, case JSOP_GETPROP: case JSOP_CALLPROP: case JSOP_LENGTH: - script->baselineScript()->noteAccessedGetter(script->pcToOffset(pc)); + script->baselineScript()->noteAccessedGetter(cx, script->pcToOffset(pc)); break; default: break; diff --git a/js/src/jsobj.h b/js/src/jsobj.h index fabb240c24c5..0c4ffa3edd08 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -474,7 +474,7 @@ class JSObject : public js::ObjectImpl bool uninlinedIsProxy() const; JSObject *getProto() const { JS_ASSERT(!uninlinedIsProxy()); - return js::ObjectImpl::getProto(); + return getTaggedProto().toObjectOrNull(); } static inline bool getProto(JSContext *cx, js::HandleObject obj, js::MutableHandleObject protop); diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 71ca517b5689..674d4ea03c17 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -393,6 +393,9 @@ JSObject::clearType(JSContext *cx, js::HandleObject obj) inline void JSObject::setType(js::types::TypeObject *newType) { + // Note: This is usually called for newly created objects that haven't + // escaped to script yet, so don't require that the compilation lock be + // held here. JS_ASSERT(newType); JS_ASSERT(!hasSingletonType()); type_ = newType; @@ -405,7 +408,7 @@ JSObject::getProto(JSContext *cx, js::HandleObject obj, js::MutableHandleObject JS_ASSERT(obj->is()); return js::Proxy::getPrototypeOf(cx, obj, protop); } else { - protop.set(obj->js::ObjectImpl::getProto()); + protop.set(obj->getTaggedProto().toObjectOrNull()); return true; } } @@ -461,11 +464,11 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi * make sure their presence is consistent with the shape. */ JS_ASSERT(shape && type); - JS_ASSERT(type->clasp == shape->getObjectClass()); - JS_ASSERT(type->clasp != &js::ArrayObject::class_); - JS_ASSERT(js::gc::GetGCKindSlots(kind, type->clasp) == shape->numFixedSlots()); - JS_ASSERT_IF(type->clasp->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind)); - JS_ASSERT_IF(type->clasp->finalize, heap == js::gc::TenuredHeap); + JS_ASSERT(type->clasp() == shape->getObjectClass()); + JS_ASSERT(type->clasp() != &js::ArrayObject::class_); + JS_ASSERT(js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots()); + JS_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind)); + JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap); JS_ASSERT_IF(extantSlots, dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan())); js::HeapSlot *slots = extantSlots; @@ -495,7 +498,7 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi obj->slots = slots; obj->elements = js::emptyObjectElements; - const js::Class *clasp = type->clasp; + const js::Class *clasp = type->clasp(); if (clasp->hasPrivate()) obj->privateRef(shape->numFixedSlots()) = nullptr; @@ -512,9 +515,9 @@ JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc:: uint32_t length) { JS_ASSERT(shape && type); - JS_ASSERT(type->clasp == shape->getObjectClass()); - JS_ASSERT(type->clasp == &js::ArrayObject::class_); - JS_ASSERT_IF(type->clasp->finalize, heap == js::gc::TenuredHeap); + JS_ASSERT(type->clasp() == shape->getObjectClass()); + JS_ASSERT(type->clasp() == &js::ArrayObject::class_); + JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap); /* * Arrays use their fixed slots to store elements, and must have enough @@ -979,8 +982,11 @@ DefineConstructorAndPrototype(JSContext *cx, Handle global, JS_ASSERT(!global->nativeLookup(cx, id)); /* Set these first in case AddTypePropertyId looks for this class. */ - global->setConstructor(key, ObjectValue(*ctor)); - global->setPrototype(key, ObjectValue(*proto)); + { + AutoLockForCompilation lock(cx); + global->setConstructor(key, ObjectValue(*ctor)); + global->setPrototype(key, ObjectValue(*proto)); + } global->setConstructorPropertySlot(key, ObjectValue(*ctor)); if (!global->addDataProperty(cx, id, GlobalObject::constructorPropertySlot(key), 0)) { diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index b1d4dcdd6605..1b1582516c2b 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -2748,7 +2748,7 @@ Proxy::defaultValue(JSContext *cx, HandleObject proxy, JSType hint, MutableHandl return proxy->as().handler()->defaultValue(cx, proxy, hint, vp); } -JSObject * const Proxy::LazyProto = reinterpret_cast(0x1); +JSObject * const TaggedProto::LazyProto = reinterpret_cast(0x1); bool Proxy::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject proto) diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h index 12cc9ceab9d1..e463388fd8d7 100644 --- a/js/src/jsproxy.h +++ b/js/src/jsproxy.h @@ -324,8 +324,6 @@ class Proxy /* IC entry path for handling __noSuchMethod__ on access. */ static bool callProp(JSContext *cx, HandleObject proxy, HandleObject reveiver, HandleId id, MutableHandleValue vp); - - static JSObject * const LazyProto; }; // Use these in places where you don't want to #include vm/ProxyObject.h. diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 7c25e8dbabe8..30c7feebe25f 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -3015,7 +3015,10 @@ JSScript::argumentsOptimizationFailed(JSContext *cx, HandleScript script) JS_ASSERT(!script->isGenerator()); - script->needsArgsObj_ = true; + { + AutoLockForCompilation lock(cx); + script->needsArgsObj_ = true; + } #ifdef JS_ION /* diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 93c309e498ca..7f03e3185827 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -977,7 +977,11 @@ class JSScript : public js::gc::BarrieredCell * that needsArgsObj is only called after the script has been analyzed. */ bool analyzedArgsUsage() const { return !needsArgsAnalysis_; } - bool needsArgsObj() const { JS_ASSERT(analyzedArgsUsage()); return needsArgsObj_; } + bool needsArgsObj() const { + JS_ASSERT(analyzedArgsUsage()); + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); + return needsArgsObj_; + } void setNeedsArgsObj(bool needsArgsObj); static bool argumentsOptimizationFailed(JSContext *cx, js::HandleScript script); @@ -1027,6 +1031,7 @@ class JSScript : public js::gc::BarrieredCell } bool hasBaselineScript() const { + JS_ASSERT(js::CurrentThreadCanReadCompilationData()); return baseline && baseline != BASELINE_DISABLED_SCRIPT; } bool canBaselineCompile() const { @@ -1036,7 +1041,7 @@ class JSScript : public js::gc::BarrieredCell JS_ASSERT(hasBaselineScript()); return baseline; } - inline void setBaselineScript(js::jit::BaselineScript *baselineScript); + inline void setBaselineScript(JSContext *maybecx, js::jit::BaselineScript *baselineScript); void updateBaselineOrIonRaw(); diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index b498a626243a..67398e8d4f8b 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -125,11 +125,14 @@ JSScript::setIsCallsiteClone(JSObject *fun) { } inline void -JSScript::setBaselineScript(js::jit::BaselineScript *baselineScript) { +JSScript::setBaselineScript(JSContext *maybecx, js::jit::BaselineScript *baselineScript) { #ifdef JS_ION if (hasBaselineScript()) js::jit::BaselineScript::writeBarrierPre(tenuredZone(), baseline); #endif + mozilla::Maybe lock; + if (maybecx) + lock.construct(maybecx); baseline = baselineScript; updateBaselineOrIonRaw(); } diff --git a/js/src/jsworkers.cpp b/js/src/jsworkers.cpp index 0136601f6a2e..09abb061397c 100644 --- a/js/src/jsworkers.cpp +++ b/js/src/jsworkers.cpp @@ -91,6 +91,8 @@ js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder) if (!state.ionWorklist.append(builder)) return false; + cx->runtime()->addCompilationThread(); + state.notifyAll(WorkerThreadState::PRODUCER); return true; } @@ -615,7 +617,7 @@ WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *toke iter.next()) { types::TypeObject *object = iter.get(); - TaggedProto proto(object->proto); + TaggedProto proto(object->proto()); if (!proto.isObject()) continue; @@ -626,7 +628,9 @@ WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *toke JSObject *newProto = GetClassPrototypePure(&parseTask->scopeChain->global(), key); JS_ASSERT(newProto); - object->proto = newProto; + // Note: this is safe to do without requiring the compilation lock, as + // the new type is not yet available to compilation threads. + object->setProtoUnchecked(newProto); } // Move the parsed script and all its contents into the desired compartment. @@ -760,7 +764,11 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state) jit::IonContext ictx(jit::CompileRuntime::get(runtime), jit::CompileCompartment::get(ionBuilder->script()->compartment()), &ionBuilder->alloc()); - ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder)); + AutoEnterIonCompilation ionCompiling; + bool succeeded = ionBuilder->build(); + ionBuilder->clearForBackEnd(); + if (succeeded) + ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder)); } state.lock(); diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 7a2e1c2c1587..413c630ecf13 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -48,7 +48,7 @@ Wrapper::New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler) RootedValue priv(cx, ObjectValue(*obj)); ProxyOptions options; options.setCallable(obj->isCallable()); - return NewProxyObject(cx, handler, priv, Proxy::LazyProto, parent, options); + return NewProxyObject(cx, handler, priv, TaggedProto::LazyProto, parent, options); } JSObject * @@ -141,7 +141,7 @@ js::TransparentObjectWrapper(JSContext *cx, HandleObject existing, HandleObject { // Allow wrapping outer window proxies. JS_ASSERT(!obj->is() || obj->getClass()->ext.innerObject); - JS_ASSERT(wrappedProto == Proxy::LazyProto); + JS_ASSERT(wrappedProto == TaggedProto::LazyProto); return Wrapper::New(cx, obj, parent, &CrossCompartmentWrapper::singleton); } diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index aff455ebc495..954e08b7a1d8 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -669,7 +669,7 @@ GlobalObject::getSelfHostedFunction(JSContext *cx, HandleAtom selfHostedName, Ha RootedId shId(cx, AtomToId(selfHostedName)); RootedObject holder(cx, cx->global()->intrinsicsHolder()); - if (HasDataProperty(cx, holder, shId, funVal.address())) + if (cx->global()->maybeGetIntrinsicValue(shId, funVal.address())) return true; if (!cx->runtime()->maybeWrappedSelfHostedFunction(cx, shId, funVal)) @@ -685,5 +685,31 @@ GlobalObject::getSelfHostedFunction(JSContext *cx, HandleAtom selfHostedName, Ha fun->setExtendedSlot(0, StringValue(selfHostedName)); funVal.setObject(*fun); - return JSObject::defineGeneric(cx, holder, shId, funVal, nullptr, nullptr, 0); + return cx->global()->addIntrinsicValue(cx, shId, funVal); +} + +bool +GlobalObject::addIntrinsicValue(JSContext *cx, HandleId id, HandleValue value) +{ + RootedObject holder(cx, intrinsicsHolder()); + + // Work directly with the shape machinery underlying the object, so that we + // don't take the compilation lock until we are ready to update the object + // without triggering a GC. + + uint32_t slot = holder->slotSpan(); + RootedShape last(cx, holder->lastProperty()); + Rooted base(cx, last->base()->unowned()); + + StackShape child(base, id, slot, holder->numFixedSlots(), 0, 0, 0); + RootedShape shape(cx, cx->compartment()->propertyTree.getChild(cx, last, holder->numFixedSlots(), child)); + if (!shape) + return false; + + AutoLockForCompilation lock(cx); + if (!JSObject::setLastProperty(cx, holder, shape)) + return false; + + holder->setSlot(shape->slot(), value); + return true; } diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 830fc509937d..57c13a4c6791 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -513,26 +513,32 @@ class GlobalObject : public JSObject return &getSlotRefForCompilation(INTRINSICS).toObject(); } - bool maybeGetIntrinsicValue(PropertyName *name, Value *vp) { + bool maybeGetIntrinsicValue(jsid id, Value *vp) { + JS_ASSERT(CurrentThreadCanReadCompilationData()); JSObject *holder = intrinsicsHolder(); - if (Shape *shape = holder->nativeLookupPure(name)) { + if (Shape *shape = holder->nativeLookupPure(id)) { *vp = holder->getSlot(shape->slot()); return true; } return false; } + bool maybeGetIntrinsicValue(PropertyName *name, Value *vp) { + return maybeGetIntrinsicValue(NameToId(name), vp); + } - bool getIntrinsicValue(JSContext *cx, HandlePropertyName name, MutableHandleValue value) { - if (maybeGetIntrinsicValue(name, value.address())) + static bool getIntrinsicValue(JSContext *cx, Handle global, + HandlePropertyName name, MutableHandleValue value) + { + if (global->maybeGetIntrinsicValue(name, value.address())) return true; - Rooted self(cx, this); if (!cx->runtime()->cloneSelfHostedValue(cx, name, value)) return false; - RootedObject holder(cx, self->intrinsicsHolder()); RootedId id(cx, NameToId(name)); - return JS_DefinePropertyById(cx, holder, id, value, nullptr, nullptr, 0); + return global->addIntrinsicValue(cx, id, value); } + bool addIntrinsicValue(JSContext *cx, HandleId id, HandleValue value); + bool setIntrinsicValue(JSContext *cx, PropertyName *name, HandleValue value) { #ifdef DEBUG RootedObject self(cx, this); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 328c96580bc0..cefe234bc064 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -210,7 +210,7 @@ inline bool GetIntrinsicOperation(JSContext *cx, jsbytecode *pc, MutableHandleValue vp) { RootedPropertyName name(cx, cx->currentScript()->getName(pc)); - return cx->global()->getIntrinsicValue(cx, name, vp); + return GlobalObject::getIntrinsicValue(cx, cx->global(), name, vp); } inline bool diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index f36d344e93c6..32cceba9f052 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1294,7 +1294,7 @@ SetObjectElementOperation(JSContext *cx, Handle obj, HandleId id, con if ((uint32_t)i >= length) { // Annotate script if provided with information (e.g. baseline) if (script && script->hasBaselineScript() && *pc == JSOP_SETELEM) - script->baselineScript()->noteArrayWriteHole(script->pcToOffset(pc)); + script->baselineScript()->noteArrayWriteHole(cx, script->pcToOffset(pc)); } } #endif diff --git a/js/src/vm/ObjectImpl.cpp b/js/src/vm/ObjectImpl.cpp index 05f355ba9564..c0bf6451fb7d 100644 --- a/js/src/vm/ObjectImpl.cpp +++ b/js/src/vm/ObjectImpl.cpp @@ -360,7 +360,7 @@ js::ObjectImpl::markChildren(JSTracer *trc) MarkShape(trc, &shape_, "shape"); - const Class *clasp = type_->clasp; + const Class *clasp = type_->clasp(); JSObject *obj = asObjectPtr(); if (clasp->trace) clasp->trace(trc, obj); @@ -538,7 +538,7 @@ js::ArrayBufferDelegate(JSContext *cx, Handle obj) if (obj->getPrivate()) return static_cast(obj->getPrivate()); JSObject *delegate = NewObjectWithGivenProto(cx, &JSObject::class_, - obj->getProto(), nullptr); + obj->getTaggedProto(), nullptr); obj->setPrivateGCThing(delegate); return delegate; } @@ -684,7 +684,7 @@ js::GetProperty(JSContext *cx, Handle obj, Handle rece /* No property? Recur or bottom out. */ if (desc.isUndefined()) { - current = current->getProto(); + current = current->getTaggedProto().toObjectOrNull(); if (current) continue; @@ -746,7 +746,7 @@ js::GetElement(JSContext *cx, Handle obj, Handle recei /* No property? Recur or bottom out. */ if (desc.isUndefined()) { - current = current->getProto(); + current = current->getTaggedProto().toObjectOrNull(); if (current) continue; @@ -811,7 +811,7 @@ js::HasElement(JSContext *cx, Handle obj, uint32_t index, unsigned return true; } - current = current->getProto(); + current = current->getTaggedProto().toObjectOrNull(); if (current) continue; @@ -1009,7 +1009,7 @@ js::SetElement(JSContext *cx, Handle obj, Handle recei MOZ_ASSUME_UNREACHABLE("NYI: setting PropertyOp-based property"); } - current = current->getProto(); + current = current->getTaggedProto().toObjectOrNull(); if (current) continue; diff --git a/js/src/vm/ObjectImpl.h b/js/src/vm/ObjectImpl.h index d367507deae8..822a1180ca5e 100644 --- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -980,12 +980,14 @@ class ObjectImpl : public gc::BarrieredCell /* These functions are public, and they should remain public. */ public: - JSObject * getProto() const { - return type_->proto; + js::TaggedProto getTaggedProto() const { + return type_->proto(); } + bool hasTenuredProto() const; + const Class *getClass() const { - return type_->clasp; + return type_->clasp(); } static inline bool @@ -1172,10 +1174,6 @@ class ObjectImpl : public gc::BarrieredCell */ public: - js::TaggedProto getTaggedProto() const { - return TaggedProto(getProto()); - } - Shape * lastProperty() const { MOZ_ASSERT(shape_); return shape_; diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 169921adeee0..7b6b6e4b34d5 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -75,6 +75,9 @@ PerThreadData::PerThreadData(JSRuntime *runtime) asmJSActivationStack_(nullptr), dtoaState(nullptr), suppressGC(0), +#ifdef DEBUG + ionCompiling(false), +#endif activeCompilations(0) {} @@ -135,6 +138,12 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads) exclusiveAccessOwner(nullptr), mainThreadHasExclusiveAccess(false), numExclusiveThreads(0), + compilationLock(nullptr), +#ifdef DEBUG + compilationLockOwner(nullptr), + mainThreadHasCompilationLock(false), +#endif + numCompilationThreads(0), #endif systemZone(nullptr), numCompartments(0), @@ -362,6 +371,10 @@ JSRuntime::init(uint32_t maxbytes) exclusiveAccessLock = PR_NewLock(); if (!exclusiveAccessLock) return false; + + compilationLock = PR_NewLock(); + if (!compilationLock) + return false; #endif if (!mainThread.init()) @@ -483,6 +496,10 @@ JSRuntime::~JSRuntime() // Avoid bogus asserts during teardown. JS_ASSERT(!numExclusiveThreads); mainThreadHasExclusiveAccess = true; + + JS_ASSERT(!compilationLockOwner); + if (compilationLock) + PR_DestroyLock(compilationLock); #endif #ifdef JS_THREADSAFE @@ -736,6 +753,24 @@ JSRuntime::getDefaultLocale() return defaultLocale; } +void +JSRuntime::triggerActivityCallback(bool active) +{ + if (!activityCallback) + return; + + /* + * The activity callback must not trigger a GC: it would create a cirular + * dependency between entering a request and Rooted's requirement of being + * in a request. In practice this callback already cannot trigger GC. The + * suppression serves to inform the exact rooting hazard analysis of this + * property and ensures that it remains true in the future. + */ + AutoSuppressGC suppress(this); + + activityCallback(activityCallbackArg, active); +} + void JSRuntime::setGCMaxMallocBytes(size_t value) { @@ -816,7 +851,7 @@ JSRuntime::activeGCInAtomsZone() #if defined(DEBUG) && !defined(XP_WIN) -AutoProtectHeapForCompilation::AutoProtectHeapForCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +AutoProtectHeapForIonCompilation::AutoProtectHeapForIonCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : runtime(rt) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; @@ -834,7 +869,7 @@ AutoProtectHeapForCompilation::AutoProtectHeapForCompilation(JSRuntime *rt MOZ_G } } -AutoProtectHeapForCompilation::~AutoProtectHeapForCompilation() +AutoProtectHeapForIonCompilation::~AutoProtectHeapForIonCompilation() { JS_ASSERT(runtime->heapProtected_); JS_ASSERT(runtime->unprotectedArenas.empty()); @@ -936,7 +971,7 @@ js::CurrentThreadCanAccessZone(Zone *zone) return true; } -#endif +#endif // JS_THREADSAFE #ifdef DEBUG @@ -954,13 +989,73 @@ JSRuntime::assertCanLock(RuntimeLock which) JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked()); case OperationCallbackLock: JS_ASSERT(!currentThreadOwnsOperationCallbackLock()); + case CompilationLock: + JS_ASSERT(compilationLockOwner != PR_GetCurrentThread()); case GCLock: JS_ASSERT(gcLockOwner != PR_GetCurrentThread()); break; default: MOZ_CRASH(); } -#endif // JS_THREADSAFE +#endif // JS_WORKER_THREADS +} + +AutoEnterIonCompilation::AutoEnterIonCompilation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + +#ifdef JS_WORKER_THREADS + PerThreadData *pt = js::TlsPerThreadData.get(); + JS_ASSERT(!pt->ionCompiling); + pt->ionCompiling = true; +#endif +} + +AutoEnterIonCompilation::~AutoEnterIonCompilation() +{ +#ifdef JS_WORKER_THREADS + PerThreadData *pt = js::TlsPerThreadData.get(); + JS_ASSERT(pt->ionCompiling); + pt->ionCompiling = false; +#endif +} + +bool +js::CurrentThreadCanWriteCompilationData() +{ +#ifdef JS_WORKER_THREADS + PerThreadData *pt = TlsPerThreadData.get(); + + // Data can only be read from during compilation. + if (pt->ionCompiling) + return false; + + // Ignore what threads with exclusive contexts are doing; these never have + // run scripts or have associated compilation threads. + JSRuntime *rt = pt->runtimeIfOnOwnerThread(); + if (!rt) + return true; + + return rt->currentThreadHasCompilationLock(); +#else + return true; +#endif +} + +bool +js::CurrentThreadCanReadCompilationData() +{ +#ifdef JS_WORKER_THREADS + PerThreadData *pt = TlsPerThreadData.get(); + + // Data can always be read from freely outside of compilation. + if (!pt || !pt->ionCompiling) + return true; + + return pt->runtime_->currentThreadHasCompilationLock(); +#else + return true; +#endif } #endif // DEBUG diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 0667445320bf..599c9d2bb8c4 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -535,6 +535,9 @@ class PerThreadData : public PerThreadDataFriendFields, friend class js::ActivationIterator; friend class js::jit::JitActivation; friend class js::AsmJSActivation; +#ifdef DEBUG + friend bool js::CurrentThreadCanReadCompilationData(); +#endif /* * Points to the most recent activation running on the thread. @@ -577,7 +580,12 @@ class PerThreadData : public PerThreadDataFriendFields, */ int32_t suppressGC; - // Whether there is an active compilation on this thread. +#ifdef DEBUG + // Whether this thread is actively Ion compiling. + bool ionCompiling; +#endif + + // Number of active bytecode compilation on this thread. unsigned activeCompilations; PerThreadData(JSRuntime *runtime); @@ -677,7 +685,8 @@ class MarkingValidator; typedef Vector ZoneVector; class AutoLockForExclusiveAccess; -class AutoProtectHeapForCompilation; +class AutoLockForCompilation; +class AutoProtectHeapForIonCompilation; void RecomputeStackLimit(JSRuntime *rt, StackKind kind); @@ -727,6 +736,7 @@ struct JSRuntime : public JS::shadow::Runtime, ExclusiveAccessLock, WorkerThreadStateLock, OperationCallbackLock, + CompilationLock, GCLock }; #ifdef DEBUG @@ -805,20 +815,44 @@ struct JSRuntime : public JS::shadow::Runtime, friend class js::AutoLockForExclusiveAccess; + /* + * Lock taken when using data that can be modified by the main thread but + * read by Ion compilation threads. Any time either the main thread writes + * such data or the compilation thread reads it, this lock must be taken. + * Note that no externally visible data is modified by the compilation + * thread, so the main thread never needs to take this lock when reading. + */ + PRLock *compilationLock; +#ifdef DEBUG + PRThread *compilationLockOwner; + bool mainThreadHasCompilationLock; +#endif + + /* Number of in flight Ion compilations. */ + size_t numCompilationThreads; + + friend class js::AutoLockForCompilation; +#ifdef DEBUG + friend bool js::CurrentThreadCanWriteCompilationData(); + friend bool js::CurrentThreadCanReadCompilationData(); +#endif + public: void setUsedByExclusiveThread(JS::Zone *zone); void clearUsedByExclusiveThread(JS::Zone *zone); #endif // JS_THREADSAFE && JS_ION +#ifdef DEBUG bool currentThreadHasExclusiveAccess() { -#if defined(JS_WORKER_THREADS) && defined(DEBUG) +#ifdef JS_WORKER_THREADS return (!numExclusiveThreads && mainThreadHasExclusiveAccess) || - exclusiveAccessOwner == PR_GetCurrentThread(); + exclusiveAccessOwner == PR_GetCurrentThread(); #else return true; #endif } +#endif // DEBUG bool exclusiveThreadsPresent() const { #ifdef JS_WORKER_THREADS @@ -828,6 +862,33 @@ struct JSRuntime : public JS::shadow::Runtime, #endif } + void addCompilationThread() { + numCompilationThreads++; + } + void removeCompilationThread() { + JS_ASSERT(numCompilationThreads); + numCompilationThreads--; + } + + bool compilationThreadsPresent() const { +#ifdef JS_WORKER_THREADS + return numCompilationThreads > 0; +#else + return false; +#endif + } + +#ifdef DEBUG + bool currentThreadHasCompilationLock() { +#ifdef JS_WORKER_THREADS + return (!numCompilationThreads && mainThreadHasCompilationLock) || + compilationLockOwner == PR_GetCurrentThread(); +#else + return true; +#endif + } +#endif // DEBUG + /* Embedders can use this zone however they wish. */ JS::Zone *systemZone; @@ -972,6 +1033,7 @@ struct JSRuntime : public JS::shadow::Runtime, js::ActivityCallback activityCallback; void *activityCallbackArg; + void triggerActivityCallback(bool active); #ifdef JS_THREADSAFE /* The request depth for this thread. */ @@ -1431,7 +1493,7 @@ struct JSRuntime : public JS::shadow::Runtime, const char *numGrouping; #endif - friend class js::AutoProtectHeapForCompilation; + friend class js::AutoProtectHeapForIonCompilation; friend class js::AutoThreadSafeAccess; mozilla::DebugOnly heapProtected_; #ifdef DEBUG @@ -2049,16 +2111,36 @@ class RuntimeAllocPolicy extern const JSSecurityCallbacks NullSecurityCallbacks; -class AutoProtectHeapForCompilation +// Debugging RAII class which marks the current thread as performing an Ion +// compilation, for use by CurrentThreadCan{Read,Write}CompilationData +class AutoEnterIonCompilation +{ + public: +#ifdef DEBUG + AutoEnterIonCompilation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + ~AutoEnterIonCompilation(); +#else + AutoEnterIonCompilation(MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } +#endif + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +// Debugging RAII class which protects the entire GC heap for the duration of an +// Ion compilation. When used only the main thread will be active and all +// accesses to GC things must be wrapped by an AutoThreadSafeAccess instance. +class AutoProtectHeapForIonCompilation { public: #if defined(DEBUG) && !defined(XP_WIN) JSRuntime *runtime; - AutoProtectHeapForCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - ~AutoProtectHeapForCompilation(); + AutoProtectHeapForIonCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoProtectHeapForIonCompilation(); #else - AutoProtectHeapForCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoProtectHeapForIonCompilation(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp index 8158a9800c01..61411664d5d6 100644 --- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -56,13 +56,14 @@ SPSProfiler::enable(bool enabled) if (enabled_ == enabled) return; - enabled_ = enabled; /* * Ensure all future generated code will be instrumented, or that all * currently instrumented code is discarded */ ReleaseAllJITCode(rt->defaultFreeOp()); + enabled_ = enabled; + #ifdef JS_ION /* Toggle SPS-related jumps on baseline jitcode. * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index d4c50beeb60e..eb1105f6c905 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -958,10 +958,11 @@ JSRuntime::cloneSelfHostedFunctionScript(JSContext *cx, Handle na JSScript *cscript = CloneScript(cx, NullPtr(), targetFun, sourceScript); if (!cscript) return false; - targetFun->setScript(cscript); cscript->setFunction(targetFun); + JS_ASSERT(sourceFun->nargs() == targetFun->nargs()); targetFun->setFlags(sourceFun->flags() | JSFunction::EXTENDED); + targetFun->setScript(cscript); return true; } @@ -1010,7 +1011,7 @@ JSFunction * js::SelfHostedFunction(JSContext *cx, HandlePropertyName propName) { RootedValue func(cx); - if (!cx->global()->getIntrinsicValue(cx, propName, &func)) + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), propName, &func)) return nullptr; JS_ASSERT(func.isObject()); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 74339e88347d..1b939022765a 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -338,7 +338,7 @@ ArrayBufferObject::neuterViews(JSContext *cx, Handle buffer) size_t numViews = 0; for (view = GetViewList(buffer); view; view = view->nextView()) { numViews++; - view->neuter(); + view->neuter(cx); // Notify compiled jit code that the base pointer has moved. MarkObjectStateChange(cx, view); @@ -1182,8 +1182,10 @@ TypedArrayObject::isArrayIndex(jsid id, uint32_t *ip) } void -TypedArrayObject::neuter() +TypedArrayObject::neuter(JSContext *cx) { + AutoLockForCompilation lock(cx); + setSlot(LENGTH_SLOT, Int32Value(0)); setSlot(BYTELENGTH_SLOT, Int32Value(0)); setSlot(BYTEOFFSET_SLOT, Int32Value(0)); @@ -2625,12 +2627,12 @@ ArrayBufferViewObject::prependToViews(ArrayBufferViewObject *viewsHead) } void -ArrayBufferViewObject::neuter() +ArrayBufferViewObject::neuter(JSContext *cx) { if (is()) as().neuter(); else - as().neuter(); + as().neuter(cx); } // this default implementation is only valid for integer types diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 275315ace116..7baa47cf8adb 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -276,7 +276,7 @@ class ArrayBufferViewObject : public JSObject void prependToViews(ArrayBufferViewObject *viewsHead); - void neuter(); + void neuter(JSContext *cx); static void trace(JSTracer *trc, JSObject *obj); }; @@ -353,7 +353,7 @@ class TypedArrayObject : public ArrayBufferViewObject inline bool isArrayIndex(jsid id, uint32_t *ip = nullptr); void copyTypedArrayElement(uint32_t index, MutableHandleValue vp); - void neuter(); + void neuter(JSContext *cx); static uint32_t slotWidth(int atype) { switch (atype) { diff --git a/media/omx-plugin/Makefile.in b/media/omx-plugin/Makefile.in index 5774eb4310da..dbfdc216adce 100644 --- a/media/omx-plugin/Makefile.in +++ b/media/omx-plugin/Makefile.in @@ -53,6 +53,8 @@ EXTRA_DSO_LDOPTS += \ -lutils \ -L$(DEPTH)/media/omx-plugin/lib/ics/libstagefright \ -lstagefright \ + -L$(DEPTH)/media/omx-plugin/lib/ics/libvideoeditorplayer \ + -lvideoeditorplayer \ $(NULL) INCLUDES += \ diff --git a/media/omx-plugin/OmxPlugin.cpp b/media/omx-plugin/OmxPlugin.cpp index 305673880db7..c2ff759ef2fe 100644 --- a/media/omx-plugin/OmxPlugin.cpp +++ b/media/omx-plugin/OmxPlugin.cpp @@ -37,6 +37,14 @@ #define MOZ_ANDROID_V2_X_X #endif +#if !defined(MOZ_ANDROID_V2_X_X) && !defined(MOZ_ANDROID_HC) +#define MOZ_ANDROID_V4_OR_ABOVE +#endif + +#if defined(MOZ_ANDROID_V4_OR_ABOVE) +#include +#endif + using namespace MPAPI; #if !defined(MOZ_STAGEFRIGHT_OFF_T) @@ -68,6 +76,8 @@ class OmxDecoder { int32_t mVideoSliceHeight; int32_t mVideoCropLeft; int32_t mVideoCropTop; + int32_t mVideoCropRight; + int32_t mVideoCropBottom; int32_t mVideoRotation; int32_t mAudioChannels; int32_t mAudioSampleRate; @@ -92,6 +102,7 @@ class OmxDecoder { void ToVideoFrame_YVU420PackedSemiPlanar32m4ka(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame); bool ToVideoFrame_RGB565(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback); bool ToVideoFrame_ColorConverter(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback); + bool ToVideoFrame_I420ColorConverter(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback); bool ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback); bool ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize, int32_t aAudioChannels, int32_t aAudioSampleRate); @@ -139,6 +150,8 @@ OmxDecoder::OmxDecoder(PluginHost *aPluginHost, Decoder *aDecoder) : mVideoSliceHeight(0), mVideoCropLeft(0), mVideoCropTop(0), + mVideoCropRight(0), + mVideoCropBottom(0), mVideoRotation(0), mAudioChannels(-1), mAudioSampleRate(-1), @@ -226,11 +239,48 @@ static uint32_t GetVideoCreationFlags(PluginHost* aPluginHost) #endif } +static bool +IsColorFormatSupported(OMX_COLOR_FORMATTYPE aColorFormat) +{ + switch (aColorFormat) { + case OMX_COLOR_FormatCbYCrY: + case OMX_COLOR_FormatYUV420Planar: + case OMX_COLOR_FormatYUV420SemiPlanar: + case OMX_QCOM_COLOR_FormatYVU420PackedSemiPlanar32m4ka: + case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: + case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar: + LOG("Colour format %#x supported natively.", aColorFormat); + return true; + default: + break; + } + +#if !defined(MOZ_ANDROID_HC) + if (ColorConverter(aColorFormat, OMX_COLOR_Format16bitRGB565).isValid()) { + LOG("Colour format %#x supported by Android ColorConverter.", aColorFormat); + return true; + } +#endif + +#if defined(MOZ_ANDROID_V4_OR_ABOVE) + I420ColorConverter yuvConverter; + + if (yuvConverter.isLoaded() && + yuvConverter.getDecoderOutputFormat() == aColorFormat) { + LOG("Colour format %#x supported by Android I420ColorConverter.", aColorFormat); + return true; + } +#endif + + return false; +} + static sp CreateVideoSource(PluginHost* aPluginHost, const sp& aOmx, const sp& aVideoTrack) { uint32_t flags = GetVideoCreationFlags(aPluginHost); + if (flags == DEFAULT_STAGEFRIGHT_FLAGS) { // Let Stagefright choose hardware or software decoder. sp videoSource = OMXCodec::Create(aOmx, aVideoTrack->getFormat(), @@ -242,30 +292,14 @@ static sp CreateVideoSource(PluginHost* aPluginHost, // check whether we know how to decode this video. int32_t videoColorFormat; if (videoSource->getFormat()->findInt32(kKeyColorFormat, &videoColorFormat)) { - switch (videoColorFormat) { - // We know how to convert these color formats. - case OMX_COLOR_FormatCbYCrY: - case OMX_COLOR_FormatYUV420Planar: - case OMX_COLOR_FormatYUV420SemiPlanar: - case OMX_QCOM_COLOR_FormatYVU420PackedSemiPlanar32m4ka: - case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: - case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar: - // Use the decoder Stagefright chose for us! - return videoSource; - // Use software decoder for color formats we don't know how to convert. - default: -#ifndef MOZ_ANDROID_HC - if (ColorConverter((OMX_COLOR_FORMATTYPE)videoColorFormat, - OMX_COLOR_Format16bitRGB565).isValid()) { - return videoSource; - } -#endif - // We need to implement a ToVideoFrame_*() color conversion - // function for this video color format. - LOG("Unknown video color format: %#x", videoColorFormat); - break; + if (IsColorFormatSupported((OMX_COLOR_FORMATTYPE)videoColorFormat)) { + return videoSource; } + + // We need to implement a ToVideoFrame_*() color conversion + // function for this video color format. + LOG("Unknown video color format: %#x", videoColorFormat); } else { LOG("Video color format not found"); } @@ -472,29 +506,28 @@ bool OmxDecoder::SetVideoFormat() { return false; } - int32_t cropRight, cropBottom; // Gingerbread does not support the kKeyCropRect key #if !defined(MOZ_ANDROID_V2_X_X) if (!format->findRect(kKeyCropRect, &mVideoCropLeft, &mVideoCropTop, - &cropRight, &cropBottom)) { + &mVideoCropRight, &mVideoCropBottom)) { #endif mVideoCropLeft = 0; mVideoCropTop = 0; - cropRight = mVideoStride - 1; - cropBottom = mVideoSliceHeight - 1; + mVideoCropRight = mVideoStride - 1; + mVideoCropBottom = mVideoSliceHeight - 1; LOG("crop rect not available, assuming no cropping"); #if !defined(MOZ_ANDROID_V2_X_X) } #endif - if (mVideoCropLeft < 0 || mVideoCropLeft >= cropRight || cropRight >= mVideoStride || - mVideoCropTop < 0 || mVideoCropTop >= cropBottom || cropBottom >= mVideoSliceHeight) { - LOG("invalid crop rect %d,%d-%d,%d", mVideoCropLeft, mVideoCropTop, cropRight, cropBottom); + if (mVideoCropLeft < 0 || mVideoCropLeft >= mVideoCropRight || mVideoCropRight >= mVideoStride || + mVideoCropTop < 0 || mVideoCropTop >= mVideoCropBottom || mVideoCropBottom >= mVideoSliceHeight) { + LOG("invalid crop rect %d,%d-%d,%d", mVideoCropLeft, mVideoCropTop, mVideoCropRight, mVideoCropBottom); return false; } - mVideoWidth = cropRight - mVideoCropLeft + 1; - mVideoHeight = cropBottom - mVideoCropTop + 1; + mVideoWidth = mVideoCropRight - mVideoCropLeft + 1; + mVideoHeight = mVideoCropBottom - mVideoCropTop + 1; MOZ_ASSERT(mVideoWidth > 0 && mVideoWidth <= mVideoStride); MOZ_ASSERT(mVideoHeight > 0 && mVideoHeight <= mVideoSliceHeight); @@ -515,7 +548,7 @@ bool OmxDecoder::SetVideoFormat() { LOG("width: %d height: %d component: %s format: %#x stride: %d sliceHeight: %d rotation: %d crop: %d,%d-%d,%d", mVideoWidth, mVideoHeight, componentName, mVideoColorFormat, mVideoStride, mVideoSliceHeight, mVideoRotation, - mVideoCropLeft, mVideoCropTop, cropRight, cropBottom); + mVideoCropLeft, mVideoCropTop, mVideoCropRight, mVideoCropBottom); return true; } @@ -682,6 +715,40 @@ bool OmxDecoder::ToVideoFrame_ColorConverter(VideoFrame *aFrame, int64_t aTimeUs #endif } +bool OmxDecoder::ToVideoFrame_I420ColorConverter(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback) +{ +#if defined(MOZ_ANDROID_V4_OR_ABOVE) + I420ColorConverter yuvConverter; + + if (!yuvConverter.isLoaded()) { + return false; + } + + if (yuvConverter.getDecoderOutputFormat() != mVideoColorFormat) { + return false; + } + + void *buffer = (*aBufferCallback)(mVideoWidth, mVideoHeight, MPAPI::I420); + + ARect crop = { mVideoCropLeft, mVideoCropTop, mVideoCropRight, mVideoCropBottom }; + int result = yuvConverter.convertDecoderOutputToI420(aData, + mVideoWidth, + mVideoHeight, + crop, + buffer); + + // result is 0 on success, -1 otherwise. + if (result == OK) { + aFrame->mTimeUs = aTimeUs; + aFrame->mSize = mVideoWidth * mVideoHeight * 3 / 2; + } + + return result == OK; +#else + return false; +#endif +} + bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame, BufferCallback *aBufferCallback) { switch (mVideoColorFormat) { // Froyo support is best handled with the android color conversion code. I @@ -710,7 +777,8 @@ bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, break; #endif default: - if (!ToVideoFrame_ColorConverter(aFrame, aTimeUs, aData, aSize, aKeyFrame, aBufferCallback)) { + if (!ToVideoFrame_ColorConverter(aFrame, aTimeUs, aData, aSize, aKeyFrame, aBufferCallback) && + !ToVideoFrame_I420ColorConverter(aFrame, aTimeUs, aData, aSize, aKeyFrame, aBufferCallback)) { LOG("Unknown video color format: %#x", mVideoColorFormat); return false; } diff --git a/media/omx-plugin/include/ics/I420ColorConverter.h b/media/omx-plugin/include/ics/I420ColorConverter.h new file mode 100644 index 000000000000..8d48e44b4d02 --- /dev/null +++ b/media/omx-plugin/include/ics/I420ColorConverter.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef I420_COLOR_CONVERTER_H +#define I420_COLOR_CONVERTER_H + +#include + +// This is a wrapper around the I420 color converter functions in +// II420ColorConverter, which is loaded from a shared library. +class I420ColorConverter: public II420ColorConverter { +public: + I420ColorConverter(); + ~I420ColorConverter(); + + // Returns true if the converter functions are successfully loaded. + bool isLoaded(); +private: + void* mHandle; +}; + +#endif /* I420_COLOR_CONVERTER_H */ diff --git a/media/omx-plugin/include/ics/II420ColorConverter.h b/media/omx-plugin/include/ics/II420ColorConverter.h new file mode 100644 index 000000000000..0e3fe8285d7d --- /dev/null +++ b/media/omx-plugin/include/ics/II420ColorConverter.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef II420_COLOR_CONVERTER_H + +#define II420_COLOR_CONVERTER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct II420ColorConverter { + + /* + * getDecoderOutputFormat + * Returns the color format (OMX_COLOR_FORMATTYPE) of the decoder output. + * If it is I420 (OMX_COLOR_FormatYUV420Planar), no conversion is needed, + * and convertDecoderOutputToI420() can be a no-op. + */ + int (*getDecoderOutputFormat)(); + + /* + * convertDecoderOutputToI420 + * @Desc Converts from the decoder output format to I420 format. + * @note Caller (e.g. VideoEditor) owns the buffers + * @param decoderBits (IN) Pointer to the buffer contains decoder output + * @param decoderWidth (IN) Buffer width, as reported by the decoder + * metadata (kKeyWidth) + * @param decoderHeight (IN) Buffer height, as reported by the decoder + * metadata (kKeyHeight) + * @param decoderRect (IN) The rectangle of the actual frame, as + * reported by decoder metadata (kKeyCropRect) + * @param dstBits (OUT) Pointer to the output I420 buffer + * @return -1 Any error + * @return 0 No Error + */ + int (*convertDecoderOutputToI420)( + void* decoderBits, int decoderWidth, int decoderHeight, + ARect decoderRect, void* dstBits); + + /* + * getEncoderIntputFormat + * Returns the color format (OMX_COLOR_FORMATTYPE) of the encoder input. + * If it is I420 (OMX_COLOR_FormatYUV420Planar), no conversion is needed, + * and convertI420ToEncoderInput() and getEncoderInputBufferInfo() can + * be no-ops. + */ + int (*getEncoderInputFormat)(); + + /* convertI420ToEncoderInput + * @Desc This function converts from I420 to the encoder input format + * @note Caller (e.g. VideoEditor) owns the buffers + * @param srcBits (IN) Pointer to the input I420 buffer + * @param srcWidth (IN) Width of the I420 frame + * @param srcHeight (IN) Height of the I420 frame + * @param encoderWidth (IN) Encoder buffer width, as calculated by + * getEncoderBufferInfo() + * @param encoderHeight (IN) Encoder buffer height, as calculated by + * getEncoderBufferInfo() + * @param encoderRect (IN) Rect coordinates of the actual frame inside + * the encoder buffer, as calculated by + * getEncoderBufferInfo(). + * @param encoderBits (OUT) Pointer to the output buffer. The size of + * this buffer is calculated by + * getEncoderBufferInfo() + * @return -1 Any error + * @return 0 No Error + */ + int (*convertI420ToEncoderInput)( + void* srcBits, int srcWidth, int srcHeight, + int encoderWidth, int encoderHeight, ARect encoderRect, + void* encoderBits); + + /* getEncoderInputBufferInfo + * @Desc This function returns metadata for the encoder input buffer + * based on the actual I420 frame width and height. + * @note This API should be be used to obtain the necessary information + * before calling convertI420ToEncoderInput(). + * VideoEditor knows only the width and height of the I420 buffer, + * but it also needs know the width, height, and size of the + * encoder input buffer. The encoder input buffer width and height + * are used to set the metadata for the encoder. + * @param srcWidth (IN) Width of the I420 frame + * @param srcHeight (IN) Height of the I420 frame + * @param encoderWidth (OUT) Encoder buffer width needed + * @param encoderHeight (OUT) Encoder buffer height needed + * @param encoderRect (OUT) Rect coordinates of the actual frame inside + * the encoder buffer + * @param encoderBufferSize (OUT) The size of the buffer that need to be + * allocated by the caller before invoking + * convertI420ToEncoderInput(). + * @return -1 Any error + * @return 0 No Error + */ + int (*getEncoderInputBufferInfo)( + int srcWidth, int srcHeight, + int* encoderWidth, int* encoderHeight, + ARect* encoderRect, int* encoderBufferSize); + +} II420ColorConverter; + +/* The only function that the shared library needs to expose: It fills the + function pointers in II420ColorConverter */ +void getI420ColorConverter(II420ColorConverter *converter); + +#if defined(__cplusplus) +} +#endif + +#endif // II420_COLOR_CONVERTER_H + diff --git a/media/omx-plugin/lib/ics/libvideoeditorplayer/Makefile.in b/media/omx-plugin/lib/ics/libvideoeditorplayer/Makefile.in new file mode 100644 index 000000000000..85c8662e331b --- /dev/null +++ b/media/omx-plugin/lib/ics/libvideoeditorplayer/Makefile.in @@ -0,0 +1,25 @@ +# Copyright 2012 Mozilla Foundation and Mozilla contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Don't use STL wrappers; this isn't Gecko code +STL_FLAGS = + +# must link statically with the CRT; this isn't Gecko code +USE_STATIC_LIBS = 1 + +include $(topsrcdir)/config/rules.mk + +INCLUDES += \ + -I$(topsrcdir)/media/omx-plugin/include/ics \ + $(NULL) diff --git a/media/omx-plugin/lib/ics/libvideoeditorplayer/libvideoeditorplayer.cpp b/media/omx-plugin/lib/ics/libvideoeditorplayer/libvideoeditorplayer.cpp new file mode 100644 index 000000000000..2c491aeb9e35 --- /dev/null +++ b/media/omx-plugin/lib/ics/libvideoeditorplayer/libvideoeditorplayer.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/Types.h" +#include "I420ColorConverter.h" + +MOZ_EXPORT +I420ColorConverter::I420ColorConverter() +{ +} + +MOZ_EXPORT +I420ColorConverter::~I420ColorConverter() +{ +} + +MOZ_EXPORT bool +I420ColorConverter::isLoaded() +{ + return false; +} diff --git a/media/omx-plugin/lib/ics/libvideoeditorplayer/moz.build b/media/omx-plugin/lib/ics/libvideoeditorplayer/moz.build new file mode 100644 index 000000000000..40e83bca89d6 --- /dev/null +++ b/media/omx-plugin/lib/ics/libvideoeditorplayer/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. +NO_DIST_INSTALL = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': + SOURCES += [ + 'libvideoeditorplayer.cpp', + ] + +LIBRARY_NAME = 'videoeditorplayer' + +FORCE_SHARED_LIB = True diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 9ad4672606e3..a4d958c339c4 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -180,7 +180,7 @@ res/values/strings.xml: FORCE $(MAKE) -C locales all_resources = \ - AndroidManifest.xml \ + $(CURDIR)/AndroidManifest.xml \ $(android_res_files) \ $(ANDROID_GENERATED_RESFILES) \ $(NULL) @@ -228,8 +228,8 @@ endef # .aapt.deps: $(all_resources) $(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,$(gecko_package_dir)/,./)) -# .aapt.nodeps: FORCE -$(eval $(call aapt_command,.aapt.nodeps,FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/)) +# .aapt.nodeps: $(CURDIR)/AndroidManifest.xml FORCE +$(eval $(call aapt_command,.aapt.nodeps,$(CURDIR)/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/)) fennec_ids.txt: $(gecko_package_dir)/R.java fennec-ids-generator.py $(PYTHON) $(topsrcdir)/mobile/android/base/fennec-ids-generator.py -i $< -o $@ diff --git a/netwerk/base/src/nsSocketTransport2.cpp b/netwerk/base/src/nsSocketTransport2.cpp index 34d2e9a2a728..15478bcfebdd 100644 --- a/netwerk/base/src/nsSocketTransport2.cpp +++ b/netwerk/base/src/nsSocketTransport2.cpp @@ -748,7 +748,7 @@ nsSocketTransport::nsSocketTransport() , mResolving(false) , mNetAddrIsSet(false) , mLock("nsSocketTransport.mLock") - , mFD(nullptr) + , mFD(MOZ_THIS_IN_INITIALIZER_LIST()) , mFDref(0) , mFDconnected(false) , mInput(MOZ_THIS_IN_INITIALIZER_LIST()) @@ -891,7 +891,7 @@ nsSocketTransport::InitWithFilename(const char *filename) nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) { - NS_ASSERTION(!mFD, "already initialized"); + NS_ASSERTION(!mFD.IsInitialized(), "already initialized"); char buf[kNetAddrMaxCStrBufSize]; NetAddrToString(addr, buf, sizeof(buf)); @@ -913,15 +913,19 @@ nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) mState = STATE_TRANSFERRING; mNetAddrIsSet = true; - mFD = fd; - mFDref = 1; - mFDconnected = 1; + { + MutexAutoLock lock(mLock); + + mFD = fd; + mFDref = 1; + mFDconnected = 1; + } // make sure new socket is non-blocking PRSocketOptionData opt; opt.option = PR_SockOpt_Nonblocking; opt.value.non_blocking = true; - PR_SetSocketOption(mFD, &opt); + PR_SetSocketOption(fd, &opt); SOCKET_LOG(("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n", this, mHost.get(), mPort)); @@ -1209,7 +1213,7 @@ nsSocketTransport::InitiateSocket() // // if we already have a connected socket, then just attach and return. // - if (mFD) { + if (mFD.IsInitialized()) { rv = gSocketTransportService->AttachSocket(mFD, this); if (NS_SUCCEEDED(rv)) mAttached = true; @@ -1533,7 +1537,7 @@ nsSocketTransport::OnSocketConnected() // to trample over mFDref if mFD is already set. { MutexAutoLock lock(mLock); - NS_ASSERTION(mFD, "no socket"); + NS_ASSERTION(mFD.IsInitialized(), "no socket"); NS_ASSERTION(mFDref == 1, "wrong socket ref count"); mFDconnected = true; } @@ -1546,11 +1550,13 @@ nsSocketTransport::OnSocketConnected() PRFileDesc * nsSocketTransport::GetFD_Locked() { + mLock.AssertCurrentThreadOwns(); + // mFD is not available to the streams while disconnected. if (!mFDconnected) return nullptr; - if (mFD) + if (mFD.IsInitialized()) mFDref++; return mFD; @@ -1587,6 +1593,8 @@ STS_PRCloseOnSocketTransport(PRFileDesc *fd) void nsSocketTransport::ReleaseFD_Locked(PRFileDesc *fd) { + mLock.AssertCurrentThreadOwns(); + NS_ASSERTION(mFD == fd, "wrong fd"); SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref)); @@ -1855,7 +1863,7 @@ nsSocketTransport::OnSocketDetached(PRFileDesc *fd) nsCOMPtr ourEventSink; { MutexAutoLock lock(mLock); - if (mFD) { + if (mFD.IsInitialized()) { ReleaseFD_Locked(mFD); // flag mFD as unusable; this prevents other consumers from // acquiring a reference to mFD. @@ -2074,14 +2082,10 @@ nsSocketTransport::IsAlive(bool *result) { *result = false; - PRFileDesc* fd = nullptr; - { - MutexAutoLock lock(mLock); - if (NS_FAILED(mCondition)) - return NS_OK; - fd = GetFD_Locked(); - if (!fd) - return NS_OK; + nsresult conditionWhileLocked = NS_OK; + PRFileDescAutoLock fd(this, &conditionWhileLocked); + if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) { + return NS_OK; } // XXX do some idle-time based checks?? @@ -2092,10 +2096,6 @@ nsSocketTransport::IsAlive(bool *result) if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) *result = true; - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } return NS_OK; } @@ -2138,13 +2138,8 @@ nsSocketTransport::GetSelfAddr(NetAddr *addr) // while holding mLock since those methods might re-enter // socket transport code. - PRFileDesc *fd; - { - MutexAutoLock lock(mLock); - fd = GetFD_Locked(); - } - - if (!fd) { + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) { return NS_ERROR_NOT_CONNECTED; } @@ -2163,11 +2158,6 @@ nsSocketTransport::GetSelfAddr(NetAddr *addr) (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; PRNetAddrToNetAddr(&prAddr, addr); - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } - return rv; } @@ -2244,106 +2234,70 @@ nsSocketTransport::GetQoSBits(uint8_t *aQoSBits) NS_IMETHODIMP nsSocketTransport::GetRecvBufferSize(uint32_t *aSize) { - PRFileDesc *fd; - { - MutexAutoLock lock(mLock); - fd = GetFD_Locked(); - } - - if (!fd) + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; nsresult rv = NS_OK; PRSocketOptionData opt; opt.option = PR_SockOpt_RecvBufferSize; - if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) + if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) *aSize = opt.value.recv_buffer_size; else rv = NS_ERROR_FAILURE; - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } return rv; } NS_IMETHODIMP nsSocketTransport::GetSendBufferSize(uint32_t *aSize) { - PRFileDesc *fd; - { - MutexAutoLock lock(mLock); - fd = GetFD_Locked(); - } - - if (!fd) + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; nsresult rv = NS_OK; PRSocketOptionData opt; opt.option = PR_SockOpt_SendBufferSize; - if (PR_GetSocketOption(mFD, &opt) == PR_SUCCESS) + if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) *aSize = opt.value.send_buffer_size; else rv = NS_ERROR_FAILURE; - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } return rv; } NS_IMETHODIMP nsSocketTransport::SetRecvBufferSize(uint32_t aSize) { - PRFileDesc *fd; - { - MutexAutoLock lock(mLock); - fd = GetFD_Locked(); - } - - if (!fd) + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; nsresult rv = NS_OK; PRSocketOptionData opt; opt.option = PR_SockOpt_RecvBufferSize; opt.value.recv_buffer_size = aSize; - if (PR_SetSocketOption(mFD, &opt) != PR_SUCCESS) + if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE; - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } return rv; } NS_IMETHODIMP nsSocketTransport::SetSendBufferSize(uint32_t aSize) { - PRFileDesc *fd; - { - MutexAutoLock lock(mLock); - fd = GetFD_Locked(); - } - - if (!fd) + PRFileDescAutoLock fd(this); + if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED; nsresult rv = NS_OK; PRSocketOptionData opt; opt.option = PR_SockOpt_SendBufferSize; opt.value.send_buffer_size = aSize; - if (PR_SetSocketOption(mFD, &opt) != PR_SUCCESS) + if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE; - { - MutexAutoLock lock(mLock); - ReleaseFD_Locked(fd); - } return rv; } diff --git a/netwerk/base/src/nsSocketTransport2.h b/netwerk/base/src/nsSocketTransport2.h index c957336ae16d..70b6ed8918cb 100644 --- a/netwerk/base/src/nsSocketTransport2.h +++ b/netwerk/base/src/nsSocketTransport2.h @@ -172,6 +172,84 @@ private: STATE_TRANSFERRING }; + // Safer way to get and automatically release PRFileDesc objects. + class MOZ_STACK_CLASS PRFileDescAutoLock + { + public: + typedef mozilla::MutexAutoLock MutexAutoLock; + + PRFileDescAutoLock(nsSocketTransport *aSocketTransport, + nsresult *aConditionWhileLocked = nullptr) + : mSocketTransport(aSocketTransport) + , mFd(nullptr) + { + MOZ_ASSERT(aSocketTransport); + MutexAutoLock lock(mSocketTransport->mLock); + if (aConditionWhileLocked) { + *aConditionWhileLocked = mSocketTransport->mCondition; + if (NS_FAILED(mSocketTransport->mCondition)) { + return; + } + } + mFd = mSocketTransport->GetFD_Locked(); + NS_WARN_IF_FALSE(mFd, "PRFileDescAutoLock cannot get fd!"); + } + ~PRFileDescAutoLock() { + MutexAutoLock lock(mSocketTransport->mLock); + if (mFd) { + mSocketTransport->ReleaseFD_Locked(mFd); + } + } + bool IsInitialized() { + return mFd; + } + operator PRFileDesc*() { + return mFd; + } + private: + operator PRFileDescAutoLock*() { return nullptr; } + // Weak ptr to nsSocketTransport since this is a stack class only. + nsSocketTransport *mSocketTransport; + PRFileDesc *mFd; + }; + friend class PRFileDescAutoLock; + + class LockedPRFileDesc + { + public: + LockedPRFileDesc(nsSocketTransport *aSocketTransport) + : mSocketTransport(aSocketTransport) + , mFd(nullptr) + { + MOZ_ASSERT(aSocketTransport); + } + ~LockedPRFileDesc() {} + bool IsInitialized() { + return mFd; + } + LockedPRFileDesc& operator=(PRFileDesc *aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + mFd = aFd; + return *this; + } + operator PRFileDesc*() { + if (mSocketTransport->mAttached) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + } + return mFd; + } + bool operator==(PRFileDesc *aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + return mFd == aFd; + } + private: + operator LockedPRFileDesc*() { return nullptr; } + // Weak ptr to nsSocketTransport since it owns this class. + nsSocketTransport *mSocketTransport; + PRFileDesc *mFd; + }; + friend class LockedPRFileDesc; + //------------------------------------------------------------------------- // these members are "set" at initialization time and are never modified // afterwards. this allows them to be safely accessed from any thread. @@ -242,10 +320,10 @@ private: // socket input/output objects. these may be accessed on any thread with // the exception of some specific methods (XXX). - Mutex mLock; // protects members in this section - PRFileDesc *mFD; - nsrefcnt mFDref; // mFD is closed when mFDref goes to zero. - bool mFDconnected; // mFD is available to consumer when TRUE. + Mutex mLock; // protects members in this section. + LockedPRFileDesc mFD; + nsrefcnt mFDref; // mFD is closed when mFDref goes to zero. + bool mFDconnected; // mFD is available to consumer when TRUE. nsCOMPtr mCallbacks; nsCOMPtr mEventSink; diff --git a/python/mozbuild/mozbuild/jar.py b/python/mozbuild/mozbuild/jar.py index db9fa4811113..98c17cce76ed 100644 --- a/python/mozbuild/mozbuild/jar.py +++ b/python/mozbuild/mozbuild/jar.py @@ -507,8 +507,8 @@ def main(args=None): jm.relativesrcdir = options.relativesrcdir jm.l10nmerge = options.locale_mergedir if jm.l10nmerge and not os.path.isdir(jm.l10nmerge): - logging.warning("WARNING: --locale-mergedir passed, but '%s' does not exist. Ignore this message if the locale is complete." - ) + logging.warning("WARNING: --locale-mergedir passed, but '%s' does not exist. " + "Ignore this message if the locale is complete." % jm.l10nmerge) elif options.locale_mergedir: p.error('l10n-base required when using locale-mergedir') jm.localedirs = options.l10n_src diff --git a/toolkit/components/telemetry/TelemetryFile.jsm b/toolkit/components/telemetry/TelemetryFile.jsm index 7a21d5c2fd4e..8db9eb815b58 100644 --- a/toolkit/components/telemetry/TelemetryFile.jsm +++ b/toolkit/components/telemetry/TelemetryFile.jsm @@ -22,8 +22,13 @@ const PR_EXCL = 0x80; const RW_OWNER = parseInt("0600", 8); const RWX_OWNER = parseInt("0700", 8); -// Delete ping files that have been lying around for longer than this. -const MAX_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week +// Files that have been lying around for longer than MAX_PING_FILE_AGE are +// deleted without being loaded. +const MAX_PING_FILE_AGE = 14 * 24 * 60 * 60 * 1000; // 2 weeks + +// Files that are older than OVERDUE_PING_FILE_AGE, but younger than +// MAX_PING_FILE_AGE indicate that we need to send all of our pings ASAP. +const OVERDUE_PING_FILE_AGE = 7 * 24 * 60 * 60 * 1000; // 1 week // The number of outstanding saved pings that we have issued loading // requests for. @@ -32,6 +37,14 @@ let pingsLoaded = 0; // The number of those requests that have actually completed. let pingLoadsCompleted = 0; +// The number of pings that we have destroyed due to being older +// than MAX_PING_FILE_AGE. +let pingsDiscarded = 0; + +// The number of pings that are older than OVERDUE_PING_FILE_AGE +// but younger than MAX_PING_FILE_AGE. +let pingsOverdue = 0; + // If |true|, send notifications "telemetry-test-save-complete" // and "telemetry-test-load-complete" once save/load is complete. let shouldNotifyUponSave = false; @@ -41,6 +54,14 @@ let pendingPings = []; this.TelemetryFile = { + get MAX_PING_FILE_AGE() { + return MAX_PING_FILE_AGE; + }, + + get OVERDUE_PING_FILE_AGE() { + return OVERDUE_PING_FILE_AGE; + }, + /** * Save a single ping to a file. * @@ -142,7 +163,7 @@ this.TelemetryFile = { * ping. It is passed |true| in case of success, |false| in case of * format error. */ - loadSavedPings: function(sync, onLoad = null) { + loadSavedPings: function(sync, onLoad = null, onDone = null) { let directory = ensurePingDirectory(); let entries = directory.directoryEntries .QueryInterface(Ci.nsIDirectoryEnumerator); @@ -150,7 +171,7 @@ this.TelemetryFile = { pingLoadsCompleted = 0; try { while (entries.hasMoreElements()) { - this.loadHistograms(entries.nextFile, sync, onLoad); + this.loadHistograms(entries.nextFile, sync, onLoad, onDone); } } finally { entries.close(); @@ -169,20 +190,26 @@ this.TelemetryFile = { * ping. It is passed |true| in case of success, |false| in case of * format error. */ - loadHistograms: function loadHistograms(file, sync, onLoad = null) { - let now = new Date(); + loadHistograms: function loadHistograms(file, sync, onLoad = null, onDone = null) { + let now = Date.now(); if (now - file.lastModifiedTime > MAX_PING_FILE_AGE) { // We haven't had much luck in sending this file; delete it. file.remove(true); + pingsDiscarded++; return; } + // This file is a bit stale, and overdue for sending. + if (now - file.lastModifiedTime > OVERDUE_PING_FILE_AGE) { + pingsOverdue++; + } + pingsLoaded++; if (sync) { let stream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); stream.init(file, -1, -1, 0); - addToPendingPings(file, stream, onLoad); + addToPendingPings(file, stream, onLoad, onDone); } else { let channel = NetUtil.newChannel(file); channel.contentType = "application/json"; @@ -191,7 +218,7 @@ this.TelemetryFile = { if (!Components.isSuccessCode(result)) { return; } - addToPendingPings(file, stream, onLoad); + addToPendingPings(file, stream, onLoad, onDone); }).bind(this)); } }, @@ -203,6 +230,22 @@ this.TelemetryFile = { return pingsLoaded; }, + /** + * The number of pings loaded that are older than OVERDUE_PING_FILE_AGE + * but younger than MAX_PING_FILE_AGE. + */ + get pingsOverdue() { + return pingsOverdue; + }, + + /** + * The number of pings that we just tossed out for being older than + * MAX_PING_FILE_AGE. + */ + get pingsDiscarded() { + return pingsDiscarded; + }, + /** * Iterate destructively through the pending pings. * @@ -249,7 +292,7 @@ function ensurePingDirectory() { return directory; }; -function addToPendingPings(file, stream, onLoad) { +function addToPendingPings(file, stream, onLoad, onDone) { let success = false; try { @@ -263,19 +306,25 @@ function addToPendingPings(file, stream, onLoad) { } pingLoadsCompleted++; pendingPings.push(ping); - if (shouldNotifyUponSave && - pingLoadsCompleted == pingsLoaded) { - Services.obs.notifyObservers(null, "telemetry-test-load-complete", null); - } success = true; } catch (e) { // An error reading the file, or an error parsing the contents. stream.close(); // close is idempotent. file.remove(true); // FIXME: Should be false, isn't it? } + if (onLoad) { onLoad(success); } + + if (pingLoadsCompleted == pingsLoaded) { + if (onDone) { + onDone(); + } + if (shouldNotifyUponSave) { + Services.obs.notifyObservers(null, "telemetry-test-load-complete", null); + } + } }; function finishTelemetrySave(ok, stream) { diff --git a/toolkit/components/telemetry/TelemetryPing.js b/toolkit/components/telemetry/TelemetryPing.js index 6a34661fb0e0..b265432610ac 100644 --- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -39,6 +39,8 @@ const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID"; const TELEMETRY_INTERVAL = 60000; // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = 60000; +// Delay before initializing telemetry if we're testing (ms) +const TELEMETRY_TEST_DELAY = 100; // Seconds of idle time before pinging. // On idle-daily a gather-telemetry notification is fired, during it probes can @@ -218,6 +220,9 @@ TelemetryPing.prototype = { ret.savedPings = TelemetryFile.pingsLoaded; } + ret.pingsOverdue = TelemetryFile.pingsOverdue; + ret.pingsDiscarded = TelemetryFile.pingsDiscarded; + return ret; }, @@ -598,7 +603,9 @@ TelemetryPing.prototype = { popPayloads: function popPayloads(reason) { function payloadIter() { - yield this.getSessionPayloadAndSlug(reason); + if (reason != "overdue-flush") { + yield this.getSessionPayloadAndSlug(reason); + } let iterator = TelemetryFile.popPendingPings(reason); for (let data of iterator) { yield data; @@ -760,7 +767,7 @@ TelemetryPing.prototype = { /** * Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry. */ - setup: function setup() { + setup: function setup(aTesting) { // Initialize some probes that are kept in their own modules this._thirdPartyCookies = new ThirdPartyCookieProbe(); this._thirdPartyCookies.init(); @@ -823,7 +830,17 @@ TelemetryPing.prototype = { { let success_histogram = Telemetry.getHistogramById("READ_SAVED_PING_SUCCESS"); success_histogram.add(success); - })); + }), () => + { + // If we have any TelemetryPings lying around, we'll be aggressive + // and try to send them all off ASAP. + if (TelemetryFile.pingsOverdue > 0) { + // It doesn't really matter what we pass to this.send as a reason, + // since it's never sent to the server. All that this.send does with + // the reason is check to make sure it's not a test-ping. + this.send("overdue-flush", this._server); + } + }); this.attachObservers(); this.gatherMemory(); @@ -831,7 +848,8 @@ TelemetryPing.prototype = { }); delete this._timer; } - this._timer.initWithCallback(timerCallback.bind(this), TELEMETRY_DELAY, + this._timer.initWithCallback(timerCallback.bind(this), + aTesting ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY, Ci.nsITimer.TYPE_ONE_SHOT); }, diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js index a9c70abaa70d..a7ecadcd8511 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryPing.js @@ -142,7 +142,7 @@ function decodeRequestPayload(request) { let observer = { buffer: "", onStreamComplete: function(loader, context, status, length, result) { - this.buffer = String.fromCharCode.apply(this, result); + this.buffer = String.fromCharCode.apply(this, result); } }; @@ -363,7 +363,7 @@ function runOldPingFileTest() { do_check_true(histogramsFile.exists()); let mtime = histogramsFile.lastModifiedTime; - histogramsFile.lastModifiedTime = mtime - 8 * 24 * 60 * 60 * 1000; // 8 days. + histogramsFile.lastModifiedTime = mtime - (14 * 24 * 60 * 60 * 1000 + 60000); // 14 days, 1m TelemetryPing.testLoadHistograms(histogramsFile, true); do_check_false(histogramsFile.exists()); } diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js new file mode 100644 index 000000000000..f840563b0fe9 --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js @@ -0,0 +1,265 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ + +/** + * This test case populates the profile with some fake stored + * pings, and checks that: + * + * 1) Pings that are considered "expired" are deleted and never sent. + * 2) Pings that are considered "overdue" trigger a send of all + * overdue and recent pings. + */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Get the TelemetryPing definitions directly so we can test it without going through xpcom. +// That gives us Cc, Ci, Cr and Cu, as well as a number of consts like PREF_ENABLED, +// and PREF_SERVER. +Services.scriptloader.loadSubScript("resource://gre/components/TelemetryPing.js"); + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/TelemetryFile.jsm"); + +// We increment TelemetryFile's MAX_PING_FILE_AGE and +// OVERDUE_PING_FILE_AGE by 1ms so that our test pings exceed +// those points in time. +const EXPIRED_PING_FILE_AGE = TelemetryFile.MAX_PING_FILE_AGE + 1; +const OVERDUE_PING_FILE_AGE = TelemetryFile.OVERDUE_PING_FILE_AGE + 1; + +const PING_SAVE_FOLDER = "saved-telemetry-pings"; +const PING_TIMEOUT_LENGTH = 5000; +const EXPIRED_PINGS = 5; +const OVERDUE_PINGS = 6; +const RECENT_PINGS = 4; + +const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS; + +let gHttpServer = new HttpServer(); +let gCreatedPings = 0; +let gSeenPings = 0; + +/** + * Creates some TelemetryPings for the current session and + * saves them to disk. Each ping gets a unique ID slug based on + * an incrementor. + * + * @param aNum the number of pings to create. + * @param aAge the age in milliseconds to offset from now. A value + * of 10 would make the ping 10ms older than now, for + * example. + * @returns an Array with the created pings. + */ +function createSavedPings(aNum, aAge) { + // Create a TelemetryPing service that we can generate payloads from. + // Luckily, the TelemetryPing constructor does nothing that we need to + // clean up. + let pingService = new TelemetryPing(); + let pings = []; + let age = Date.now() - aAge; + for (let i = 0; i < aNum; ++i) { + let payload = pingService.getPayload(); + let ping = { slug: "test-ping-" + gCreatedPings, reason: "test", payload: payload }; + TelemetryFile.savePing(ping); + if (aAge) { + // savePing writes to the file synchronously, so we're good to + // modify the lastModifedTime now. + let file = getSaveFileForPing(ping); + file.lastModifiedTime = age; + } + gCreatedPings++; + pings.push(ping); + } + return pings; +} + +/** + * Deletes locally saved pings in aPings if they + * exist. + * + * @param aPings an Array of pings to delete. + */ +function clearPings(aPings) { + for (let ping of aPings) { + let file = getSaveFileForPing(ping); + if (file.exists()) { + file.remove(false); + } + } +} + +/** + * Returns a handle for the file that aPing should be + * stored in locally. + * + * @returns nsILocalFile + */ +function getSaveFileForPing(aPing) { + let file = Services.dirsvc.get("ProfD", Ci.nsILocalFile).clone(); + file.append(PING_SAVE_FOLDER); + file.append(aPing.slug); + return file; +} + +/** + * Wait for PING_TIMEOUT_LENGTH ms, and make sure we didn't receive + * TelemetryPings in that time. + * + * @returns Promise + */ +function assertReceivedNoPings() { + let deferred = Promise.defer(); + + do_timeout(PING_TIMEOUT_LENGTH, function() { + if (gSeenPings > 0) { + deferred.reject(); + } else { + deferred.resolve(); + } + }); + + return deferred.promise; +} + +/** + * Returns a Promise that rejects if the number of TelemetryPings + * received by the HttpServer is not equal to aExpectedNum. + * + * @param aExpectedNum the number of pings we expect to receive. + * @returns Promise + */ +function assertReceivedPings(aExpectedNum) { + let deferred = Promise.defer(); + + do_timeout(PING_TIMEOUT_LENGTH, function() { + if (gSeenPings == aExpectedNum) { + deferred.resolve(); + } else { + deferred.reject("Saw " + gSeenPings + " TelemetryPings, " + + "but expected " + aExpectedNum); + } + }) + + return deferred.promise; +} + +/** + * Throws if any pings in aPings is saved locally. + * + * @param aPings an Array of pings to check. + */ +function assertNotSaved(aPings) { + let saved = 0; + for (let ping of aPings) { + let file = getSaveFileForPing(ping); + if (file.exists()) { + saved++; + } + } + if (saved > 0) { + do_throw("Found " + saved + " unexpected saved pings."); + } +} + +/** + * Our handler function for the HttpServer that simply + * increments the gSeenPings global when it successfully + * receives and decodes a TelemetryPing payload. + * + * @param aRequest the HTTP request sent from HttpServer. + */ +function pingHandler(aRequest) { + gSeenPings++; +} + +/** + * Returns a Promise that resolves when gHttpServer has been + * successfully shut down. + * + * @returns Promise + */ +function stopHttpServer() { + let deferred = Promise.defer(); + gHttpServer.stop(function() { + deferred.resolve(); + }) + return deferred.promise; +} + +/** + * Teardown a TelemetryPing instance and clear out any pending + * pings to put as back in the starting state. + */ +function resetTelemetry(aPingService) { + aPingService.uninstall(); + // Quick and dirty way to clear TelemetryFile's pendingPings + // collection, and put it back in its initial state. + let gen = TelemetryFile.popPendingPings(); + for (let item of gen) {}; +} + +/** + * Creates and returns a TelemetryPing instance in "testing" + * mode. + */ +function startTelemetry() { + let service = new TelemetryPing(); + service.setup(true); + return service; +} + +function run_test() { + gHttpServer.registerPrefixHandler("/submit/telemetry/", pingHandler); + gHttpServer.start(-1); + do_get_profile(); + Services.prefs.setBoolPref(PREF_ENABLED, true); + Services.prefs.setCharPref(PREF_SERVER, + "http://localhost:" + gHttpServer.identity.primaryPort); + run_next_test(); +} + +/** + * Test that pings that are considered too old are just chucked out + * immediately and never sent. + */ +add_task(function test_expired_pings_are_deleted() { + let expiredPings = createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); + let pingService = startTelemetry(); + yield assertReceivedNoPings(); + assertNotSaved(expiredPings); + resetTelemetry(pingService); +}) + +/** + * Test that really recent pings are not sent on Telemetry initialization. + */ +add_task(function test_recent_pings_not_sent() { + let recentPings = createSavedPings(RECENT_PINGS); + let pingService = startTelemetry(); + yield assertReceivedNoPings(); + resetTelemetry(pingService); + clearPings(recentPings); +}); + +/** + * Create some recent, expired and overdue pings. The overdue pings should + * trigger a send of all recent and overdue pings, but the expired pings + * should just be deleted. + */ +add_task(function test_overdue_pings_trigger_send() { + let recentPings = createSavedPings(RECENT_PINGS); + let expiredPings = createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE); + let overduePings = createSavedPings(OVERDUE_PINGS, OVERDUE_PING_FILE_AGE); + + let pingService = startTelemetry(); + yield assertReceivedPings(TOTAL_EXPECTED_PINGS); + + assertNotSaved(recentPings); + assertNotSaved(expiredPings); + assertNotSaved(overduePings); + resetTelemetry(pingService); +}) + +add_task(function teardown() { + yield stopHttpServer(); +}); diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index 339219fedcdc..cccc7a40baa6 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -12,3 +12,5 @@ tail = [test_TelemetryStopwatch.js] [test_TelemetryPingBuildID.js] [test_ThirdPartyCookieProbe.js] +[test_TelemetrySendOldPings.js] +skip-if = debug == true || os == "android" # Disabled due to intermittent orange on Android diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index 1dea725bd03e..1851a606cf4d 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -445,14 +445,28 @@ OMNIJAR_NAME := $(notdir $(OMNIJAR_NAME)) # thing if there are resource changes in between build time and # package time. We try to prevent mismatched resources by erroring # out if the compiled resource IDs are not the same as the resource -# IDs being packaged. +# IDs being packaged. If we're doing a single locale repack, however, +# we don't have a complete object directory, so we can't compare +# resource IDs. + +# A note on the res/ directory. We unzip the ap_ during packaging, +# which produces the res/ directory. This directory is then included +# in the final package. When we unpack (during locale repacks), we +# need to remove the res/ directory because these resources confuse +# the l10n packaging script that updates omni.ja: the script tries to +# localize the contents of the res/ directory, which fails. Instead, +# after the l10n packaging script completes, we build the ap_ +# described above (which includes freshly localized Android resources) +# and the res/ directory is taken from the ap_ as part of the regular +# packaging. PKG_SUFFIX = .apk INNER_MAKE_PACKAGE = \ $(if $(ALREADY_SZIPPED),,$(foreach lib,$(SZIP_LIBRARIES),host/bin/szip $(MOZ_SZIP_FLAGS) $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/$(lib) && )) \ make -C $(GECKO_APP_AP_PATH) gecko-nodeps.ap_ && \ cp $(GECKO_APP_AP_PATH)/gecko-nodeps.ap_ $(_ABS_DIST)/gecko.ap_ && \ - ( diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \ + ( (test ! -f $(GECKO_APP_AP_PATH)/R.txt && echo "*** Warning: The R.txt that is being packaged might not agree with the R.txt that was built. This is normal during l10n repacks.") || \ + diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \ (echo "*** Error: The R.txt that was built and the R.txt that is being packaged are not the same. Rebuild mobile/android/base and re-package." && exit 1)) && \ ( cd $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH) && \ mkdir -p lib/$(ABI_DIR) && \ @@ -487,6 +501,7 @@ INNER_UNMAKE_PACKAGE = \ mv lib/$(ABI_DIR)/libmozglue.so . && \ mv lib/$(ABI_DIR)/*plugin-container* $(MOZ_CHILD_PROCESS_NAME) && \ rm -rf lib/$(ABI_DIR) \ + rm -rf res \ $(if $(filter-out ./,$(OMNIJAR_DIR)), \ && mv $(OMNIJAR_DIR)$(OMNIJAR_NAME) $(OMNIJAR_NAME)) ) endif diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 1bfbee8e53a9..eeee4ec3045f 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -75,6 +75,7 @@ if CONFIG['MOZ_OMX_PLUGIN']: add_tier_dir('platform', [ 'media/omx-plugin/lib/ics/libutils', 'media/omx-plugin/lib/ics/libstagefright', + 'media/omx-plugin/lib/ics/libvideoeditorplayer', 'media/omx-plugin/lib/gb/libutils', 'media/omx-plugin/lib/gb/libstagefright', 'media/omx-plugin/lib/gb/libstagefright_color_conversion', diff --git a/xpcom/glue/nsISupportsImpl.cpp b/xpcom/glue/nsISupportsImpl.cpp index 4f0170c5d653..75198aacce68 100644 --- a/xpcom/glue/nsISupportsImpl.cpp +++ b/xpcom/glue/nsISupportsImpl.cpp @@ -8,7 +8,7 @@ nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, const QITableEntry* entries, REFNSIID aIID, void **aInstancePtr) { - while (entries->iid) { + do { if (aIID.Equals(*entries->iid)) { nsISupports* r = reinterpret_cast @@ -19,7 +19,7 @@ NS_TableDrivenQI(void* aThis, const QITableEntry* entries, } ++entries; - } + } while (entries->iid); *aInstancePtr = nullptr; return NS_ERROR_NO_INTERFACE; diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h index bd09a65f1711..27abc32a1cb1 100644 --- a/xpcom/glue/nsISupportsImpl.h +++ b/xpcom/glue/nsISupportsImpl.h @@ -609,8 +609,15 @@ NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) \ reinterpret_cast((_class*) 0x1000)) \ }, +/* + * XXX: we want to use mozilla::ArrayLength (or equivalent, + * MOZ_ARRAY_LENGTH) in this condition, but some versions of GCC don't + * see that the static_assert condition is actually constant in those + * cases, even with constexpr support (?). + */ #define NS_INTERFACE_TABLE_END_WITH_PTR(_ptr) \ { nullptr, 0 } }; \ + static_assert((sizeof(table)/sizeof(table[0])) > 1, "need at least 1 interface"); \ rv = NS_TableDrivenQI(static_cast(_ptr), \ table, aIID, aInstancePtr); diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index 402a4cdaa97d..714fef38ec65 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -18,8 +18,8 @@ #include "nsAutoPtr.h" // A native thread -class nsThread MOZ_FINAL : public nsIThreadInternal, - public nsISupportsPriority +class nsThread : public nsIThreadInternal, + public nsISupportsPriority { public: NS_DECL_THREADSAFE_ISUPPORTS @@ -54,7 +54,7 @@ public: static nsresult SetMainThreadObserver(nsIThreadObserver* aObserver); -private: +protected: static nsIThreadObserver* sMainThreadObserver; class nsChainedEventQueue; @@ -64,7 +64,7 @@ private: friend class nsThreadShutdownEvent; - ~nsThread(); + virtual ~nsThread(); bool ShuttingDown() { return mShutdownContext != nullptr; }