mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 09:05:45 +00:00
20e5c3933b
This part replaces the JSContext *cx argument in most GC-related API with JSRuntime *rt. When possible, the patch removes the code to obtain a temporary cx just to run the GC. The patch also removes JS_DestroyContextMaybeGC. That function is not used in FF code base and its implementation is broken. It requires that the context has an entered compartment when it is destroyed, which in turns implies a missing leave compartment call.
3995 lines
106 KiB
C++
3995 lines
106 KiB
C++
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* ***** 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 <bent.mozilla@gmail.com> (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 ***** */
|
|
|
|
#include "WorkerPrivate.h"
|
|
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "nsIClassInfo.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIDOMFile.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIJSContextStack.h"
|
|
#include "nsIMemoryReporter.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsITextToSubURI.h"
|
|
#include "nsITimer.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIXPConnect.h"
|
|
|
|
#include "jsfriendapi.h"
|
|
#include "jsdbgapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "jsprf.h"
|
|
#include "js/MemoryMetrics.h"
|
|
#include "nsAlgorithm.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDOMClassInfo.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsGUIEvent.h"
|
|
#include "nsJSEnvironment.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "xpcpublic.h"
|
|
|
|
#ifdef ANDROID
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
#include "Events.h"
|
|
#include "Exceptions.h"
|
|
#include "File.h"
|
|
#include "Principal.h"
|
|
#include "RuntimeService.h"
|
|
#include "ScriptLoader.h"
|
|
#include "Worker.h"
|
|
#include "WorkerFeature.h"
|
|
#include "WorkerScope.h"
|
|
|
|
#if 0 // Define to run GC more often.
|
|
#define EXTRA_GC
|
|
#endif
|
|
|
|
// GC will run once every thirty seconds during normal execution.
|
|
#define NORMAL_GC_TIMER_DELAY_MS 30000
|
|
|
|
// GC will run five seconds after the last event is processed.
|
|
#define IDLE_GC_TIMER_DELAY_MS 5000
|
|
|
|
using mozilla::MutexAutoLock;
|
|
using mozilla::TimeDuration;
|
|
using mozilla::TimeStamp;
|
|
using mozilla::dom::workers::exceptions::ThrowDOMExceptionForCode;
|
|
using mozilla::xpconnect::memory::ReportJSRuntimeExplicitTreeStats;
|
|
|
|
USING_WORKERS_NAMESPACE
|
|
using namespace mozilla::dom::workers::events;
|
|
|
|
namespace {
|
|
|
|
const char gErrorChars[] = "error";
|
|
const char gMessageChars[] = "message";
|
|
|
|
template <class T>
|
|
class AutoPtrComparator
|
|
{
|
|
typedef nsAutoPtr<T> A;
|
|
typedef T* B;
|
|
|
|
public:
|
|
bool Equals(const A& a, const B& b) const {
|
|
return a && b ? *a == *b : !a && !b ? true : false;
|
|
}
|
|
bool LessThan(const A& a, const B& b) const {
|
|
return a && b ? *a < *b : b ? true : false;
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
inline AutoPtrComparator<T>
|
|
GetAutoPtrComparator(const nsTArray<nsAutoPtr<T> >&)
|
|
{
|
|
return AutoPtrComparator<T>();
|
|
}
|
|
|
|
// Specialize this if there's some class that has multiple nsISupports bases.
|
|
template <class T>
|
|
struct ISupportsBaseInfo
|
|
{
|
|
typedef T ISupportsBase;
|
|
};
|
|
|
|
template <template <class> class SmartPtr, class T>
|
|
inline void
|
|
SwapToISupportsArray(SmartPtr<T>& aSrc,
|
|
nsTArray<nsCOMPtr<nsISupports> >& aDest)
|
|
{
|
|
nsCOMPtr<nsISupports>* dest = aDest.AppendElement();
|
|
|
|
T* raw = nsnull;
|
|
aSrc.swap(raw);
|
|
|
|
nsISupports* rawSupports =
|
|
static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
|
|
dest->swap(rawSupports);
|
|
}
|
|
|
|
NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsWorkerMallocSizeOf, "js-worker")
|
|
|
|
struct WorkerJSRuntimeStats : public JS::RuntimeStats
|
|
{
|
|
WorkerJSRuntimeStats()
|
|
: JS::RuntimeStats(JsWorkerMallocSizeOf) { }
|
|
|
|
virtual void initExtraCompartmentStats(JSCompartment *c,
|
|
JS::CompartmentStats *cstats) MOZ_OVERRIDE
|
|
{
|
|
MOZ_ASSERT(!cstats->extra);
|
|
|
|
// ReportJSRuntimeExplicitTreeStats expects that cstats->extra is a char pointer
|
|
const char *name = js::IsAtomsCompartment(c) ? "Web Worker Atoms" : "Web Worker";
|
|
cstats->extra = const_cast<char *>(name);
|
|
}
|
|
};
|
|
|
|
class WorkerMemoryReporter : public nsIMemoryMultiReporter
|
|
{
|
|
WorkerPrivate* mWorkerPrivate;
|
|
nsCString mAddressString;
|
|
nsCString mPathPrefix;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate)
|
|
: mWorkerPrivate(aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
nsCString escapedDomain(aWorkerPrivate->Domain());
|
|
escapedDomain.ReplaceChar('/', '\\');
|
|
|
|
NS_ConvertUTF16toUTF8 escapedURL(aWorkerPrivate->ScriptURL());
|
|
escapedURL.ReplaceChar('/', '\\');
|
|
|
|
{
|
|
// 64bit address plus '0x' plus null terminator.
|
|
char address[21];
|
|
uint32_t addressSize =
|
|
JS_snprintf(address, sizeof(address), "0x%llx", aWorkerPrivate);
|
|
if (addressSize != uint32_t(-1)) {
|
|
mAddressString.Assign(address, addressSize);
|
|
}
|
|
else {
|
|
NS_WARNING("JS_snprintf failed!");
|
|
mAddressString.AssignLiteral("<unknown address>");
|
|
}
|
|
}
|
|
|
|
mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") +
|
|
escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
|
|
escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString +
|
|
NS_LITERAL_CSTRING(")/");
|
|
}
|
|
|
|
nsresult
|
|
CollectForRuntime(bool aIsQuick, void* aData)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (mWorkerPrivate) {
|
|
bool disabled;
|
|
if (!mWorkerPrivate->BlockAndCollectRuntimeStats(aIsQuick, aData, &disabled)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Don't ever try to talk to the worker again.
|
|
if (disabled) {
|
|
#ifdef DEBUG
|
|
{
|
|
nsCAutoString message("Unable to report memory for ");
|
|
if (mWorkerPrivate->IsChromeWorker()) {
|
|
message.AppendLiteral("Chrome");
|
|
}
|
|
message += NS_LITERAL_CSTRING("Worker (") + mAddressString +
|
|
NS_LITERAL_CSTRING(")! It is either using ctypes or is in "
|
|
"the process of being destroyed");
|
|
NS_WARNING(message.get());
|
|
}
|
|
#endif
|
|
mWorkerPrivate = nsnull;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD GetName(nsACString &aName)
|
|
{
|
|
aName.AssignLiteral("workers");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
CollectReports(nsIMemoryMultiReporterCallback* aCallback,
|
|
nsISupports* aClosure)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
WorkerJSRuntimeStats rtStats;
|
|
nsresult rv = CollectForRuntime(/* isQuick = */false, &rtStats);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// Always report, even if we're disabled, so that we at least get an entry
|
|
// in about::memory.
|
|
rv = ReportJSRuntimeExplicitTreeStats(rtStats, mPathPrefix, aCallback, aClosure);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
GetExplicitNonHeap(PRInt64 *aAmount)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
return CollectForRuntime(/* isQuick = */true, aAmount);
|
|
}
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerMemoryReporter, nsIMemoryMultiReporter)
|
|
|
|
struct WorkerStructuredCloneCallbacks
|
|
{
|
|
static JSObject*
|
|
Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
|
|
uint32_t aData, void* aClosure)
|
|
{
|
|
// See if object is a nsIDOMFile pointer.
|
|
if (aTag == DOMWORKER_SCTAG_FILE) {
|
|
JS_ASSERT(!aData);
|
|
|
|
nsIDOMFile* file;
|
|
if (JS_ReadBytes(aReader, &file, sizeof(file))) {
|
|
JS_ASSERT(file);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// File should not be mutable.
|
|
nsCOMPtr<nsIMutable> mutableFile = do_QueryInterface(file);
|
|
bool isMutable;
|
|
NS_ASSERTION(NS_SUCCEEDED(mutableFile->GetMutable(&isMutable)) &&
|
|
!isMutable,
|
|
"Only immutable file should be passed to worker");
|
|
}
|
|
#endif
|
|
|
|
// nsIDOMFiles should be threadsafe, thus we will use the same instance
|
|
// in the worker.
|
|
JSObject* jsFile = file::CreateFile(aCx, file);
|
|
return jsFile;
|
|
}
|
|
}
|
|
// See if object is a nsIDOMBlob pointer.
|
|
else if (aTag == DOMWORKER_SCTAG_BLOB) {
|
|
JS_ASSERT(!aData);
|
|
|
|
nsIDOMBlob* blob;
|
|
if (JS_ReadBytes(aReader, &blob, sizeof(blob))) {
|
|
JS_ASSERT(blob);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Blob should not be mutable.
|
|
nsCOMPtr<nsIMutable> mutableBlob = do_QueryInterface(blob);
|
|
bool isMutable;
|
|
NS_ASSERTION(NS_SUCCEEDED(mutableBlob->GetMutable(&isMutable)) &&
|
|
!isMutable,
|
|
"Only immutable blob should be passed to worker");
|
|
}
|
|
#endif
|
|
|
|
// nsIDOMBlob should be threadsafe, thus we will use the same instance
|
|
// in the worker.
|
|
JSObject* jsBlob = file::CreateBlob(aCx, blob);
|
|
return jsBlob;
|
|
}
|
|
}
|
|
|
|
Error(aCx, 0);
|
|
return nsnull;
|
|
}
|
|
|
|
static JSBool
|
|
Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
|
|
void* aClosure)
|
|
{
|
|
NS_ASSERTION(aClosure, "Null pointer!");
|
|
|
|
// We'll stash any nsISupports pointers that need to be AddRef'd here.
|
|
nsTArray<nsCOMPtr<nsISupports> >* clonedObjects =
|
|
static_cast<nsTArray<nsCOMPtr<nsISupports> >*>(aClosure);
|
|
|
|
// See if this is a File object.
|
|
{
|
|
nsIDOMFile* file = file::GetDOMFileFromJSObject(aObj);
|
|
if (file) {
|
|
if (JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_FILE, 0) &&
|
|
JS_WriteBytes(aWriter, &file, sizeof(file))) {
|
|
clonedObjects->AppendElement(file);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if this is a Blob object.
|
|
{
|
|
nsIDOMBlob* blob = file::GetDOMBlobFromJSObject(aObj);
|
|
if (blob) {
|
|
nsCOMPtr<nsIMutable> mutableBlob = do_QueryInterface(blob);
|
|
if (mutableBlob && NS_SUCCEEDED(mutableBlob->SetMutable(false)) &&
|
|
JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_BLOB, 0) &&
|
|
JS_WriteBytes(aWriter, &blob, sizeof(blob))) {
|
|
clonedObjects->AppendElement(blob);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Error(aCx, 0);
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
Error(JSContext* aCx, uint32_t /* aErrorId */)
|
|
{
|
|
ThrowDOMExceptionForCode(aCx, DATA_CLONE_ERR);
|
|
}
|
|
};
|
|
|
|
JSStructuredCloneCallbacks gWorkerStructuredCloneCallbacks = {
|
|
WorkerStructuredCloneCallbacks::Read,
|
|
WorkerStructuredCloneCallbacks::Write,
|
|
WorkerStructuredCloneCallbacks::Error
|
|
};
|
|
|
|
struct MainThreadWorkerStructuredCloneCallbacks
|
|
{
|
|
static JSObject*
|
|
Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
|
|
uint32_t aData, void* aClosure)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
// See if object is a nsIDOMFile pointer.
|
|
if (aTag == DOMWORKER_SCTAG_FILE) {
|
|
JS_ASSERT(!aData);
|
|
|
|
nsIDOMFile* file;
|
|
if (JS_ReadBytes(aReader, &file, sizeof(file))) {
|
|
JS_ASSERT(file);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// File should not be mutable.
|
|
nsCOMPtr<nsIMutable> mutableFile = do_QueryInterface(file);
|
|
bool isMutable;
|
|
NS_ASSERTION(NS_SUCCEEDED(mutableFile->GetMutable(&isMutable)) &&
|
|
!isMutable,
|
|
"Only immutable file should be passed to worker");
|
|
}
|
|
#endif
|
|
|
|
// nsIDOMFiles should be threadsafe, thus we will use the same instance
|
|
// on the main thread.
|
|
jsval wrappedFile;
|
|
nsresult rv =
|
|
nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), file,
|
|
&NS_GET_IID(nsIDOMFile), &wrappedFile);
|
|
if (NS_FAILED(rv)) {
|
|
Error(aCx, DATA_CLONE_ERR);
|
|
return nsnull;
|
|
}
|
|
|
|
return JSVAL_TO_OBJECT(wrappedFile);
|
|
}
|
|
}
|
|
// See if object is a nsIDOMBlob pointer.
|
|
else if (aTag == DOMWORKER_SCTAG_BLOB) {
|
|
JS_ASSERT(!aData);
|
|
|
|
nsIDOMBlob* blob;
|
|
if (JS_ReadBytes(aReader, &blob, sizeof(blob))) {
|
|
JS_ASSERT(blob);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Blob should not be mutable.
|
|
nsCOMPtr<nsIMutable> mutableBlob = do_QueryInterface(blob);
|
|
bool isMutable;
|
|
NS_ASSERTION(NS_SUCCEEDED(mutableBlob->GetMutable(&isMutable)) &&
|
|
!isMutable,
|
|
"Only immutable blob should be passed to worker");
|
|
}
|
|
#endif
|
|
|
|
// nsIDOMBlobs should be threadsafe, thus we will use the same instance
|
|
// on the main thread.
|
|
jsval wrappedBlob;
|
|
nsresult rv =
|
|
nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), blob,
|
|
&NS_GET_IID(nsIDOMBlob), &wrappedBlob);
|
|
if (NS_FAILED(rv)) {
|
|
Error(aCx, DATA_CLONE_ERR);
|
|
return nsnull;
|
|
}
|
|
|
|
return JSVAL_TO_OBJECT(wrappedBlob);
|
|
}
|
|
}
|
|
|
|
JSObject* clone =
|
|
WorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData, aClosure);
|
|
if (clone) {
|
|
return clone;
|
|
}
|
|
|
|
JS_ClearPendingException(aCx);
|
|
return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nsnull);
|
|
}
|
|
|
|
static JSBool
|
|
Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
|
|
void* aClosure)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
NS_ASSERTION(aClosure, "Null pointer!");
|
|
|
|
// We'll stash any nsISupports pointers that need to be AddRef'd here.
|
|
nsTArray<nsCOMPtr<nsISupports> >* clonedObjects =
|
|
static_cast<nsTArray<nsCOMPtr<nsISupports> >*>(aClosure);
|
|
|
|
// See if this is a wrapped native.
|
|
nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative;
|
|
nsContentUtils::XPConnect()->
|
|
GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative));
|
|
|
|
if (wrappedNative) {
|
|
// Get the raw nsISupports out of it.
|
|
nsISupports* wrappedObject = wrappedNative->Native();
|
|
NS_ASSERTION(wrappedObject, "Null pointer?!");
|
|
|
|
// See if the wrapped native is a nsIDOMFile.
|
|
nsCOMPtr<nsIDOMFile> file = do_QueryInterface(wrappedObject);
|
|
if (file) {
|
|
nsCOMPtr<nsIMutable> mutableFile = do_QueryInterface(file);
|
|
if (mutableFile && NS_SUCCEEDED(mutableFile->SetMutable(false))) {
|
|
nsIDOMFile* filePtr = file;
|
|
if (JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_FILE, 0) &&
|
|
JS_WriteBytes(aWriter, &filePtr, sizeof(filePtr))) {
|
|
clonedObjects->AppendElement(file);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if the wrapped native is a nsIDOMBlob.
|
|
nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(wrappedObject);
|
|
if (blob) {
|
|
nsCOMPtr<nsIMutable> mutableBlob = do_QueryInterface(blob);
|
|
if (mutableBlob && NS_SUCCEEDED(mutableBlob->SetMutable(false))) {
|
|
nsIDOMBlob* blobPtr = blob;
|
|
if (JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_BLOB, 0) &&
|
|
JS_WriteBytes(aWriter, &blobPtr, sizeof(blobPtr))) {
|
|
clonedObjects->AppendElement(blob);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
JSBool ok =
|
|
WorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj, aClosure);
|
|
if (ok) {
|
|
return ok;
|
|
}
|
|
|
|
JS_ClearPendingException(aCx);
|
|
return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull);
|
|
}
|
|
|
|
static void
|
|
Error(JSContext* aCx, uint32_t aErrorId)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
NS_DOMStructuredCloneError(aCx, aErrorId);
|
|
}
|
|
};
|
|
|
|
JSStructuredCloneCallbacks gMainThreadWorkerStructuredCloneCallbacks = {
|
|
MainThreadWorkerStructuredCloneCallbacks::Read,
|
|
MainThreadWorkerStructuredCloneCallbacks::Write,
|
|
MainThreadWorkerStructuredCloneCallbacks::Error
|
|
};
|
|
|
|
struct ChromeWorkerStructuredCloneCallbacks
|
|
{
|
|
static JSObject*
|
|
Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
|
|
uint32_t aData, void* aClosure)
|
|
{
|
|
return WorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
|
|
aClosure);
|
|
}
|
|
|
|
static JSBool
|
|
Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
|
|
void* aClosure)
|
|
{
|
|
return WorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj, aClosure);
|
|
}
|
|
|
|
static void
|
|
Error(JSContext* aCx, uint32_t aErrorId)
|
|
{
|
|
return WorkerStructuredCloneCallbacks::Error(aCx, aErrorId);
|
|
}
|
|
};
|
|
|
|
JSStructuredCloneCallbacks gChromeWorkerStructuredCloneCallbacks = {
|
|
ChromeWorkerStructuredCloneCallbacks::Read,
|
|
ChromeWorkerStructuredCloneCallbacks::Write,
|
|
ChromeWorkerStructuredCloneCallbacks::Error
|
|
};
|
|
|
|
struct MainThreadChromeWorkerStructuredCloneCallbacks
|
|
{
|
|
static JSObject*
|
|
Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
|
|
uint32_t aData, void* aClosure)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
JSObject* clone =
|
|
MainThreadWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
|
|
aClosure);
|
|
if (clone) {
|
|
return clone;
|
|
}
|
|
|
|
clone =
|
|
ChromeWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
|
|
aClosure);
|
|
if (clone) {
|
|
return clone;
|
|
}
|
|
|
|
JS_ClearPendingException(aCx);
|
|
return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nsnull);
|
|
}
|
|
|
|
static JSBool
|
|
Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
|
|
void* aClosure)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (MainThreadWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
|
|
aClosure) ||
|
|
ChromeWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
|
|
aClosure) ||
|
|
NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
Error(JSContext* aCx, uint32_t aErrorId)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
NS_DOMStructuredCloneError(aCx, aErrorId);
|
|
}
|
|
};
|
|
|
|
JSStructuredCloneCallbacks gMainThreadChromeWorkerStructuredCloneCallbacks = {
|
|
MainThreadChromeWorkerStructuredCloneCallbacks::Read,
|
|
MainThreadChromeWorkerStructuredCloneCallbacks::Write,
|
|
MainThreadChromeWorkerStructuredCloneCallbacks::Error
|
|
};
|
|
|
|
class MainThreadReleaseRunnable : public nsRunnable
|
|
{
|
|
nsCOMPtr<nsIThread> mThread;
|
|
nsTArray<nsCOMPtr<nsISupports> > mDoomed;
|
|
|
|
public:
|
|
MainThreadReleaseRunnable(nsCOMPtr<nsIThread>& aThread,
|
|
nsTArray<nsCOMPtr<nsISupports> >& aDoomed)
|
|
{
|
|
mThread.swap(aThread);
|
|
mDoomed.SwapElements(aDoomed);
|
|
}
|
|
|
|
MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports> >& aDoomed)
|
|
{
|
|
mDoomed.SwapElements(aDoomed);
|
|
}
|
|
|
|
NS_IMETHOD
|
|
Run()
|
|
{
|
|
mDoomed.Clear();
|
|
|
|
if (mThread) {
|
|
RuntimeService* runtime = RuntimeService::GetService();
|
|
NS_ASSERTION(runtime, "This should never be null!");
|
|
|
|
runtime->NoteIdleThread(mThread);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
class WorkerFinishedRunnable : public WorkerControlRunnable
|
|
{
|
|
WorkerPrivate* mFinishedWorker;
|
|
nsCOMPtr<nsIThread> mThread;
|
|
|
|
public:
|
|
WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
|
|
WorkerPrivate* aFinishedWorker,
|
|
nsIThread* aFinishedThread)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mFinishedWorker(aFinishedWorker), mThread(aFinishedThread)
|
|
{ }
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// Silence bad assertions.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
// Silence bad assertions.
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
nsTArray<nsCOMPtr<nsISupports> > doomed;
|
|
mFinishedWorker->ForgetMainThreadObjects(doomed);
|
|
|
|
nsRefPtr<MainThreadReleaseRunnable> runnable =
|
|
new MainThreadReleaseRunnable(mThread, doomed);
|
|
if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
|
|
NS_WARNING("Failed to dispatch, going to leak!");
|
|
}
|
|
|
|
mFinishedWorker->Finish(aCx);
|
|
|
|
RuntimeService* runtime = RuntimeService::GetService();
|
|
NS_ASSERTION(runtime, "This should never be null!");
|
|
|
|
runtime->UnregisterWorker(aCx, mFinishedWorker);
|
|
|
|
mFinishedWorker->Release();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class TopLevelWorkerFinishedRunnable : public nsRunnable
|
|
{
|
|
WorkerPrivate* mFinishedWorker;
|
|
nsCOMPtr<nsIThread> mThread;
|
|
|
|
public:
|
|
TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker,
|
|
nsIThread* aFinishedThread)
|
|
: mFinishedWorker(aFinishedWorker), mThread(aFinishedThread)
|
|
{
|
|
aFinishedWorker->AssertIsOnWorkerThread();
|
|
}
|
|
|
|
NS_IMETHOD
|
|
Run()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
RuntimeService::AutoSafeJSContext cx;
|
|
|
|
mFinishedWorker->Finish(cx);
|
|
|
|
RuntimeService* runtime = RuntimeService::GetService();
|
|
NS_ASSERTION(runtime, "This should never be null!");
|
|
|
|
runtime->UnregisterWorker(cx, mFinishedWorker);
|
|
|
|
nsTArray<nsCOMPtr<nsISupports> > doomed;
|
|
mFinishedWorker->ForgetMainThreadObjects(doomed);
|
|
|
|
nsRefPtr<MainThreadReleaseRunnable> runnable =
|
|
new MainThreadReleaseRunnable(doomed);
|
|
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
|
NS_WARNING("Failed to dispatch, going to leak!");
|
|
}
|
|
|
|
if (mThread) {
|
|
runtime->NoteIdleThread(mThread);
|
|
}
|
|
|
|
mFinishedWorker->Release();
|
|
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
class ModifyBusyCountRunnable : public WorkerControlRunnable
|
|
{
|
|
bool mIncrease;
|
|
|
|
public:
|
|
ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
|
|
: WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount),
|
|
mIncrease(aIncrease)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
return aWorkerPrivate->ModifyBusyCount(aCx, mIncrease);
|
|
}
|
|
|
|
void
|
|
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
|
|
{
|
|
if (mIncrease) {
|
|
WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
|
|
return;
|
|
}
|
|
// Don't do anything here as it's possible that aWorkerPrivate has been
|
|
// deleted.
|
|
}
|
|
};
|
|
|
|
class CompileScriptRunnable : public WorkerRunnable
|
|
{
|
|
public:
|
|
CompileScriptRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerRunnable(aWorkerPrivate, WorkerThread, ModifyBusyCount,
|
|
SkipWhenClearing)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
JSObject* global = CreateDedicatedWorkerGlobalScope(aCx);
|
|
if (!global) {
|
|
NS_WARNING("Failed to make global!");
|
|
return false;
|
|
}
|
|
|
|
JSAutoEnterCompartment ac;
|
|
if (!ac.enter(aCx, global)) {
|
|
NS_WARNING("Failed to enter compartment!");
|
|
return false;
|
|
}
|
|
|
|
JS_SetGlobalObject(aCx, global);
|
|
|
|
return scriptloader::LoadWorkerScript(aCx);
|
|
}
|
|
};
|
|
|
|
class CloseEventRunnable : public WorkerRunnable
|
|
{
|
|
public:
|
|
CloseEventRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
|
|
SkipWhenClearing)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
JSObject* target = JS_GetGlobalObject(aCx);
|
|
NS_ASSERTION(target, "This must never be null!");
|
|
|
|
aWorkerPrivate->CloseHandlerStarted();
|
|
|
|
JSString* type = JS_InternString(aCx, "close");
|
|
if (!type) {
|
|
return false;
|
|
}
|
|
|
|
JSObject* event = CreateGenericEvent(aCx, type, false, false, false);
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
|
|
bool ignored;
|
|
return DispatchEventToTarget(aCx, target, event, &ignored);
|
|
}
|
|
|
|
void
|
|
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
|
|
{
|
|
// Report errors.
|
|
WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
|
|
|
|
// Match the busy count increase from NotifyRunnable.
|
|
if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) {
|
|
JS_ReportPendingException(aCx);
|
|
}
|
|
|
|
aWorkerPrivate->CloseHandlerFinished();
|
|
}
|
|
};
|
|
|
|
class MessageEventRunnable : public WorkerRunnable
|
|
{
|
|
uint64_t* mData;
|
|
size_t mDataByteCount;
|
|
nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
|
|
|
|
public:
|
|
MessageEventRunnable(WorkerPrivate* aWorkerPrivate, Target aTarget,
|
|
JSAutoStructuredCloneBuffer& aData,
|
|
nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects)
|
|
: WorkerRunnable(aWorkerPrivate, aTarget, aTarget == WorkerThread ?
|
|
ModifyBusyCount :
|
|
UnchangedBusyCount,
|
|
SkipWhenClearing)
|
|
{
|
|
aData.steal(&mData, &mDataByteCount);
|
|
|
|
if (!mClonedObjects.SwapElements(aClonedObjects)) {
|
|
NS_ERROR("This should never fail!");
|
|
}
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
JSAutoStructuredCloneBuffer buffer;
|
|
buffer.adopt(mData, mDataByteCount);
|
|
|
|
mData = nsnull;
|
|
mDataByteCount = 0;
|
|
|
|
bool mainRuntime;
|
|
JSObject* target;
|
|
if (mTarget == ParentThread) {
|
|
// Don't fire this event if the JS object has been disconnected from the
|
|
// private object.
|
|
if (!aWorkerPrivate->IsAcceptingEvents()) {
|
|
return true;
|
|
}
|
|
|
|
mainRuntime = !aWorkerPrivate->GetParent();
|
|
|
|
target = aWorkerPrivate->GetJSObject();
|
|
NS_ASSERTION(target, "Must have a target!");
|
|
|
|
if (aWorkerPrivate->IsSuspended()) {
|
|
aWorkerPrivate->QueueRunnable(this);
|
|
buffer.steal(&mData, &mDataByteCount);
|
|
return true;
|
|
}
|
|
|
|
aWorkerPrivate->AssertInnerWindowIsCorrect();
|
|
}
|
|
else {
|
|
NS_ASSERTION(aWorkerPrivate == GetWorkerPrivateFromContext(aCx),
|
|
"Badness!");
|
|
mainRuntime = false;
|
|
target = JS_GetGlobalObject(aCx);
|
|
}
|
|
|
|
NS_ASSERTION(target, "This should never be null!");
|
|
|
|
JSObject* event =
|
|
CreateMessageEvent(aCx, buffer, mClonedObjects, mainRuntime);
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
|
|
bool dummy;
|
|
return DispatchEventToTarget(aCx, target, event, &dummy);
|
|
}
|
|
};
|
|
|
|
class NotifyRunnable : public WorkerControlRunnable
|
|
{
|
|
bool mFromJSObjectFinalizer;
|
|
Status mStatus;
|
|
|
|
public:
|
|
NotifyRunnable(WorkerPrivate* aWorkerPrivate, bool aFromJSObjectFinalizer,
|
|
Status aStatus)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mFromJSObjectFinalizer(aFromJSObjectFinalizer), mStatus(aStatus)
|
|
{
|
|
NS_ASSERTION(aStatus == Terminating || aStatus == Canceling ||
|
|
aStatus == Killing, "Bad status!");
|
|
}
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// Modify here, but not in PostRun! This busy count addition will be matched
|
|
// by the CloseEventRunnable. If we're running from a finalizer there is no
|
|
// need to modify the count because future changes to the busy count will
|
|
// have no effect.
|
|
return mFromJSObjectFinalizer ?
|
|
true :
|
|
aWorkerPrivate->ModifyBusyCount(aCx, true);
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
return aWorkerPrivate->NotifyInternal(aCx, mStatus);
|
|
}
|
|
};
|
|
|
|
class CloseRunnable : public WorkerControlRunnable
|
|
{
|
|
public:
|
|
CloseRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerControlRunnable(aWorkerPrivate, ParentThread, UnchangedBusyCount)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// This busy count will be matched by the CloseEventRunnable.
|
|
return aWorkerPrivate->ModifyBusyCount(aCx, true) &&
|
|
aWorkerPrivate->Close(aCx);
|
|
}
|
|
};
|
|
|
|
class SuspendRunnable : public WorkerControlRunnable
|
|
{
|
|
public:
|
|
SuspendRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
return aWorkerPrivate->SuspendInternal(aCx);
|
|
}
|
|
};
|
|
|
|
class ResumeRunnable : public WorkerControlRunnable
|
|
{
|
|
public:
|
|
ResumeRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
return aWorkerPrivate->ResumeInternal(aCx);
|
|
}
|
|
};
|
|
|
|
class ReportErrorRunnable : public WorkerRunnable
|
|
{
|
|
nsString mMessage;
|
|
nsString mFilename;
|
|
nsString mLine;
|
|
PRUint32 mLineNumber;
|
|
PRUint32 mColumnNumber;
|
|
PRUint32 mFlags;
|
|
PRUint32 mErrorNumber;
|
|
|
|
public:
|
|
ReportErrorRunnable(WorkerPrivate* aWorkerPrivate, const nsString& aMessage,
|
|
const nsString& aFilename, const nsString& aLine,
|
|
PRUint32 aLineNumber, PRUint32 aColumnNumber,
|
|
PRUint32 aFlags, PRUint32 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)
|
|
{
|
|
JSObject* target = aWorkerPrivate->IsAcceptingEvents() ?
|
|
aWorkerPrivate->GetJSObject() :
|
|
nsnull;
|
|
if (target) {
|
|
aWorkerPrivate->AssertInnerWindowIsCorrect();
|
|
}
|
|
|
|
PRUint64 innerWindowId;
|
|
|
|
WorkerPrivate* parent = aWorkerPrivate->GetParent();
|
|
if (parent) {
|
|
innerWindowId = 0;
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
|
|
if (aWorkerPrivate->IsSuspended()) {
|
|
aWorkerPrivate->QueueRunnable(this);
|
|
return true;
|
|
}
|
|
|
|
innerWindowId = aWorkerPrivate->GetInnerWindowId();
|
|
}
|
|
|
|
return ReportErrorRunnable::ReportError(aCx, parent, true, target, mMessage,
|
|
mFilename, mLine, mLineNumber,
|
|
mColumnNumber, mFlags,
|
|
mErrorNumber, innerWindowId);
|
|
}
|
|
|
|
static bool
|
|
ReportError(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aFireAtScope, JSObject* aTarget, const nsString& aMessage,
|
|
const nsString& aFilename, const nsString& aLine,
|
|
PRUint32 aLineNumber, PRUint32 aColumnNumber, PRUint32 aFlags,
|
|
PRUint32 aErrorNumber, PRUint64 aInnerWindowId)
|
|
{
|
|
if (aWorkerPrivate) {
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
JSString* message = JS_NewUCStringCopyN(aCx, aMessage.get(),
|
|
aMessage.Length());
|
|
if (!message) {
|
|
return false;
|
|
}
|
|
|
|
JSString* filename = JS_NewUCStringCopyN(aCx, aFilename.get(),
|
|
aFilename.Length());
|
|
if (!filename) {
|
|
return false;
|
|
}
|
|
|
|
// First fire an ErrorEvent at the worker.
|
|
if (aTarget) {
|
|
JSObject* event =
|
|
CreateErrorEvent(aCx, message, filename, aLineNumber, !aWorkerPrivate);
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
|
|
bool preventDefaultCalled;
|
|
if (!DispatchEventToTarget(aCx, aTarget, event, &preventDefaultCalled)) {
|
|
return false;
|
|
}
|
|
|
|
if (preventDefaultCalled) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Now fire an event at the global object, but don't do that if the error
|
|
// code is too much recursion and this is the same script threw the error.
|
|
if (aFireAtScope && (aTarget || aErrorNumber != JSMSG_OVER_RECURSED)) {
|
|
aTarget = JS_GetGlobalForScopeChain(aCx);
|
|
NS_ASSERTION(aTarget, "This should never be null!");
|
|
|
|
bool preventDefaultCalled;
|
|
nsIScriptGlobalObject* sgo;
|
|
|
|
if (aWorkerPrivate ||
|
|
!(sgo = nsJSUtils::GetStaticScriptGlobal(aCx, aTarget))) {
|
|
// Fire a normal ErrorEvent if we're running on a worker thread.
|
|
JSObject* event =
|
|
CreateErrorEvent(aCx, message, filename, aLineNumber, false);
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
|
|
if (!DispatchEventToTarget(aCx, aTarget, event,
|
|
&preventDefaultCalled)) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// Icky, we have to fire an nsScriptErrorEvent...
|
|
nsScriptErrorEvent event(true, NS_LOAD_ERROR);
|
|
event.lineNr = aLineNumber;
|
|
event.errorMsg = aMessage.get();
|
|
event.fileName = aFilename.get();
|
|
|
|
nsEventStatus status = nsEventStatus_eIgnore;
|
|
if (NS_FAILED(sgo->HandleScriptError(&event, &status))) {
|
|
NS_WARNING("Failed to dispatch main thread error event!");
|
|
status = nsEventStatus_eIgnore;
|
|
}
|
|
|
|
preventDefaultCalled = status == nsEventStatus_eConsumeNoDefault;
|
|
}
|
|
|
|
if (preventDefaultCalled) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Now fire a runnable to do the same on the parent's thread if we can.
|
|
if (aWorkerPrivate) {
|
|
nsRefPtr<ReportErrorRunnable> runnable =
|
|
new ReportErrorRunnable(aWorkerPrivate, aMessage, aFilename, aLine,
|
|
aLineNumber, aColumnNumber, aFlags,
|
|
aErrorNumber);
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
// Otherwise log an error to the error console.
|
|
nsCOMPtr<nsIScriptError> scriptError =
|
|
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
|
|
NS_WARN_IF_FALSE(scriptError, "Failed to create script error!");
|
|
|
|
if (scriptError) {
|
|
if (NS_FAILED(scriptError->InitWithWindowID(aMessage.get(),
|
|
aFilename.get(),
|
|
aLine.get(), aLineNumber,
|
|
aColumnNumber, aFlags,
|
|
"Web Worker",
|
|
aInnerWindowId))) {
|
|
NS_WARNING("Failed to init script error!");
|
|
scriptError = nsnull;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIConsoleService> consoleService =
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
|
|
NS_WARN_IF_FALSE(consoleService, "Failed to get console service!");
|
|
|
|
bool logged = false;
|
|
|
|
if (consoleService) {
|
|
if (scriptError) {
|
|
if (NS_SUCCEEDED(consoleService->LogMessage(scriptError))) {
|
|
logged = true;
|
|
}
|
|
else {
|
|
NS_WARNING("Failed to log script error!");
|
|
}
|
|
}
|
|
else if (NS_SUCCEEDED(consoleService->LogStringMessage(aMessage.get()))) {
|
|
logged = true;
|
|
}
|
|
else {
|
|
NS_WARNING("Failed to log script error!");
|
|
}
|
|
}
|
|
|
|
if (!logged) {
|
|
NS_ConvertUTF16toUTF8 msg(aMessage);
|
|
#ifdef ANDROID
|
|
__android_log_print(ANDROID_LOG_INFO, "Gecko", msg.get());
|
|
#endif
|
|
fputs(msg.get(), stderr);
|
|
fflush(stderr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class TimerRunnable : public WorkerRunnable
|
|
{
|
|
public:
|
|
TimerRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
|
|
SkipWhenClearing)
|
|
{ }
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// Silence bad assertions.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
// Silence bad assertions.
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
return aWorkerPrivate->RunExpiredTimeouts(aCx);
|
|
}
|
|
};
|
|
|
|
void
|
|
DummyCallback(nsITimer* aTimer, void* aClosure)
|
|
{
|
|
// Nothing!
|
|
}
|
|
|
|
class WorkerRunnableEventTarget : public nsIEventTarget
|
|
{
|
|
protected:
|
|
nsRefPtr<WorkerRunnable> mWorkerRunnable;
|
|
|
|
public:
|
|
WorkerRunnableEventTarget(WorkerRunnable* aWorkerRunnable)
|
|
: mWorkerRunnable(aWorkerRunnable)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD
|
|
Dispatch(nsIRunnable* aRunnable, PRUint32 aFlags)
|
|
{
|
|
NS_ASSERTION(aFlags == nsIEventTarget::DISPATCH_NORMAL, "Don't call me!");
|
|
|
|
nsRefPtr<WorkerRunnableEventTarget> 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
|
|
// dispatch running the event can race against resetting the timer.
|
|
aRunnable->Run();
|
|
|
|
// This can fail if we're racing to terminate or cancel, should be handled
|
|
// by the terminate or cancel code.
|
|
mWorkerRunnable->Dispatch(nsnull);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
IsOnCurrentThread(bool* aIsOnCurrentThread)
|
|
{
|
|
*aIsOnCurrentThread = false;
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(WorkerRunnableEventTarget, nsIEventTarget)
|
|
|
|
class KillCloseEventRunnable : public WorkerRunnable
|
|
{
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
|
|
class KillScriptRunnable : public WorkerControlRunnable
|
|
{
|
|
public:
|
|
KillScriptRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount)
|
|
{ }
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// Silence bad assertions.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
// Silence bad assertions.
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
// Kill running script.
|
|
return false;
|
|
}
|
|
};
|
|
|
|
public:
|
|
KillCloseEventRunnable(WorkerPrivate* aWorkerPrivate)
|
|
: WorkerRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount,
|
|
SkipWhenClearing)
|
|
{ }
|
|
|
|
~KillCloseEventRunnable()
|
|
{
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
}
|
|
}
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
NS_NOTREACHED("Not meant to be dispatched!");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SetTimeout(JSContext* aCx, PRUint32 aDelayMS)
|
|
{
|
|
nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
if (!timer) {
|
|
JS_ReportError(aCx, "Failed to create timer!");
|
|
return false;
|
|
}
|
|
|
|
nsRefPtr<KillScriptRunnable> runnable =
|
|
new KillScriptRunnable(mWorkerPrivate);
|
|
|
|
nsRefPtr<WorkerRunnableEventTarget> target =
|
|
new WorkerRunnableEventTarget(runnable);
|
|
|
|
if (NS_FAILED(timer->SetTarget(target))) {
|
|
JS_ReportError(aCx, "Failed to set timer's target!");
|
|
return false;
|
|
}
|
|
|
|
if (NS_FAILED(timer->InitWithFuncCallback(DummyCallback, nsnull, aDelayMS,
|
|
nsITimer::TYPE_ONE_SHOT))) {
|
|
JS_ReportError(aCx, "Failed to start timer!");
|
|
return false;
|
|
}
|
|
|
|
mTimer.swap(timer);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nsnull;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class UpdateJSContextOptionsRunnable : public WorkerControlRunnable
|
|
{
|
|
PRUint32 mOptions;
|
|
|
|
public:
|
|
UpdateJSContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
|
|
PRUint32 aOptions)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mOptions(aOptions)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->UpdateJSContextOptionsInternal(aCx, mOptions);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class UpdateJSRuntimeHeapSizeRunnable : public WorkerControlRunnable
|
|
{
|
|
PRUint32 mJSRuntimeHeapSize;
|
|
|
|
public:
|
|
UpdateJSRuntimeHeapSizeRunnable(WorkerPrivate* aWorkerPrivate,
|
|
PRUint32 aJSRuntimeHeapSize)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mJSRuntimeHeapSize(aJSRuntimeHeapSize)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->UpdateJSRuntimeHeapSizeInternal(aCx, mJSRuntimeHeapSize);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
#ifdef JS_GC_ZEAL
|
|
class UpdateGCZealRunnable : public WorkerControlRunnable
|
|
{
|
|
PRUint8 mGCZeal;
|
|
|
|
public:
|
|
UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate,
|
|
PRUint8 aGCZeal)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mGCZeal(aGCZeal)
|
|
{ }
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal);
|
|
return true;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
class GarbageCollectRunnable : public WorkerControlRunnable
|
|
{
|
|
protected:
|
|
bool mShrinking;
|
|
bool mCollectChildren;
|
|
|
|
public:
|
|
GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
|
|
bool aCollectChildren)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mShrinking(aShrinking), 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)
|
|
{
|
|
aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class CollectRuntimeStatsRunnable : public WorkerControlRunnable
|
|
{
|
|
typedef mozilla::Mutex Mutex;
|
|
typedef mozilla::CondVar CondVar;
|
|
|
|
Mutex mMutex;
|
|
CondVar mCondVar;
|
|
volatile bool mDone;
|
|
bool mIsQuick;
|
|
void* mData;
|
|
bool* mSucceeded;
|
|
|
|
public:
|
|
CollectRuntimeStatsRunnable(WorkerPrivate* aWorkerPrivate, bool aIsQuick,
|
|
void* aData, bool* aSucceeded)
|
|
: WorkerControlRunnable(aWorkerPrivate, WorkerThread, UnchangedBusyCount),
|
|
mMutex("CollectRuntimeStatsRunnable::mMutex"),
|
|
mCondVar(mMutex, "CollectRuntimeStatsRunnable::mCondVar"), mDone(false),
|
|
mIsQuick(aIsQuick), mData(aData), mSucceeded(aSucceeded)
|
|
{ }
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
AssertIsOnMainThread();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
bool
|
|
DispatchInternal()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!WorkerControlRunnable::DispatchInternal()) {
|
|
NS_WARNING("Failed to dispatch runnable!");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
while (!mDone) {
|
|
mCondVar.Wait();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
JSRuntime *rt = JS_GetRuntime(aCx);
|
|
if (mIsQuick) {
|
|
*static_cast<int64_t*>(mData) = JS::GetExplicitNonHeapForRuntime(rt, JsWorkerMallocSizeOf);
|
|
*mSucceeded = true;
|
|
} else {
|
|
*mSucceeded = JS::CollectRuntimeStats(rt, static_cast<JS::RuntimeStats*>(mData));
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
mDone = true;
|
|
mCondVar.Notify();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} /* anonymous namespace */
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
mozilla::dom::workers::AssertIsOnMainThread()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
}
|
|
|
|
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_THREADSAFE_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(nsnull, mWorkerPrivate);
|
|
if (ok) {
|
|
ok = DispatchInternal();
|
|
}
|
|
PostDispatch(nsnull, mWorkerPrivate, ok);
|
|
return ok;
|
|
}
|
|
|
|
JSAutoRequest ar(aCx);
|
|
|
|
JSObject* global = JS_GetGlobalObject(aCx);
|
|
|
|
JSAutoEnterCompartment ac;
|
|
if (global && !ac.enter(aCx, global)) {
|
|
return false;
|
|
}
|
|
|
|
ok = PreDispatch(aCx, mWorkerPrivate);
|
|
|
|
if (ok && !DispatchInternal()) {
|
|
ok = false;
|
|
}
|
|
|
|
PostDispatch(aCx, mWorkerPrivate, ok);
|
|
|
|
return ok;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
WorkerRunnable::DispatchToMainThread(nsIRunnable* aRunnable)
|
|
{
|
|
nsCOMPtr<nsIThread> 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;
|
|
JSObject* targetCompartmentObject;
|
|
nsIThreadJSContextStack* contextStack = nsnull;
|
|
|
|
if (mTarget == WorkerThread) {
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
cx = mWorkerPrivate->GetJSContext();
|
|
targetCompartmentObject = JS_GetGlobalObject(cx);
|
|
} else {
|
|
mWorkerPrivate->AssertIsOnParentThread();
|
|
cx = mWorkerPrivate->ParentJSContext();
|
|
targetCompartmentObject = mWorkerPrivate->GetJSObject();
|
|
|
|
if (!mWorkerPrivate->GetParent()) {
|
|
AssertIsOnMainThread();
|
|
|
|
contextStack = nsContentUtils::ThreadJSContextStack();
|
|
NS_ASSERTION(contextStack, "This should never be null!");
|
|
|
|
if (NS_FAILED(contextStack->Push(cx))) {
|
|
NS_WARNING("Failed to push context!");
|
|
contextStack = nsnull;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(cx, "Must have a context!");
|
|
|
|
JSAutoRequest ar(cx);
|
|
|
|
JSAutoEnterCompartment ac;
|
|
if (targetCompartmentObject && !ac.enter(cx, targetCompartmentObject)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = WorkerRun(cx, mWorkerPrivate);
|
|
|
|
PostRun(cx, mWorkerPrivate, result);
|
|
|
|
if (contextStack) {
|
|
JSContext* otherCx;
|
|
if (NS_FAILED(contextStack->Pop(&otherCx))) {
|
|
NS_WARNING("Failed to pop context!");
|
|
}
|
|
else if (otherCx != cx) {
|
|
NS_WARNING("Popped a different context!");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
struct WorkerPrivate::TimeoutInfo
|
|
{
|
|
TimeoutInfo()
|
|
: mTimeoutVal(JSVAL_VOID), mLineNumber(0), mId(0), mIsInterval(false),
|
|
mCanceled(false)
|
|
{
|
|
MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
|
|
}
|
|
|
|
~TimeoutInfo()
|
|
{
|
|
MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate::TimeoutInfo);
|
|
}
|
|
|
|
bool operator==(const TimeoutInfo& aOther)
|
|
{
|
|
return mTargetTime == aOther.mTargetTime;
|
|
}
|
|
|
|
bool operator<(const TimeoutInfo& aOther)
|
|
{
|
|
return mTargetTime < aOther.mTargetTime;
|
|
}
|
|
|
|
jsval mTimeoutVal;
|
|
nsTArray<jsval> mExtraArgVals;
|
|
mozilla::TimeStamp mTargetTime;
|
|
mozilla::TimeDuration mInterval;
|
|
nsCString mFilename;
|
|
PRUint32 mLineNumber;
|
|
PRUint32 mId;
|
|
bool mIsInterval;
|
|
bool mCanceled;
|
|
};
|
|
|
|
template <class Derived>
|
|
WorkerPrivateParent<Derived>::WorkerPrivateParent(
|
|
JSContext* aCx, JSObject* aObject,
|
|
WorkerPrivate* aParent,
|
|
JSContext* aParentJSContext,
|
|
const nsAString& aScriptURL,
|
|
bool aIsChromeWorker,
|
|
const nsACString& aDomain,
|
|
nsCOMPtr<nsPIDOMWindow>& aWindow,
|
|
nsCOMPtr<nsIScriptContext>& aScriptContext,
|
|
nsCOMPtr<nsIURI>& aBaseURI,
|
|
nsCOMPtr<nsIPrincipal>& aPrincipal,
|
|
nsCOMPtr<nsIDocument>& aDocument)
|
|
: EventTarget(aParent ? aCx : NULL), mMutex("WorkerPrivateParent Mutex"),
|
|
mCondVar(mMutex, "WorkerPrivateParent CondVar"),
|
|
mJSObject(aObject), mParent(aParent), mParentJSContext(aParentJSContext),
|
|
mScriptURL(aScriptURL), mDomain(aDomain), mBusyCount(0),
|
|
mParentStatus(Pending), mJSContextOptions(0), mJSRuntimeHeapSize(0),
|
|
mGCZeal(0), mJSObjectRooted(false), mParentSuspended(false),
|
|
mIsChromeWorker(aIsChromeWorker), mPrincipalIsSystem(false),
|
|
mMainThreadObjectsForgotten(false)
|
|
{
|
|
MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivateParent);
|
|
|
|
if (aWindow) {
|
|
NS_ASSERTION(aWindow->IsInnerWindow(), "Should have inner window here!");
|
|
}
|
|
|
|
mWindow.swap(aWindow);
|
|
mScriptContext.swap(aScriptContext);
|
|
mBaseURI.swap(aBaseURI);
|
|
mPrincipal.swap(aPrincipal);
|
|
mDocument.swap(aDocument);
|
|
|
|
if (aParent) {
|
|
aParent->AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(JS_GetOptions(aCx) == aParent->GetJSContextOptions(),
|
|
"Options mismatch!");
|
|
mJSContextOptions = aParent->GetJSContextOptions();
|
|
|
|
NS_ASSERTION(JS_GetGCParameter(JS_GetRuntime(aCx), JSGC_MAX_BYTES) ==
|
|
aParent->GetJSRuntimeHeapSize(),
|
|
"Runtime heap size mismatch!");
|
|
mJSRuntimeHeapSize = aParent->GetJSRuntimeHeapSize();
|
|
|
|
#ifdef JS_GC_ZEAL
|
|
mGCZeal = aParent->GetGCZeal();
|
|
#endif
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
|
|
mJSContextOptions = RuntimeService::GetDefaultJSContextOptions();
|
|
mJSRuntimeHeapSize = RuntimeService::GetDefaultJSRuntimeHeapSize();
|
|
#ifdef JS_GC_ZEAL
|
|
mGCZeal = RuntimeService::GetDefaultGCZeal();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
WorkerPrivateParent<Derived>::~WorkerPrivateParent()
|
|
{
|
|
MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivateParent);
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::Start()
|
|
{
|
|
// May be called on any thread!
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
NS_ASSERTION(mParentStatus != Running, "How can this be?!");
|
|
|
|
if (mParentStatus == Pending) {
|
|
mParentStatus = Running;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// aCx is null when called from the finalizer
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::NotifyPrivate(JSContext* aCx, Status aStatus)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
bool pending;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mParentStatus >= aStatus) {
|
|
return true;
|
|
}
|
|
|
|
pending = mParentStatus == Pending;
|
|
mParentStatus = aStatus;
|
|
}
|
|
|
|
if (pending) {
|
|
WorkerPrivate* self = ParentAsWorkerPrivate();
|
|
#ifdef DEBUG
|
|
{
|
|
// Silence useless assertions in debug builds.
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
NS_ASSERTION(currentThread, "This should never be null!");
|
|
|
|
self->SetThread(currentThread);
|
|
}
|
|
#endif
|
|
// Worker never got a chance to run, go ahead and delete it.
|
|
self->ScheduleDeletion(true);
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(aStatus != Terminating || mQueuedRunnables.IsEmpty(),
|
|
"Shouldn't have anything queued!");
|
|
|
|
// Anything queued will be discarded.
|
|
mQueuedRunnables.Clear();
|
|
|
|
nsRefPtr<NotifyRunnable> runnable =
|
|
new NotifyRunnable(ParentAsWorkerPrivate(), !aCx, aStatus);
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::Suspend(JSContext* aCx)
|
|
{
|
|
AssertIsOnParentThread();
|
|
NS_ASSERTION(!mParentSuspended, "Suspended more than once!");
|
|
|
|
mParentSuspended = true;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mParentStatus >= Terminating) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
nsRefPtr<SuspendRunnable> runnable =
|
|
new SuspendRunnable(ParentAsWorkerPrivate());
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::Resume(JSContext* aCx)
|
|
{
|
|
AssertIsOnParentThread();
|
|
NS_ASSERTION(mParentSuspended, "Not yet suspended!");
|
|
|
|
mParentSuspended = false;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mParentStatus >= Terminating) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Dispatch queued runnables before waking up the worker, otherwise the worker
|
|
// could post new messages before we run those that have been queued.
|
|
if (!mQueuedRunnables.IsEmpty()) {
|
|
AssertIsOnMainThread();
|
|
|
|
nsTArray<nsRefPtr<WorkerRunnable> > runnables;
|
|
mQueuedRunnables.SwapElements(runnables);
|
|
|
|
for (PRUint32 index = 0; index < runnables.Length(); index++) {
|
|
nsRefPtr<WorkerRunnable>& runnable = runnables[index];
|
|
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
|
NS_WARNING("Failed to dispatch queued runnable!");
|
|
}
|
|
}
|
|
}
|
|
|
|
nsRefPtr<ResumeRunnable> runnable =
|
|
new ResumeRunnable(ParentAsWorkerPrivate());
|
|
if (!runnable->Dispatch(aCx)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::_Trace(JSTracer* aTrc)
|
|
{
|
|
// This should only happen on the parent thread but we can't assert that
|
|
// because it can also happen on the cycle collector thread when this is a
|
|
// top-level worker.
|
|
EventTarget::_Trace(aTrc);
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::_Finalize(JSFreeOp* aFop)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
MOZ_ASSERT(mJSObject);
|
|
MOZ_ASSERT(!mJSObjectRooted);
|
|
|
|
// Clear the JS object.
|
|
mJSObject = nsnull;
|
|
|
|
if (!TerminatePrivate(nsnull)) {
|
|
NS_WARNING("Failed to terminate!");
|
|
}
|
|
|
|
// Before calling through to the base class we need to grab another reference
|
|
// if we're on the main thread. Otherwise the base class' _Finalize method
|
|
// will call Release, and some of our members cannot be released during
|
|
// finalization. Of course, if those members are already gone then we can skip
|
|
// this mess...
|
|
WorkerPrivateParent<Derived>* extraSelfRef = NULL;
|
|
|
|
if (!mParent && !mMainThreadObjectsForgotten) {
|
|
AssertIsOnMainThread();
|
|
NS_ADDREF(extraSelfRef = this);
|
|
}
|
|
|
|
EventTarget::_Finalize(aFop);
|
|
|
|
if (extraSelfRef) {
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
NS_NewNonOwningRunnableMethod(extraSelfRef,
|
|
&WorkerPrivateParent<Derived>::Release);
|
|
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
|
NS_WARNING("Failed to proxy release, this will leak!");
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::Close(JSContext* aCx)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mParentStatus < Closing) {
|
|
mParentStatus = Closing;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::ModifyBusyCount(JSContext* aCx, bool aIncrease)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
NS_ASSERTION(aIncrease || mBusyCount, "Mismatched busy count mods!");
|
|
|
|
if (aIncrease) {
|
|
if (mBusyCount++ == 0) {
|
|
if (!RootJSObject(aCx, true)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (--mBusyCount == 0) {
|
|
if (!RootJSObject(aCx, false)) {
|
|
return false;
|
|
}
|
|
|
|
bool shouldCancel;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
shouldCancel = mParentStatus == Terminating;
|
|
}
|
|
|
|
if (shouldCancel && !Cancel(aCx)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::RootJSObject(JSContext* aCx, bool aRoot)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
if (aRoot != mJSObjectRooted) {
|
|
if (aRoot) {
|
|
if (!JS_AddNamedObjectRoot(aCx, &mJSObject, "Worker root")) {
|
|
NS_WARNING("JS_AddNamedObjectRoot failed!");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
JS_RemoveObjectRoot(aCx, &mJSObject);
|
|
}
|
|
|
|
mJSObjectRooted = aRoot;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
|
|
nsTArray<nsCOMPtr<nsISupports> >& aDoomed)
|
|
{
|
|
AssertIsOnParentThread();
|
|
MOZ_ASSERT(!mMainThreadObjectsForgotten);
|
|
|
|
aDoomed.SetCapacity(6);
|
|
|
|
SwapToISupportsArray(mWindow, aDoomed);
|
|
SwapToISupportsArray(mScriptContext, aDoomed);
|
|
SwapToISupportsArray(mBaseURI, aDoomed);
|
|
SwapToISupportsArray(mScriptURI, aDoomed);
|
|
SwapToISupportsArray(mPrincipal, aDoomed);
|
|
SwapToISupportsArray(mDocument, aDoomed);
|
|
|
|
mMainThreadObjectsForgotten = true;
|
|
}
|
|
|
|
template <class Derived>
|
|
bool
|
|
WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
if (mParentStatus != Running) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
JSStructuredCloneCallbacks* callbacks;
|
|
if (GetParent()) {
|
|
if (IsChromeWorker()) {
|
|
callbacks = &gChromeWorkerStructuredCloneCallbacks;
|
|
}
|
|
else {
|
|
callbacks = &gWorkerStructuredCloneCallbacks;
|
|
}
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
|
|
if (IsChromeWorker()) {
|
|
callbacks = &gMainThreadChromeWorkerStructuredCloneCallbacks;
|
|
}
|
|
else {
|
|
callbacks = &gMainThreadWorkerStructuredCloneCallbacks;
|
|
}
|
|
}
|
|
|
|
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
|
|
|
|
JSAutoStructuredCloneBuffer buffer;
|
|
if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
|
|
return false;
|
|
}
|
|
|
|
nsRefPtr<MessageEventRunnable> runnable =
|
|
new MessageEventRunnable(ParentAsWorkerPrivate(),
|
|
WorkerRunnable::WorkerThread, buffer,
|
|
clonedObjects);
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
template <class Derived>
|
|
PRUint64
|
|
WorkerPrivateParent<Derived>::GetInnerWindowId()
|
|
{
|
|
AssertIsOnMainThread();
|
|
return mDocument ? mDocument->InnerWindowID() : 0;
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::UpdateJSContextOptions(JSContext* aCx,
|
|
PRUint32 aOptions)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
mJSContextOptions = aOptions;
|
|
|
|
nsRefPtr<UpdateJSContextOptionsRunnable> runnable =
|
|
new UpdateJSContextOptionsRunnable(ParentAsWorkerPrivate(), aOptions);
|
|
if (!runnable->Dispatch(aCx)) {
|
|
NS_WARNING("Failed to update worker context options!");
|
|
JS_ClearPendingException(aCx);
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::UpdateJSRuntimeHeapSize(JSContext* aCx,
|
|
PRUint32 aMaxBytes)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
mJSRuntimeHeapSize = aMaxBytes;
|
|
|
|
nsRefPtr<UpdateJSRuntimeHeapSizeRunnable> runnable =
|
|
new UpdateJSRuntimeHeapSizeRunnable(ParentAsWorkerPrivate(), aMaxBytes);
|
|
if (!runnable->Dispatch(aCx)) {
|
|
NS_WARNING("Failed to update worker heap size!");
|
|
JS_ClearPendingException(aCx);
|
|
}
|
|
}
|
|
|
|
#ifdef JS_GC_ZEAL
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::UpdateGCZeal(JSContext* aCx, PRUint8 aGCZeal)
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
mGCZeal = aGCZeal;
|
|
|
|
nsRefPtr<UpdateGCZealRunnable> runnable =
|
|
new UpdateGCZealRunnable(ParentAsWorkerPrivate(), aGCZeal);
|
|
if (!runnable->Dispatch(aCx)) {
|
|
NS_WARNING("Failed to update worker gczeal!");
|
|
JS_ClearPendingException(aCx);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::GarbageCollect(JSContext* aCx, bool aShrinking)
|
|
{
|
|
nsRefPtr<GarbageCollectRunnable> runnable =
|
|
new GarbageCollectRunnable(ParentAsWorkerPrivate(), aShrinking, true);
|
|
if (!runnable->Dispatch(aCx)) {
|
|
NS_WARNING("Failed to update worker heap size!");
|
|
JS_ClearPendingException(aCx);
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
mBaseURI = aBaseURI;
|
|
|
|
if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
|
|
mLocationInfo.mHref.Truncate();
|
|
}
|
|
|
|
if (NS_FAILED(aBaseURI->GetHost(mLocationInfo.mHostname))) {
|
|
mLocationInfo.mHostname.Truncate();
|
|
}
|
|
|
|
if (NS_FAILED(aBaseURI->GetPath(mLocationInfo.mPathname))) {
|
|
mLocationInfo.mPathname.Truncate();
|
|
}
|
|
|
|
nsCString temp;
|
|
|
|
nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
|
|
if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
|
|
mLocationInfo.mSearch.AssignLiteral("?");
|
|
mLocationInfo.mSearch.Append(temp);
|
|
}
|
|
|
|
if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
|
|
nsCOMPtr<nsITextToSubURI> converter =
|
|
do_GetService(NS_ITEXTTOSUBURI_CONTRACTID);
|
|
if (converter) {
|
|
nsCString charset;
|
|
nsAutoString unicodeRef;
|
|
if (NS_SUCCEEDED(aBaseURI->GetOriginCharset(charset)) &&
|
|
NS_SUCCEEDED(converter->UnEscapeURIForUI(charset, temp,
|
|
unicodeRef))) {
|
|
mLocationInfo.mHash.AssignLiteral("#");
|
|
mLocationInfo.mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef));
|
|
}
|
|
}
|
|
|
|
if (mLocationInfo.mHash.IsEmpty()) {
|
|
mLocationInfo.mHash.AssignLiteral("#");
|
|
mLocationInfo.mHash.Append(temp);
|
|
}
|
|
}
|
|
|
|
if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
|
|
mLocationInfo.mProtocol.AppendLiteral(":");
|
|
}
|
|
else {
|
|
mLocationInfo.mProtocol.Truncate();
|
|
}
|
|
|
|
PRInt32 port;
|
|
if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
|
|
mLocationInfo.mPort.AppendInt(port);
|
|
|
|
nsCAutoString host(mLocationInfo.mHostname);
|
|
host.AppendLiteral(":");
|
|
host.Append(mLocationInfo.mPort);
|
|
|
|
mLocationInfo.mHost.Assign(host);
|
|
}
|
|
else {
|
|
mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::SetPrincipal(nsIPrincipal* aPrincipal)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
mPrincipal = aPrincipal;
|
|
mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
|
|
}
|
|
|
|
template <class Derived>
|
|
JSContext*
|
|
WorkerPrivateParent<Derived>::ParentJSContext() const
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
if (!mParent) {
|
|
AssertIsOnMainThread();
|
|
|
|
if (!mScriptContext) {
|
|
NS_ASSERTION(!mParentJSContext, "Shouldn't have a parent context!");
|
|
return RuntimeService::AutoSafeJSContext::GetSafeContext();
|
|
}
|
|
|
|
NS_ASSERTION(mParentJSContext == mScriptContext->GetNativeContext(),
|
|
"Native context has changed!");
|
|
}
|
|
|
|
return mParentJSContext;
|
|
}
|
|
|
|
WorkerPrivate::WorkerPrivate(JSContext* aCx, JSObject* aObject,
|
|
WorkerPrivate* aParent,
|
|
JSContext* aParentJSContext,
|
|
const nsAString& aScriptURL, bool aIsChromeWorker,
|
|
const nsACString& aDomain,
|
|
nsCOMPtr<nsPIDOMWindow>& aWindow,
|
|
nsCOMPtr<nsIScriptContext>& aParentScriptContext,
|
|
nsCOMPtr<nsIURI>& aBaseURI,
|
|
nsCOMPtr<nsIPrincipal>& aPrincipal,
|
|
nsCOMPtr<nsIDocument>& aDocument)
|
|
: WorkerPrivateParent<WorkerPrivate>(aCx, aObject, aParent, aParentJSContext,
|
|
aScriptURL, aIsChromeWorker, aDomain,
|
|
aWindow, aParentScriptContext, aBaseURI,
|
|
aPrincipal, aDocument),
|
|
mJSContext(nsnull), mErrorHandlerRecursionCount(0), mNextTimeoutId(1),
|
|
mStatus(Pending), mSuspended(false), mTimerRunning(false),
|
|
mRunningExpiredTimeouts(false), mCloseHandlerStarted(false),
|
|
mCloseHandlerFinished(false), mMemoryReporterRunning(false),
|
|
mMemoryReporterDisabled(false)
|
|
{
|
|
MOZ_COUNT_CTOR(mozilla::dom::workers::WorkerPrivate);
|
|
}
|
|
|
|
WorkerPrivate::~WorkerPrivate()
|
|
{
|
|
MOZ_COUNT_DTOR(mozilla::dom::workers::WorkerPrivate);
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<WorkerPrivate>
|
|
WorkerPrivate::Create(JSContext* aCx, JSObject* aObj, WorkerPrivate* aParent,
|
|
JSString* aScriptURL, bool aIsChromeWorker)
|
|
{
|
|
nsCString domain;
|
|
nsCOMPtr<nsIURI> baseURI;
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsCOMPtr<nsIScriptContext> scriptContext;
|
|
nsCOMPtr<nsIDocument> document;
|
|
nsCOMPtr<nsPIDOMWindow> window;
|
|
|
|
JSContext* parentContext;
|
|
|
|
if (aParent) {
|
|
aParent->AssertIsOnWorkerThread();
|
|
|
|
parentContext = aCx;
|
|
|
|
// Domain is the only thing we can touch here. The rest will be handled by
|
|
// the ScriptLoader.
|
|
domain = aParent->Domain();
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(ssm, "This should never be null!");
|
|
|
|
bool isChrome;
|
|
if (NS_FAILED(ssm->IsCapabilityEnabled("UniversalXPConnect", &isChrome))) {
|
|
NS_WARNING("IsCapabilityEnabled failed!");
|
|
isChrome = false;
|
|
}
|
|
|
|
// First check to make sure the caller has permission to make a
|
|
// ChromeWorker if they called the ChromeWorker constructor.
|
|
if (aIsChromeWorker && !isChrome) {
|
|
nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_SECURITY_ERR);
|
|
return nsnull;
|
|
}
|
|
|
|
// Chrome callers (whether ChromeWorker of Worker) always get the system
|
|
// principal here as they're allowed to load anything. The script loader may
|
|
// change the principal later depending on the script uri.
|
|
if (isChrome &&
|
|
NS_FAILED(ssm->GetSystemPrincipal(getter_AddRefs(principal)))) {
|
|
JS_ReportError(aCx, "Could not get system principal!");
|
|
return nsnull;
|
|
}
|
|
|
|
// See if we're being called from a window or from somewhere else.
|
|
nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
|
|
nsJSUtils::GetStaticScriptGlobal(aCx, JS_GetGlobalForScopeChain(aCx));
|
|
if (scriptGlobal) {
|
|
// Window!
|
|
nsCOMPtr<nsPIDOMWindow> globalWindow = do_QueryInterface(scriptGlobal);
|
|
|
|
// Only use the current inner window, and only use it if the caller can
|
|
// access it.
|
|
nsPIDOMWindow* outerWindow = globalWindow ?
|
|
globalWindow->GetOuterWindow() :
|
|
nsnull;
|
|
window = outerWindow ? outerWindow->GetCurrentInnerWindow() : nsnull;
|
|
if (!window ||
|
|
(globalWindow != window &&
|
|
!nsContentUtils::CanCallerAccess(window))) {
|
|
nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_SECURITY_ERR);
|
|
return nsnull;
|
|
}
|
|
|
|
scriptContext = scriptGlobal->GetContext();
|
|
if (!scriptContext) {
|
|
JS_ReportError(aCx, "Couldn't get script context for this worker!");
|
|
return nsnull;
|
|
}
|
|
|
|
parentContext = scriptContext->GetNativeContext();
|
|
|
|
// If we're called from a window then we can dig out the principal and URI
|
|
// from the document.
|
|
document = do_QueryInterface(window->GetExtantDocument());
|
|
if (!document) {
|
|
JS_ReportError(aCx, "No document in this window!");
|
|
return nsnull;
|
|
}
|
|
|
|
baseURI = document->GetDocBaseURI();
|
|
|
|
// Use the document's NodePrincipal as our principal if we're not being
|
|
// called from chrome.
|
|
if (!principal) {
|
|
if (!(principal = document->NodePrincipal())) {
|
|
JS_ReportError(aCx, "Could not get document principal!");
|
|
return nsnull;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> codebase;
|
|
if (NS_FAILED(principal->GetURI(getter_AddRefs(codebase)))) {
|
|
JS_ReportError(aCx, "Could not determine codebase!");
|
|
return nsnull;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(file, "file");
|
|
|
|
bool isFile;
|
|
if (NS_FAILED(codebase->SchemeIs(file.get(), &isFile))) {
|
|
JS_ReportError(aCx, "Could not determine if codebase is file!");
|
|
return nsnull;
|
|
}
|
|
|
|
if (isFile) {
|
|
// XXX Fix this, need a real domain here.
|
|
domain = file;
|
|
}
|
|
else {
|
|
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
|
|
do_GetService(THIRDPARTYUTIL_CONTRACTID);
|
|
if (!thirdPartyUtil) {
|
|
JS_ReportError(aCx, "Could not get third party helper service!");
|
|
return nsnull;
|
|
}
|
|
|
|
if (NS_FAILED(thirdPartyUtil->GetBaseDomain(codebase, domain))) {
|
|
JS_ReportError(aCx, "Could not get domain!");
|
|
return nsnull;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Not a window
|
|
NS_ASSERTION(isChrome, "Should be chrome only!");
|
|
|
|
parentContext = nsnull;
|
|
|
|
// We're being created outside of a window. Need to figure out the script
|
|
// that is creating us in order for us to use relative URIs later on.
|
|
JSScript *script;
|
|
if (JS_DescribeScriptedCaller(aCx, &script, nsnull)) {
|
|
if (NS_FAILED(NS_NewURI(getter_AddRefs(baseURI),
|
|
JS_GetScriptFilename(aCx, script)))) {
|
|
JS_ReportError(aCx, "Failed to construct base URI!");
|
|
return nsnull;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(principal, "Must have a principal now!");
|
|
|
|
if (!isChrome && domain.IsEmpty()) {
|
|
NS_ERROR("Must be chrome or have an domain!");
|
|
return nsnull;
|
|
}
|
|
}
|
|
|
|
size_t urlLength;
|
|
const jschar* urlChars = JS_GetStringCharsZAndLength(aCx, aScriptURL,
|
|
&urlLength);
|
|
if (!urlChars) {
|
|
return nsnull;
|
|
}
|
|
|
|
nsDependentString scriptURL(urlChars, urlLength);
|
|
|
|
nsRefPtr<WorkerPrivate> worker =
|
|
new WorkerPrivate(aCx, aObj, aParent, parentContext, scriptURL,
|
|
aIsChromeWorker, domain, window, scriptContext, baseURI,
|
|
principal, document);
|
|
|
|
worker->SetIsDOMBinding();
|
|
worker->SetWrapper(aObj);
|
|
|
|
nsRefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(worker);
|
|
if (!compiler->Dispatch(aCx)) {
|
|
return nsnull;
|
|
}
|
|
|
|
return worker.forget();
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::DoRunLoop(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
mJSContext = aCx;
|
|
|
|
NS_ASSERTION(mStatus == Pending, "Huh?!");
|
|
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<nsITimer> 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<nsIEventTarget> normalGCEventTarget;
|
|
nsCOMPtr<nsIEventTarget> 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<nsIRunnable> idleGCEvent;
|
|
{
|
|
nsRefPtr<GarbageCollectRunnable> runnable =
|
|
new GarbageCollectRunnable(this, false, false);
|
|
normalGCEventTarget = new WorkerRunnableEventTarget(runnable);
|
|
|
|
runnable = new GarbageCollectRunnable(this, true, false);
|
|
idleGCEventTarget = new WorkerRunnableEventTarget(runnable);
|
|
|
|
idleGCEvent = runnable;
|
|
}
|
|
|
|
mMemoryReporter = new WorkerMemoryReporter(this);
|
|
|
|
if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
|
|
NS_WARNING("Failed to register memory reporter!");
|
|
mMemoryReporter = nsnull;
|
|
}
|
|
|
|
for (;;) {
|
|
Status currentStatus;
|
|
bool scheduleIdleGC;
|
|
|
|
WorkerRunnable* event;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
while (!mControlQueue.Pop(event) && !mQueue.Pop(event)) {
|
|
mCondVar.Wait();
|
|
}
|
|
|
|
bool eventIsNotIdleGCEvent;
|
|
currentStatus = mStatus;
|
|
|
|
{
|
|
MutexAutoUnlock unlock(mMutex);
|
|
|
|
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, nsnull,
|
|
NORMAL_GC_TIMER_DELAY_MS,
|
|
nsITimer::TYPE_REPEATING_SLACK))) {
|
|
normalGCTimerRunning = true;
|
|
}
|
|
else {
|
|
JS_ReportError(aCx, "Failed to start normal GC timer!");
|
|
}
|
|
}
|
|
|
|
#ifdef EXTRA_GC
|
|
// Find GC bugs...
|
|
JS_GC(aCx);
|
|
#endif
|
|
|
|
// Keep track of whether or not this is the idle GC event.
|
|
eventIsNotIdleGCEvent = event != idleGCEvent;
|
|
|
|
static_cast<nsIRunnable*>(event)->Run();
|
|
NS_RELEASE(event);
|
|
}
|
|
|
|
currentStatus = mStatus;
|
|
scheduleIdleGC = mControlQueue.IsEmpty() &&
|
|
mQueue.IsEmpty() &&
|
|
eventIsNotIdleGCEvent;
|
|
}
|
|
|
|
// 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) {
|
|
if (NS_SUCCEEDED(gcTimer->SetTarget(idleGCEventTarget)) &&
|
|
NS_SUCCEEDED(gcTimer->InitWithFuncCallback(
|
|
DummyCallback, nsnull,
|
|
IDLE_GC_TIMER_DELAY_MS,
|
|
nsITimer::TYPE_ONE_SHOT))) {
|
|
}
|
|
else {
|
|
JS_ReportError(aCx, "Failed to start idle GC timer!");
|
|
}
|
|
}
|
|
|
|
#ifdef EXTRA_GC
|
|
// Find GC bugs...
|
|
JS_GC(aCx);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
#ifdef DEBUG
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
currentStatus = mStatus;
|
|
}
|
|
NS_ASSERTION(currentStatus == Killing, "Should have changed status!");
|
|
#else
|
|
currentStatus = Killing;
|
|
#endif
|
|
}
|
|
|
|
// 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!");
|
|
}
|
|
|
|
// Call this before unregistering the reporter as we may be racing with
|
|
// the main thread.
|
|
DisableMemoryReporter();
|
|
|
|
if (mMemoryReporter) {
|
|
if (NS_FAILED(NS_UnregisterMemoryMultiReporter(mMemoryReporter))) {
|
|
NS_WARNING("Failed to unregister memory reporter!");
|
|
}
|
|
mMemoryReporter = nsnull;
|
|
}
|
|
|
|
StopAcceptingEvents();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_NOTREACHED("Shouldn't get here!");
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::OperationCallback(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
bool mayContinue = true;
|
|
|
|
for (;;) {
|
|
// Run all control events now.
|
|
mayContinue = ProcessAllControlRunnables();
|
|
|
|
if (!mayContinue || !mSuspended) {
|
|
break;
|
|
}
|
|
|
|
// Clean up before suspending.
|
|
JS_GC(JS_GetRuntime(aCx));
|
|
|
|
while ((mayContinue = MayContinueRunning())) {
|
|
MutexAutoLock lock(mMutex);
|
|
if (!mControlQueue.IsEmpty()) {
|
|
break;
|
|
}
|
|
|
|
mCondVar.Wait(PR_MillisecondsToInterval(RemainingRunTimeMS()));
|
|
}
|
|
}
|
|
|
|
if (!mayContinue) {
|
|
// We want only uncatchable exceptions here.
|
|
NS_ASSERTION(!JS_IsExceptionPending(aCx),
|
|
"Should not have an exception set here!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::ScheduleDeletion(bool aWasPending)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
NS_ASSERTION(mChildWorkers.IsEmpty(), "Live child workers!");
|
|
NS_ASSERTION(mSyncQueues.IsEmpty(), "Should have no sync queues here!");
|
|
|
|
StopAcceptingEvents();
|
|
|
|
nsIThread* currentThread;
|
|
if (aWasPending) {
|
|
// Don't want to close down this thread since we never got to run!
|
|
currentThread = nsnull;
|
|
}
|
|
else {
|
|
currentThread = NS_GetCurrentThread();
|
|
NS_ASSERTION(currentThread, "This should never be null!");
|
|
}
|
|
|
|
WorkerPrivate* parent = GetParent();
|
|
if (parent) {
|
|
nsRefPtr<WorkerFinishedRunnable> runnable =
|
|
new WorkerFinishedRunnable(parent, this, currentThread);
|
|
if (!runnable->Dispatch(nsnull)) {
|
|
NS_WARNING("Failed to dispatch runnable!");
|
|
}
|
|
}
|
|
else {
|
|
nsRefPtr<TopLevelWorkerFinishedRunnable> runnable =
|
|
new TopLevelWorkerFinishedRunnable(this, currentThread);
|
|
if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
|
|
NS_WARNING("Failed to dispatch runnable!");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::BlockAndCollectRuntimeStats(bool aIsQuick, void* aData, bool* aDisabled)
|
|
{
|
|
AssertIsOnMainThread();
|
|
NS_ASSERTION(aData, "Null data!");
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mMemoryReporterDisabled) {
|
|
*aDisabled = true;
|
|
return true;
|
|
}
|
|
|
|
*aDisabled = false;
|
|
mMemoryReporterRunning = true;
|
|
}
|
|
|
|
bool succeeded;
|
|
|
|
nsRefPtr<CollectRuntimeStatsRunnable> runnable =
|
|
new CollectRuntimeStatsRunnable(this, aIsQuick, aData, &succeeded);
|
|
if (!runnable->Dispatch(nsnull)) {
|
|
NS_WARNING("Failed to dispatch runnable!");
|
|
succeeded = false;
|
|
}
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
mMemoryReporterRunning = false;
|
|
}
|
|
|
|
return succeeded;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::DisableMemoryReporter()
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
bool result = true;
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
mMemoryReporterDisabled = true;
|
|
|
|
while (mMemoryReporterRunning) {
|
|
MutexAutoUnlock unlock(mMutex);
|
|
result = ProcessAllControlRunnables() && result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::ProcessAllControlRunnables()
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
bool result = true;
|
|
|
|
for (;;) {
|
|
WorkerRunnable* event;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
if (!mControlQueue.Pop(event)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
|
|
result = false;
|
|
}
|
|
|
|
NS_RELEASE(event);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::Dispatch(WorkerRunnable* aEvent, EventQueue* aQueue)
|
|
{
|
|
nsRefPtr<WorkerRunnable> 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<WorkerRunnable> 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)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
mMutex.AssertCurrentThreadOwns();
|
|
|
|
WorkerRunnable* event;
|
|
while (aQueue->Pop(event)) {
|
|
if (event->WantsToRunDuringClear()) {
|
|
MutexAutoUnlock unlock(mMutex);
|
|
|
|
static_cast<nsIRunnable*>(event)->Run();
|
|
}
|
|
event->Release();
|
|
}
|
|
}
|
|
|
|
PRUint32
|
|
WorkerPrivate::RemainingRunTimeMS() const
|
|
{
|
|
if (mKillTime.IsNull()) {
|
|
return PR_UINT32_MAX;
|
|
}
|
|
TimeDuration runtime = mKillTime - TimeStamp::Now();
|
|
double ms = runtime > TimeDuration(0) ? runtime.ToMilliseconds() : 0;
|
|
return ms > double(PR_UINT32_MAX) ? PR_UINT32_MAX : PRUint32(ms);
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::SuspendInternal(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(!mSuspended, "Already suspended!");
|
|
|
|
mSuspended = true;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::ResumeInternal(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(mSuspended, "Not yet suspended!");
|
|
|
|
mSuspended = false;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::TraceInternal(JSTracer* aTrc)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
for (PRUint32 index = 0; index < mTimeouts.Length(); index++) {
|
|
TimeoutInfo* info = mTimeouts[index];
|
|
JS_CALL_VALUE_TRACER(aTrc, info->mTimeoutVal,
|
|
"WorkerPrivate timeout value");
|
|
for (PRUint32 index2 = 0; index2 < info->mExtraArgVals.Length(); index2++) {
|
|
JS_CALL_VALUE_TRACER(aTrc, info->mExtraArgVals[index2],
|
|
"WorkerPrivate timeout extra argument value");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::ModifyBusyCountFromWorker(JSContext* aCx, bool aIncrease)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
// If we're in shutdown then the busy count is no longer being considered so
|
|
// just return now.
|
|
if (mStatus >= Killing) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
nsRefPtr<ModifyBusyCountRunnable> runnable =
|
|
new ModifyBusyCountRunnable(this, aIncrease);
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::AddChildWorker(JSContext* aCx, ParentType* aChildWorker)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
Status currentStatus;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
currentStatus = mStatus;
|
|
}
|
|
|
|
if (currentStatus > Running) {
|
|
JS_ReportError(aCx, "Cannot create child workers from the close handler!");
|
|
return false;
|
|
}
|
|
|
|
NS_ASSERTION(!mChildWorkers.Contains(aChildWorker),
|
|
"Already know about this one!");
|
|
mChildWorkers.AppendElement(aChildWorker);
|
|
|
|
return mChildWorkers.Length() == 1 ?
|
|
ModifyBusyCountFromWorker(aCx, true) :
|
|
true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::RemoveChildWorker(JSContext* aCx, ParentType* aChildWorker)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(mChildWorkers.Contains(aChildWorker),
|
|
"Didn't know about this one!");
|
|
mChildWorkers.RemoveElement(aChildWorker);
|
|
|
|
if (mChildWorkers.IsEmpty() && !ModifyBusyCountFromWorker(aCx, false)) {
|
|
NS_WARNING("Failed to modify busy count!");
|
|
}
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::AddFeature(JSContext* aCx, WorkerFeature* aFeature)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mStatus >= Canceling) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(!mFeatures.Contains(aFeature), "Already know about this one!");
|
|
mFeatures.AppendElement(aFeature);
|
|
|
|
return mFeatures.Length() == 1 ?
|
|
ModifyBusyCountFromWorker(aCx, true) :
|
|
true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::RemoveFeature(JSContext* aCx, WorkerFeature* aFeature)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(mFeatures.Contains(aFeature), "Didn't know about this one!");
|
|
mFeatures.RemoveElement(aFeature);
|
|
|
|
if (mFeatures.IsEmpty() && !ModifyBusyCountFromWorker(aCx, false)) {
|
|
NS_WARNING("Failed to modify busy count!");
|
|
}
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::NotifyFeatures(JSContext* aCx, Status aStatus)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(aStatus > Running, "Bad status!");
|
|
|
|
if (aStatus >= Closing) {
|
|
CancelAllTimeouts(aCx);
|
|
}
|
|
|
|
nsAutoTArray<WorkerFeature*, 30> features;
|
|
features.AppendElements(mFeatures);
|
|
|
|
for (PRUint32 index = 0; index < features.Length(); index++) {
|
|
if (!features[index]->Notify(aCx, aStatus)) {
|
|
NS_WARNING("Failed to notify feature!");
|
|
}
|
|
}
|
|
|
|
nsAutoTArray<ParentType*, 10> children;
|
|
children.AppendElements(mChildWorkers);
|
|
|
|
for (PRUint32 index = 0; index < children.Length(); index++) {
|
|
if (!children[index]->Notify(aCx, aStatus)) {
|
|
NS_WARNING("Failed to notify child worker!");
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::CancelAllTimeouts(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
if (mTimerRunning) {
|
|
NS_ASSERTION(mTimer, "Huh?!");
|
|
NS_ASSERTION(!mTimeouts.IsEmpty(), "Huh?!");
|
|
|
|
if (NS_FAILED(mTimer->Cancel())) {
|
|
NS_WARNING("Failed to cancel timer!");
|
|
}
|
|
|
|
for (PRUint32 index = 0; index < mTimeouts.Length(); index++) {
|
|
mTimeouts[index]->mCanceled = true;
|
|
}
|
|
|
|
if (!RunExpiredTimeouts(aCx)) {
|
|
JS_ReportPendingException(aCx);
|
|
}
|
|
|
|
mTimerRunning = false;
|
|
}
|
|
#ifdef DEBUG
|
|
else if (!mRunningExpiredTimeouts) {
|
|
NS_ASSERTION(mTimeouts.IsEmpty(), "Huh?!");
|
|
}
|
|
#endif
|
|
|
|
mTimer = nsnull;
|
|
}
|
|
|
|
PRUint32
|
|
WorkerPrivate::CreateNewSyncLoop()
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(mSyncQueues.Length() < PR_UINT32_MAX,
|
|
"Should have bailed by now!");
|
|
|
|
mSyncQueues.AppendElement(new SyncQueue());
|
|
return mSyncQueues.Length() - 1;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::RunSyncLoop(JSContext* aCx, PRUint32 aSyncLoopKey)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(!mSyncQueues.IsEmpty() ||
|
|
(aSyncLoopKey != mSyncQueues.Length() - 1),
|
|
"Forgot to call CreateNewSyncLoop!");
|
|
if (aSyncLoopKey != mSyncQueues.Length() - 1) {
|
|
return false;
|
|
}
|
|
|
|
SyncQueue* syncQueue = mSyncQueues[aSyncLoopKey].get();
|
|
|
|
for (;;) {
|
|
WorkerRunnable* event;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
while (!mControlQueue.Pop(event) && !syncQueue->mQueue.Pop(event)) {
|
|
mCondVar.Wait();
|
|
}
|
|
}
|
|
|
|
#ifdef EXTRA_GC
|
|
// Find GC bugs...
|
|
JS_GC(mJSContext);
|
|
#endif
|
|
|
|
static_cast<nsIRunnable*>(event)->Run();
|
|
NS_RELEASE(event);
|
|
|
|
#ifdef EXTRA_GC
|
|
// Find GC bugs...
|
|
JS_GC(mJSContext);
|
|
#endif
|
|
|
|
if (syncQueue->mComplete) {
|
|
NS_ASSERTION(mSyncQueues.Length() - 1 == aSyncLoopKey,
|
|
"Mismatched calls!");
|
|
NS_ASSERTION(syncQueue->mQueue.IsEmpty(), "Unprocessed sync events!");
|
|
|
|
bool result = syncQueue->mResult;
|
|
mSyncQueues.RemoveElementAt(aSyncLoopKey);
|
|
|
|
#ifdef DEBUG
|
|
syncQueue = nsnull;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
NS_NOTREACHED("Shouldn't get here!");
|
|
return false;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::StopSyncLoop(PRUint32 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;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
JSStructuredCloneCallbacks* callbacks =
|
|
IsChromeWorker() ?
|
|
&gChromeWorkerStructuredCloneCallbacks :
|
|
&gWorkerStructuredCloneCallbacks;
|
|
|
|
nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
|
|
|
|
JSAutoStructuredCloneBuffer buffer;
|
|
if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
|
|
return false;
|
|
}
|
|
|
|
nsRefPtr<MessageEventRunnable> runnable =
|
|
new MessageEventRunnable(this, WorkerRunnable::ParentThread, buffer,
|
|
clonedObjects);
|
|
return runnable->Dispatch(aCx);
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
|
|
|
|
// Save the old status and set the new status.
|
|
Status previousStatus;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (mStatus >= aStatus) {
|
|
return true;
|
|
}
|
|
|
|
previousStatus = mStatus;
|
|
mStatus = aStatus;
|
|
}
|
|
|
|
// 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.
|
|
mCrossThreadDispatcher->Forget();
|
|
}
|
|
|
|
NS_ASSERTION(previousStatus != Pending, "How is this possible?!");
|
|
|
|
NS_ASSERTION(previousStatus >= Canceling || mKillTime.IsNull(),
|
|
"Bad kill time set!");
|
|
|
|
// Let all our features know the new status.
|
|
NotifyFeatures(aCx, aStatus);
|
|
|
|
// There's nothing to do here if we never succeeded in running the worker
|
|
// script or if the close handler has already run.
|
|
if (!JS_GetGlobalObject(aCx) || mCloseHandlerFinished) {
|
|
return true;
|
|
}
|
|
|
|
// If this is the first time our status has changed then we need to clear the
|
|
// main event queue. We also need to schedule the close handler unless we're
|
|
// being shut down.
|
|
if (previousStatus == Running) {
|
|
NS_ASSERTION(!mCloseHandlerStarted && !mCloseHandlerFinished,
|
|
"This is impossible!");
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
ClearQueue(&mQueue);
|
|
}
|
|
|
|
if (aStatus != Killing) {
|
|
nsRefPtr<CloseEventRunnable> closeRunnable = new CloseEventRunnable(this);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (!mQueue.Push(closeRunnable)) {
|
|
NS_WARNING("Failed to push closeRunnable!");
|
|
return false;
|
|
}
|
|
|
|
closeRunnable.forget();
|
|
}
|
|
}
|
|
|
|
if (aStatus == Closing) {
|
|
// Notify parent to stop sending us messages and balance our busy count.
|
|
nsRefPtr<CloseRunnable> runnable = new CloseRunnable(this);
|
|
if (!runnable->Dispatch(aCx)) {
|
|
return false;
|
|
}
|
|
|
|
// Don't abort the script.
|
|
return true;
|
|
}
|
|
|
|
if (aStatus == Terminating) {
|
|
// Only abort the script if we're not yet running the close handler.
|
|
return mCloseHandlerStarted;
|
|
}
|
|
|
|
if (aStatus == Canceling) {
|
|
// We need to enforce a timeout on the close handler.
|
|
NS_ASSERTION(previousStatus == Running || previousStatus == Closing ||
|
|
previousStatus == Terminating,
|
|
"Bad previous status!");
|
|
|
|
PRUint32 killSeconds = RuntimeService::GetCloseHandlerTimeoutSeconds();
|
|
if (killSeconds) {
|
|
mKillTime = TimeStamp::Now() + TimeDuration::FromSeconds(killSeconds);
|
|
|
|
if (!mCloseHandlerFinished && !ScheduleKillCloseEventRunnable(aCx)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Only abort the script if we're not yet running the close handler.
|
|
return mCloseHandlerStarted;
|
|
}
|
|
|
|
if (aStatus == Killing) {
|
|
mKillTime = TimeStamp::Now();
|
|
|
|
if (!mCloseHandlerFinished && !ScheduleKillCloseEventRunnable(aCx)) {
|
|
return false;
|
|
}
|
|
|
|
// Always abort the script.
|
|
return false;
|
|
}
|
|
|
|
NS_NOTREACHED("Should never get here!");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::ScheduleKillCloseEventRunnable(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
NS_ASSERTION(!mKillTime.IsNull(), "Must have a kill time!");
|
|
|
|
nsRefPtr<KillCloseEventRunnable> killCloseEventRunnable =
|
|
new KillCloseEventRunnable(this);
|
|
if (!killCloseEventRunnable->SetTimeout(aCx, RemainingRunTimeMS())) {
|
|
return false;
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
|
|
if (!mQueue.Push(killCloseEventRunnable)) {
|
|
NS_WARNING("Failed to push killCloseEventRunnable!");
|
|
return false;
|
|
}
|
|
|
|
killCloseEventRunnable.forget();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::ReportError(JSContext* aCx, const char* aMessage,
|
|
JSErrorReport* aReport)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
if (!MayContinueRunning() || mErrorHandlerRecursionCount == 2) {
|
|
return;
|
|
}
|
|
|
|
NS_ASSERTION(mErrorHandlerRecursionCount == 0 ||
|
|
mErrorHandlerRecursionCount == 1,
|
|
"Bad recursion logic!");
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
nsString message, filename, line;
|
|
PRUint32 lineNumber, columnNumber, flags, errorNumber;
|
|
|
|
if (aReport) {
|
|
if (aReport->ucmessage) {
|
|
message = aReport->ucmessage;
|
|
}
|
|
filename = NS_ConvertUTF8toUTF16(aReport->filename);
|
|
line = aReport->uclinebuf;
|
|
lineNumber = aReport->lineno;
|
|
columnNumber = aReport->uctokenptr - aReport->uclinebuf;
|
|
flags = aReport->flags;
|
|
errorNumber = aReport->errorNumber;
|
|
}
|
|
else {
|
|
lineNumber = columnNumber = errorNumber = 0;
|
|
flags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
|
|
}
|
|
|
|
if (message.IsEmpty()) {
|
|
message = NS_ConvertUTF8toUTF16(aMessage);
|
|
}
|
|
|
|
mErrorHandlerRecursionCount++;
|
|
|
|
// Don't want to run the scope's error handler if this is a recursive error or
|
|
// if there was an error in the close handler or if we ran out of memory.
|
|
bool fireAtScope = mErrorHandlerRecursionCount == 1 &&
|
|
!mCloseHandlerStarted &&
|
|
errorNumber != JSMSG_OUT_OF_MEMORY;
|
|
|
|
if (!ReportErrorRunnable::ReportError(aCx, this, fireAtScope, nsnull, message,
|
|
filename, line, lineNumber,
|
|
columnNumber, flags, errorNumber, 0)) {
|
|
JS_ReportPendingException(aCx);
|
|
}
|
|
|
|
mErrorHandlerRecursionCount--;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::SetTimeout(JSContext* aCx, unsigned aArgc, jsval* aVp,
|
|
bool aIsInterval)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
NS_ASSERTION(aArgc, "Huh?!");
|
|
|
|
const PRUint32 timerId = mNextTimeoutId++;
|
|
|
|
Status currentStatus;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
currentStatus = mStatus;
|
|
}
|
|
|
|
// It's a script bug if setTimeout/setInterval are called from a close handler
|
|
// so throw an exception.
|
|
if (currentStatus == Closing) {
|
|
JS_ReportError(aCx, "Cannot schedule timeouts from the close handler!");
|
|
}
|
|
|
|
// If the worker is trying to call setTimeout/setInterval and the parent
|
|
// thread has initiated the close process then just silently fail.
|
|
if (currentStatus >= Closing) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
|
|
newInfo->mIsInterval = aIsInterval;
|
|
newInfo->mId = timerId;
|
|
|
|
if (NS_UNLIKELY(timerId == PR_UINT32_MAX)) {
|
|
NS_WARNING("Timeout ids overflowed!");
|
|
mNextTimeoutId = 1;
|
|
}
|
|
|
|
jsval* argv = JS_ARGV(aCx, aVp);
|
|
|
|
// Take care of the main argument.
|
|
if (JSVAL_IS_OBJECT(argv[0])) {
|
|
if (JS_ObjectIsCallable(aCx, JSVAL_TO_OBJECT(argv[0]))) {
|
|
newInfo->mTimeoutVal = argv[0];
|
|
}
|
|
else {
|
|
JSString* timeoutStr = JS_ValueToString(aCx, argv[0]);
|
|
if (!timeoutStr) {
|
|
return false;
|
|
}
|
|
newInfo->mTimeoutVal = STRING_TO_JSVAL(timeoutStr);
|
|
}
|
|
}
|
|
else if (JSVAL_IS_STRING(argv[0])) {
|
|
newInfo->mTimeoutVal = argv[0];
|
|
}
|
|
else {
|
|
JS_ReportError(aCx, "Useless %s call (missing quotes around argument?)",
|
|
aIsInterval ? "setInterval" : "setTimeout");
|
|
return false;
|
|
}
|
|
|
|
// See if any of the optional arguments were passed.
|
|
if (aArgc > 1) {
|
|
double intervalMS = 0;
|
|
if (!JS_ValueToNumber(aCx, argv[1], &intervalMS)) {
|
|
return false;
|
|
}
|
|
newInfo->mInterval = TimeDuration::FromMilliseconds(intervalMS);
|
|
|
|
if (aArgc > 2 && JSVAL_IS_OBJECT(newInfo->mTimeoutVal)) {
|
|
nsTArray<jsval> extraArgVals(aArgc - 2);
|
|
for (unsigned index = 2; index < aArgc; index++) {
|
|
extraArgVals.AppendElement(argv[index]);
|
|
}
|
|
newInfo->mExtraArgVals.SwapElements(extraArgVals);
|
|
}
|
|
}
|
|
|
|
newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
|
|
|
|
if (JSVAL_IS_STRING(newInfo->mTimeoutVal)) {
|
|
const char* filenameChars;
|
|
PRUint32 lineNumber;
|
|
if (nsJSUtils::GetCallingLocation(aCx, &filenameChars, &lineNumber)) {
|
|
newInfo->mFilename = filenameChars;
|
|
newInfo->mLineNumber = lineNumber;
|
|
}
|
|
else {
|
|
NS_WARNING("Failed to get calling location!");
|
|
}
|
|
}
|
|
|
|
mTimeouts.InsertElementSorted(newInfo.get(), GetAutoPtrComparator(mTimeouts));
|
|
|
|
// If the timeout we just made is set to fire next then we need to update the
|
|
// timer.
|
|
if (mTimeouts[0] == newInfo) {
|
|
nsresult rv;
|
|
|
|
if (!mTimer) {
|
|
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
JS_ReportError(aCx, "Failed to create timer!");
|
|
return false;
|
|
}
|
|
|
|
nsRefPtr<TimerRunnable> timerRunnable = new TimerRunnable(this);
|
|
|
|
nsCOMPtr<nsIEventTarget> target =
|
|
new WorkerRunnableEventTarget(timerRunnable);
|
|
rv = mTimer->SetTarget(target);
|
|
if (NS_FAILED(rv)) {
|
|
JS_ReportError(aCx, "Failed to set timer's target!");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mTimerRunning) {
|
|
if (!ModifyBusyCountFromWorker(aCx, true)) {
|
|
return false;
|
|
}
|
|
mTimerRunning = true;
|
|
}
|
|
|
|
if (!RescheduleTimeoutTimer(aCx)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS_SET_RVAL(aCx, aVp, INT_TO_JSVAL(timerId));
|
|
|
|
newInfo.forget();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::ClearTimeout(JSContext* aCx, uint32 aId)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
if (!mTimeouts.IsEmpty()) {
|
|
NS_ASSERTION(mTimerRunning, "Huh?!");
|
|
|
|
for (PRUint32 index = 0; index < mTimeouts.Length(); index++) {
|
|
nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
|
|
if (info->mId == aId) {
|
|
info->mCanceled = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
// We may be called recursively (e.g. close() inside a timeout) or we could
|
|
// have been canceled while this event was pending, bail out if there is
|
|
// nothing to do.
|
|
if (mRunningExpiredTimeouts || !mTimerRunning) {
|
|
return true;
|
|
}
|
|
|
|
NS_ASSERTION(mTimer, "Must have a timer!");
|
|
NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some work to do!");
|
|
|
|
bool retval = true;
|
|
|
|
AutoPtrComparator<TimeoutInfo> comparator = GetAutoPtrComparator(mTimeouts);
|
|
JSObject* global = JS_GetGlobalObject(aCx);
|
|
JSPrincipals* principal = GetWorkerPrincipal();
|
|
|
|
// We want to make sure to run *something*, even if the timer fired a little
|
|
// early. Fudge the value of now to at least include the first timeout.
|
|
const TimeStamp now = NS_MAX(TimeStamp::Now(), mTimeouts[0]->mTargetTime);
|
|
|
|
nsAutoTArray<TimeoutInfo*, 10> expiredTimeouts;
|
|
for (PRUint32 index = 0; index < mTimeouts.Length(); index++) {
|
|
nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
|
|
if (info->mTargetTime > now) {
|
|
break;
|
|
}
|
|
expiredTimeouts.AppendElement(info);
|
|
}
|
|
|
|
// Guard against recursion.
|
|
mRunningExpiredTimeouts = true;
|
|
|
|
// Run expired timeouts.
|
|
for (PRUint32 index = 0; index < expiredTimeouts.Length(); index++) {
|
|
TimeoutInfo*& info = expiredTimeouts[index];
|
|
|
|
if (info->mCanceled) {
|
|
continue;
|
|
}
|
|
|
|
// Always call JS_ReportPendingException if something fails, and if
|
|
// JS_ReportPendingException returns false (i.e. uncatchable exception) then
|
|
// break out of the loop.
|
|
|
|
if (JSVAL_IS_STRING(info->mTimeoutVal)) {
|
|
JSString* expression = JSVAL_TO_STRING(info->mTimeoutVal);
|
|
|
|
size_t stringLength;
|
|
const jschar* string = JS_GetStringCharsAndLength(aCx, expression,
|
|
&stringLength);
|
|
|
|
if ((!string ||
|
|
!JS_EvaluateUCScriptForPrincipals(aCx, global, principal, string,
|
|
stringLength,
|
|
info->mFilename.get(),
|
|
info->mLineNumber, nsnull)) &&
|
|
!JS_ReportPendingException(aCx)) {
|
|
retval = false;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
jsval rval;
|
|
if (!JS_CallFunctionValue(aCx, global, info->mTimeoutVal,
|
|
info->mExtraArgVals.Length(),
|
|
info->mExtraArgVals.Elements(), &rval) &&
|
|
!JS_ReportPendingException(aCx)) {
|
|
retval = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
|
|
|
|
// Reschedule intervals.
|
|
if (info->mIsInterval && !info->mCanceled) {
|
|
PRUint32 timeoutIndex = mTimeouts.IndexOf(info);
|
|
NS_ASSERTION(timeoutIndex != PRUint32(-1),
|
|
"Should still be in the main list!");
|
|
|
|
// This is nasty but we have to keep the old nsAutoPtr from deleting the
|
|
// info we're about to re-add.
|
|
mTimeouts[timeoutIndex].forget();
|
|
mTimeouts.RemoveElementAt(timeoutIndex);
|
|
|
|
NS_ASSERTION(!mTimeouts.Contains(info), "Shouldn't have duplicates!");
|
|
|
|
// NB: We must ensure that info->mTargetTime > now (where now is the
|
|
// now above, not literally TimeStamp::Now()) or we will remove the
|
|
// interval in the next loop below.
|
|
info->mTargetTime = NS_MAX(info->mTargetTime + info->mInterval,
|
|
now + TimeDuration::FromMilliseconds(1));
|
|
mTimeouts.InsertElementSorted(info, comparator);
|
|
}
|
|
}
|
|
|
|
// No longer possible to be called recursively.
|
|
mRunningExpiredTimeouts = false;
|
|
|
|
// Now remove canceled and expired timeouts from the main list.
|
|
for (PRUint32 index = 0; index < mTimeouts.Length(); ) {
|
|
nsAutoPtr<TimeoutInfo>& info = mTimeouts[index];
|
|
if (info->mTargetTime <= now || info->mCanceled) {
|
|
NS_ASSERTION(!info->mIsInterval || info->mCanceled,
|
|
"Interval timers can only be removed when canceled!");
|
|
mTimeouts.RemoveElement(info);
|
|
}
|
|
else {
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// Either signal the parent that we're no longer using timeouts or reschedule
|
|
// the timer.
|
|
if (mTimeouts.IsEmpty()) {
|
|
if (!ModifyBusyCountFromWorker(aCx, false)) {
|
|
retval = false;
|
|
}
|
|
mTimerRunning = false;
|
|
}
|
|
else if (retval && !RescheduleTimeoutTimer(aCx)) {
|
|
retval = false;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool
|
|
WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some timeouts!");
|
|
NS_ASSERTION(mTimer, "Should have a timer!");
|
|
|
|
double delta =
|
|
(mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
|
|
PRUint32 delay = delta > 0 ? NS_MIN(delta, double(PR_UINT32_MAX)) : 0;
|
|
|
|
nsresult rv = mTimer->InitWithFuncCallback(DummyCallback, nsnull, delay,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
if (NS_FAILED(rv)) {
|
|
JS_ReportError(aCx, "Failed to start timer!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::UpdateJSContextOptionsInternal(JSContext* aCx, PRUint32 aOptions)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
JS_SetOptions(aCx, aOptions);
|
|
|
|
for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
|
|
mChildWorkers[index]->UpdateJSContextOptions(aCx, aOptions);
|
|
}
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::UpdateJSRuntimeHeapSizeInternal(JSContext* aCx,
|
|
PRUint32 aMaxBytes)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
JS_SetGCParameter(JS_GetRuntime(aCx), JSGC_MAX_BYTES, aMaxBytes);
|
|
|
|
for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
|
|
mChildWorkers[index]->UpdateJSRuntimeHeapSize(aCx, aMaxBytes);
|
|
}
|
|
}
|
|
|
|
#ifdef JS_GC_ZEAL
|
|
void
|
|
WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, PRUint8 aGCZeal)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
PRUint32 frequency = aGCZeal <= 2 ? JS_DEFAULT_ZEAL_FREQ : 1;
|
|
JS_SetGCZeal(aCx, aGCZeal, frequency);
|
|
|
|
for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
|
|
mChildWorkers[index]->UpdateGCZeal(aCx, aGCZeal);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
|
|
bool aCollectChildren)
|
|
{
|
|
AssertIsOnWorkerThread();
|
|
|
|
JSRuntime *rt = JS_GetRuntime(aCx);
|
|
js::PrepareForFullGC(rt);
|
|
if (aShrinking) {
|
|
js::ShrinkingGC(rt, js::gcreason::DOM_WORKER);
|
|
}
|
|
else {
|
|
js::GCForReason(rt, js::gcreason::DOM_WORKER);
|
|
}
|
|
|
|
if (aCollectChildren) {
|
|
for (PRUint32 index = 0; index < mChildWorkers.Length(); index++) {
|
|
mChildWorkers[index]->GarbageCollect(aCx, aShrinking);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::AssertIsOnParentThread() const
|
|
{
|
|
if (GetParent()) {
|
|
GetParent()->AssertIsOnWorkerThread();
|
|
}
|
|
else {
|
|
AssertIsOnMainThread();
|
|
}
|
|
}
|
|
|
|
template <class Derived>
|
|
void
|
|
WorkerPrivateParent<Derived>::AssertInnerWindowIsCorrect() const
|
|
{
|
|
AssertIsOnParentThread();
|
|
|
|
// Only care about top level workers from windows.
|
|
if (mParent || !mWindow) {
|
|
return;
|
|
}
|
|
|
|
AssertIsOnMainThread();
|
|
|
|
nsPIDOMWindow* outer = mWindow->GetOuterWindow();
|
|
NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mWindow,
|
|
"Inner window no longer correct!");
|
|
}
|
|
|
|
void
|
|
WorkerPrivate::AssertIsOnWorkerThread() const
|
|
{
|
|
if (mThread) {
|
|
bool current;
|
|
if (NS_FAILED(mThread->IsOnCurrentThread(¤t)) || !current) {
|
|
NS_ERROR("Wrong thread!");
|
|
}
|
|
}
|
|
else {
|
|
NS_ERROR("Trying to assert thread identity after thread has been "
|
|
"shutdown!");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
WorkerCrossThreadDispatcher*
|
|
WorkerPrivate::GetCrossThreadDispatcher()
|
|
{
|
|
mozilla::MutexAutoLock lock(mMutex);
|
|
if (!mCrossThreadDispatcher && mStatus <= Running) {
|
|
mCrossThreadDispatcher = new WorkerCrossThreadDispatcher(this);
|
|
}
|
|
return mCrossThreadDispatcher;
|
|
}
|
|
|
|
BEGIN_WORKERS_NAMESPACE
|
|
|
|
// Force instantiation.
|
|
template class WorkerPrivateParent<WorkerPrivate>;
|
|
|
|
WorkerPrivate*
|
|
GetWorkerPrivateFromContext(JSContext* aCx)
|
|
{
|
|
NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
|
|
return static_cast<WorkerPrivate*>(JS_GetContextPrivate(aCx));
|
|
}
|
|
|
|
JSStructuredCloneCallbacks*
|
|
WorkerStructuredCloneCallbacks(bool aMainRuntime)
|
|
{
|
|
return aMainRuntime ?
|
|
&gMainThreadWorkerStructuredCloneCallbacks :
|
|
&gWorkerStructuredCloneCallbacks;
|
|
}
|
|
|
|
JSStructuredCloneCallbacks*
|
|
ChromeWorkerStructuredCloneCallbacks(bool aMainRuntime)
|
|
{
|
|
return aMainRuntime ?
|
|
&gMainThreadChromeWorkerStructuredCloneCallbacks :
|
|
&gChromeWorkerStructuredCloneCallbacks;
|
|
}
|
|
|
|
END_WORKERS_NAMESPACE
|