/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Web Workers. * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ben Turner (Original Author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef mozilla_dom_workers_workerprivate_h__ #define mozilla_dom_workers_workerprivate_h__ #include "Workers.h" #include "nsIRunnable.h" #include "nsIThread.h" #include "nsIThreadInternal.h" #include "jsapi.h" #include "mozilla/CondVar.h" #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" #include "nsAutoPtr.h" #include "nsCOMPtr.h" #include "nsEventQueue.h" #include "nsStringGlue.h" #include "nsTArray.h" #include "nsTPriorityQueue.h" #include "EventTarget.h" #include "Queue.h" #include "WorkerFeature.h" class JSAutoStructuredCloneBuffer; class nsIDocument; class nsIPrincipal; class nsIScriptContext; class nsIURI; class nsPIDOMWindow; class nsITimer; namespace mozilla { namespace xpconnect { namespace memory { struct IterateData; } // namespace memory } // namespace xpconnect } // namespace mozilla BEGIN_WORKERS_NAMESPACE class WorkerPrivate; class WorkerRunnable : public nsIRunnable { public: enum Target { ParentThread, WorkerThread }; enum BusyBehavior { ModifyBusyCount, UnchangedBusyCount }; protected: WorkerPrivate* mWorkerPrivate; Target mTarget; bool mBusyBehavior; public: NS_DECL_ISUPPORTS bool Dispatch(JSContext* aCx); static bool DispatchToMainThread(nsIRunnable*); protected: WorkerRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, BusyBehavior aBusyBehavior) #ifdef DEBUG ; #else : mWorkerPrivate(aWorkerPrivate), mTarget(aTarget), mBusyBehavior(aBusyBehavior) { } #endif virtual ~WorkerRunnable() { } 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); private: NS_DECL_NSIRUNNABLE }; class WorkerSyncRunnable : public WorkerRunnable { protected: PRUint32 mSyncQueueKey; bool mBypassSyncQueue; protected: friend class WorkerPrivate; WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, PRUint32 aSyncQueueKey, bool aBypassSyncQueue = false) : WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount), mSyncQueueKey(aSyncQueueKey), mBypassSyncQueue(aBypassSyncQueue) { } virtual ~WorkerSyncRunnable() { } virtual bool DispatchInternal(); }; class WorkerControlRunnable : public WorkerRunnable { protected: WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget, BusyBehavior aBusyBehavior) : WorkerRunnable(aWorkerPrivate, aTarget, aBusyBehavior) { } virtual ~WorkerControlRunnable() { } virtual bool DispatchInternal(); }; template class WorkerPrivateParent : public events::EventTarget { public: struct LocationInfo { nsCString mHref; nsCString mProtocol; nsCString mHost; nsCString mHostname; nsCString mPort; nsCString mPathname; nsCString mSearch; nsCString mHash; }; protected: mozilla::Mutex mMutex; mozilla::CondVar mCondVar; private: JSObject* mJSObject; WorkerPrivate* mParent; JSContext* mParentJSContext; nsString mScriptURL; nsCString mDomain; LocationInfo mLocationInfo; // Main-thread things. nsCOMPtr mWindow; nsCOMPtr mScriptContext; nsCOMPtr mBaseURI; nsCOMPtr mScriptURI; nsCOMPtr mPrincipal; nsCOMPtr mDocument; // Only used for top level workers. nsTArray > mQueuedRunnables; PRUint64 mBusyCount; Status mParentStatus; PRUint32 mJSContextOptions; PRUint8 mGCZeal; bool mJSObjectRooted; bool mParentSuspended; bool mIsChromeWorker; bool mPrincipalIsSystem; protected: WorkerPrivateParent(JSContext* aCx, JSObject* aObject, WorkerPrivate* aParent, JSContext* aParentJSContext, const nsAString& aScriptURL, bool aIsChromeWorker, const nsACString& aDomain, nsCOMPtr& aWindow, nsCOMPtr& aScriptContext, nsCOMPtr& aBaseURI, nsCOMPtr& aPrincipal, nsCOMPtr& aDocument); ~WorkerPrivateParent(); private: Derived* ParentAsWorkerPrivate() const { return static_cast(const_cast(this)); } public: // May be called on any thread... bool Start(); // Called on the parent thread. bool Notify(JSContext* aCx, Status aStatus); bool Cancel(JSContext* aCx) { return Notify(aCx, Canceling); } bool Kill(JSContext* aCx) { return Notify(aCx, Killing); } bool Suspend(JSContext* aCx); bool Resume(JSContext* aCx); void TraceInstance(JSTracer* aTrc) { AssertIsOnParentThread(); events::EventTarget::TraceInstance(aTrc); } void FinalizeInstance(JSContext* aCx); bool Terminate(JSContext* aCx) { return Notify(aCx, Terminating); } bool Close(JSContext* aCx); bool ModifyBusyCount(JSContext* aCx, bool aIncrease); bool RootJSObject(JSContext* aCx, bool aRoot); void ForgetMainThreadObjects(nsTArray >& aDoomed); bool PostMessage(JSContext* aCx, jsval aMessage); PRUint64 GetInnerWindowId(); void UpdateJSContextOptions(JSContext* aCx, PRUint32 aOptions); #ifdef JS_GC_ZEAL void UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal); #endif using events::EventTarget::GetEventListenerOnEventTarget; using events::EventTarget::SetEventListenerOnEventTarget; void QueueRunnable(WorkerRunnable* aRunnable) { AssertIsOnMainThread(); mQueuedRunnables.AppendElement(aRunnable); } WorkerPrivate* GetParent() const { return mParent; } bool IsSuspended() const { AssertIsOnParentThread(); return mParentSuspended; } Status ParentStatus() const { mMutex.AssertCurrentThreadOwns(); return mParentStatus; } JSContext* ParentJSContext() const; nsIScriptContext* GetScriptContext() const { AssertIsOnMainThread(); return mScriptContext; } JSObject* GetJSObject() const { return mJSObject; } const nsString& ScriptURL() const { return mScriptURL; } const nsCString& Domain() const { return mDomain; } nsIURI* GetBaseURI() const { AssertIsOnMainThread(); return mBaseURI; } nsresult SetBaseURI(nsIURI* aBaseURI); nsIURI* GetScriptURI() const { AssertIsOnMainThread(); return mScriptURI; } void SetScriptURI(nsIURI* aScriptURI) { AssertIsOnMainThread(); mScriptURI = aScriptURI; } nsIPrincipal* GetPrincipal() const { AssertIsOnMainThread(); return mPrincipal; } void SetPrincipal(nsIPrincipal* aPrincipal); bool UsesSystemPrincipal() const { return mPrincipalIsSystem; } nsIDocument* GetDocument() const { AssertIsOnMainThread(); return mDocument; } void SetDocument(nsIDocument* aDocument) { AssertIsOnMainThread(); mDocument = aDocument; } nsPIDOMWindow* GetWindow() { AssertIsOnMainThread(); return mWindow; } LocationInfo& GetLocationInfo() { return mLocationInfo; } PRUint32 GetJSContextOptions() const { return mJSContextOptions; } #ifdef JS_GC_ZEAL PRUint8 GetGCZeal() const { return mGCZeal; } #endif bool IsChromeWorker() const { return mIsChromeWorker; } #ifdef DEBUG void AssertIsOnParentThread() const; void AssertInnerWindowIsCorrect() const; #else void AssertIsOnParentThread() const { } void AssertInnerWindowIsCorrect() const { } #endif }; class WorkerPrivate : public WorkerPrivateParent { friend class WorkerPrivateParent; typedef WorkerPrivateParent ParentType; struct TimeoutInfo; typedef Queue EventQueue; EventQueue mQueue; EventQueue mControlQueue; struct SyncQueue { Queue mQueue; bool mComplete; bool mResult; SyncQueue() : mComplete(false), mResult(false) { } ~SyncQueue() { nsIRunnable* event; while (mQueue.Pop(event)) { event->Release(); } } }; nsTArray > mSyncQueues; // Touched on multiple threads, protected with mMutex. JSContext* mJSContext; // Things touched on worker thread only. nsTArray mChildWorkers; nsTArray mFeatures; nsTArray > mTimeouts; nsCOMPtr mTimer; mozilla::TimeStamp mKillTime; PRUint32 mErrorHandlerRecursionCount; PRUint32 mNextTimeoutId; Status mStatus; bool mSuspended; bool mTimerRunning; bool mRunningExpiredTimeouts; bool mCloseHandlerStarted; bool mCloseHandlerFinished; #ifdef DEBUG nsCOMPtr mThread; #endif public: ~WorkerPrivate(); static WorkerPrivate* Create(JSContext* aCx, JSObject* aObj, WorkerPrivate* aParent, JSString* aScriptURL, bool aIsChromeWorker); void DoRunLoop(JSContext* aCx); 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); } bool CloseInternal(JSContext* aCx) { AssertIsOnWorkerThread(); return NotifyInternal(aCx, Closing); } bool SuspendInternal(JSContext* aCx); bool ResumeInternal(JSContext* aCx); void TraceInternal(JSTracer* aTrc); bool ModifyBusyCountFromWorker(JSContext* aCx, bool aIncrease); bool AddChildWorker(JSContext* aCx, ParentType* aChildWorker); void RemoveChildWorker(JSContext* aCx, ParentType* aChildWorker); bool AddFeature(JSContext* aCx, WorkerFeature* aFeature); void RemoveFeature(JSContext* aCx, WorkerFeature* aFeature); void NotifyFeatures(JSContext* aCx, Status aStatus); bool HasActiveFeatures() { return !(mChildWorkers.IsEmpty() && mTimeouts.IsEmpty() && mFeatures.IsEmpty()); } PRUint32 CreateNewSyncLoop(); bool RunSyncLoop(JSContext* aCx, PRUint32 aSyncLoopKey); void StopSyncLoop(PRUint32 aSyncLoopKey, bool aSyncResult); bool PostMessageToParent(JSContext* aCx, jsval aMessage); bool NotifyInternal(JSContext* aCx, Status aStatus); void ReportError(JSContext* aCx, const char* aMessage, JSErrorReport* aReport); bool SetTimeout(JSContext* aCx, uintN aArgc, jsval* aVp, bool aIsInterval); bool ClearTimeout(JSContext* aCx, uint32 aId); bool RunExpiredTimeouts(JSContext* aCx); bool RescheduleTimeoutTimer(JSContext* aCx); void CloseHandlerStarted() { AssertIsOnWorkerThread(); mCloseHandlerStarted = true; } void CloseHandlerFinished() { AssertIsOnWorkerThread(); mCloseHandlerFinished = true; } void UpdateJSContextOptionsInternal(JSContext* aCx, PRUint32 aOptions); void ScheduleDeletion(bool aWasPending); bool BlockAndCollectRuntimeStats(mozilla::xpconnect::memory::IterateData* aData); #ifdef JS_GC_ZEAL void UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal); #endif JSContext* GetJSContext() const { AssertIsOnWorkerThread(); return mJSContext; } #ifdef DEBUG void AssertIsOnWorkerThread() const; void SetThread(nsIThread* aThread) { mThread = aThread; } #else void AssertIsOnWorkerThread() const { } #endif private: WorkerPrivate(JSContext* aCx, JSObject* aObject, WorkerPrivate* aParent, JSContext* aParentJSContext, const nsAString& aScriptURL, bool aIsChromeWorker, const nsACString& aDomain, nsCOMPtr& aWindow, nsCOMPtr& aScriptContext, nsCOMPtr& aBaseURI, nsCOMPtr& aPrincipal, nsCOMPtr& aDocument); bool Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue); bool DispatchToSyncQueue(WorkerSyncRunnable* aEvent); void ClearQueue(EventQueue* aQueue); bool MayContinueRunning() { AssertIsOnWorkerThread(); Status status; { mozilla::MutexAutoLock lock(mMutex); status = mStatus; } if (status >= Killing) { return false; } if (status >= Running) { return mKillTime.IsNull() || RemainingRunTimeMS() > 0; } return true; } PRUint32 RemainingRunTimeMS() const; void CancelAllTimeouts(JSContext* aCx); bool ScheduleKillCloseEventRunnable(JSContext* aCx); void StopAcceptingEvents() { AssertIsOnWorkerThread(); mozilla::MutexAutoLock lock(mMutex); mStatus = Dead; mJSContext = nsnull; ClearQueue(&mQueue); ClearQueue(&mControlQueue); } }; WorkerPrivate* GetWorkerPrivateFromContext(JSContext* aCx); enum WorkerStructuredDataType { DOMWORKER_SCTAG_FILE = JS_SCTAG_USER_MIN + 0x1000, DOMWORKER_SCTAG_BLOB, DOMWORKER_SCTAG_END }; JSStructuredCloneCallbacks* WorkerStructuredCloneCallbacks(bool aMainRuntime); JSStructuredCloneCallbacks* ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime); END_WORKERS_NAMESPACE #endif /* mozilla_dom_workers_workerprivate_h__ */