Bug 550275 - 'Implement the HTML5 structured clone algorithm'. Make Web Workers use the new clones. r=jst

This commit is contained in:
Ben Turner 2010-03-17 12:56:49 -07:00
Родитель e47beee477
Коммит f8db2a1523
7 изменённых файлов: 190 добавлений и 350 удалений

Просмотреть файл

@ -128,16 +128,6 @@ static const char* sPrefsToWatch[] = {
// The length of time the close handler is allowed to run in milliseconds.
static PRUint32 gWorkerCloseHandlerTimeoutMS = 10000;
static int sStringFinalizerIndex = -1;
static void
StringFinalizer(JSContext* aCx,
JSString* aStr)
{
NS_ASSERTION(aStr, "Null string!");
nsStringBuffer::FromData(JS_GetStringChars(aStr))->Release();
}
/**
* Simple class to automatically destroy a JSContext to make error handling
* easier.
@ -720,7 +710,6 @@ nsDOMThreadService::Init()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!gDOMThreadService, "Only one instance should ever be created!");
NS_ASSERTION(sStringFinalizerIndex == -1, "String finalizer already set!");
nsresult rv;
nsCOMPtr<nsIObserverService> obs =
@ -732,9 +721,6 @@ nsDOMThreadService::Init()
obs.forget(&gObserverService);
sStringFinalizerIndex = JS_AddExternalStringFinalizer(StringFinalizer);
NS_ENSURE_TRUE(sStringFinalizerIndex != -1, NS_ERROR_FAILURE);
RegisterPrefCallbacks();
mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
@ -1249,38 +1235,6 @@ nsDOMThreadService::WorkerSecurityManager()
return gWorkerSecurityManager;
}
// static
jsval
nsDOMThreadService::ShareStringAsJSVal(JSContext* aCx,
const nsAString& aString)
{
NS_ASSERTION(sStringFinalizerIndex != -1, "Bad index!");
NS_ASSERTION(aCx, "Null context!");
PRUint32 length = aString.Length();
if (!length) {
JSAtom* atom = aCx->runtime->atomState.emptyAtom;
return ATOM_KEY(atom);
}
nsStringBuffer* buf = nsStringBuffer::FromString(aString);
if (!buf) {
NS_WARNING("Can't share this string buffer!");
return JSVAL_VOID;
}
JSString* str =
JS_NewExternalString(aCx, reinterpret_cast<jschar*>(buf->Data()), length,
sStringFinalizerIndex);
if (str) {
buf->AddRef();
return STRING_TO_JSVAL(str);
}
NS_WARNING("JS_NewExternalString failed!");
return JSVAL_VOID;
}
/**
* See nsIEventTarget
*/

Просмотреть файл

@ -108,9 +108,6 @@ public:
static nsIThreadJSContextStack* ThreadJSContextStack();
static nsIXPCSecurityManager* WorkerSecurityManager();
static jsval ShareStringAsJSVal(JSContext* aCx,
const nsAString& aString);
void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);

Просмотреть файл

@ -401,105 +401,6 @@ JSFunctionSpec gDOMWorkerFunctions[] = {
{ nsnull, nsnull, 0, 0, 0 }
};
static JSBool
WriteCallback(const jschar* aBuffer,
uint32 aLength,
void* aData)
{
nsJSONWriter* writer = static_cast<nsJSONWriter*>(aData);
nsresult rv = writer->Write((const PRUnichar*)aBuffer, (PRUint32)aLength);
return NS_SUCCEEDED(rv) ? JS_TRUE : JS_FALSE;
}
static nsresult
GetStringForArgument(JSContext* aCx,
jsval aVal,
PRBool* aIsJSON,
PRBool* aIsPrimitive,
nsAutoJSValHolder& _retval)
{
NS_ASSERTION(aIsJSON && aIsPrimitive, "Null pointer!");
if (JSVAL_IS_STRING(aVal)) {
if (!JS_MakeStringImmutable(aCx, JSVAL_TO_STRING(aVal))) {
return NS_ERROR_FAILURE;
}
*aIsJSON = *aIsPrimitive = PR_FALSE;
_retval = aVal;
return NS_OK;
}
nsAutoJSValHolder jsonVal;
JSBool ok = jsonVal.Hold(aCx);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
if (JSVAL_IS_PRIMITIVE(aVal)) {
// Only objects can be serialized through JSON, currently, so if we've been
// given a primitive we set it as a property on a dummy object before
// sending it to the serializer.
JSObject* obj = JS_NewObject(aCx, NULL, NULL, NULL);
NS_ENSURE_TRUE(obj, NS_ERROR_OUT_OF_MEMORY);
jsonVal = obj;
ok = JS_DefineProperty(aCx, obj, JSON_PRIMITIVE_PROPNAME, aVal, NULL,
NULL, JSPROP_ENUMERATE);
NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
*aIsPrimitive = PR_TRUE;
}
else {
jsonVal = aVal;
*aIsPrimitive = PR_FALSE;
}
JSType type;
jsval* vp = jsonVal.ToJSValPtr();
// This may change vp if there is a 'toJSON' function on the object.
ok = JS_TryJSON(aCx, vp);
if (!(ok && !JSVAL_IS_PRIMITIVE(*vp) &&
(type = JS_TypeOfValue(aCx, *vp)) != JSTYPE_FUNCTION &&
type != JSTYPE_XML)) {
return NS_ERROR_INVALID_ARG;
}
// Make sure to hold the new vp in case it changed.
jsonVal = *vp;
nsJSONWriter writer;
ok = JS_Stringify(aCx, jsonVal.ToJSValPtr(), NULL, JSVAL_NULL, WriteCallback,
&writer);
if (!ok) {
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
writer.FlushBuffer();
_retval = nsDOMThreadService::ShareStringAsJSVal(aCx, writer.mOutputString);
if (!JSVAL_IS_STRING(_retval)) {
// Yuck, we can't share.
const jschar* buf =
reinterpret_cast<const jschar*>(writer.mOutputString.get());
JSString* str = JS_NewUCStringCopyN(aCx, buf, writer.mOutputString.Length());
if (!str) {
JS_ReportOutOfMemory(aCx);
return NS_ERROR_OUT_OF_MEMORY;
}
_retval = STRING_TO_JSVAL(str);
}
*aIsJSON = PR_TRUE;
return NS_OK;
}
nsDOMWorkerScope::nsDOMWorkerScope(nsDOMWorker* aWorker)
: mWorker(aWorker),
mWrappedNative(nsnull),
@ -1506,11 +1407,10 @@ nsDOMWorker::PostMessageInternal(PRBool aToInner)
return NS_ERROR_FAILURE;
}
PRBool isJSON, isPrimitive;
rv = GetStringForArgument(cx, argv[0], &isJSON, &isPrimitive, val);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(JSVAL_IS_STRING(val), "Bad jsval!");
rv = nsContentUtils::CreateStructuredClone(cx, argv[0], val.ToJSValPtr());
if (NS_FAILED(rv)) {
return rv;
}
nsRefPtr<nsDOMWorkerMessageEvent> message = new nsDOMWorkerMessageEvent();
NS_ENSURE_TRUE(message, NS_ERROR_OUT_OF_MEMORY);
@ -1520,7 +1420,7 @@ nsDOMWorker::PostMessageInternal(PRBool aToInner)
nsnull);
NS_ENSURE_SUCCESS(rv, rv);
rv = message->SetJSONData(cx, val, isJSON, isPrimitive);
rv = message->SetJSVal(cx, val);
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<nsDOMFireEventRunnable> runnable =

Просмотреть файл

@ -264,16 +264,9 @@ NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent,
NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent)
nsresult
nsDOMWorkerMessageEvent::SetJSONData(JSContext* aCx,
jsval aData,
PRBool aIsJSON,
PRBool aIsPrimitive)
nsDOMWorkerMessageEvent::SetJSVal(JSContext* aCx,
jsval aData)
{
NS_ASSERTION(JSVAL_IS_STRING(aData), "Bad jsval!");
mIsJSON = aIsJSON ? PR_TRUE : PR_FALSE;
mIsPrimitive = aIsPrimitive ? PR_TRUE : PR_FALSE;
if (!mDataVal.Hold(aCx)) {
NS_WARNING("Failed to hold jsval!");
return NS_ERROR_FAILURE;
@ -294,80 +287,27 @@ nsDOMWorkerMessageEvent::GetData(nsAString& aData)
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(cc, NS_ERROR_UNEXPECTED);
if (!mDataValWasReparented) {
if (JSVAL_IS_OBJECT(mDataVal) && !JSVAL_IS_NULL(mDataVal)) {
JSContext* cx;
rv = cc->GetJSContext(&cx);
NS_ENSURE_SUCCESS(rv, rv);
rv =
nsContentUtils::ReparentClonedObjectToScope(cx,
JSVAL_TO_OBJECT(mDataVal),
JS_GetGlobalObject(cx));
NS_ENSURE_SUCCESS(rv, rv);
}
mDataValWasReparented = PR_TRUE;
}
jsval* retval;
rv = cc->GetRetValPtr(&retval);
NS_ENSURE_SUCCESS(rv, rv);
if (!mIsJSON) {
cc->SetReturnValueWasSet(PR_TRUE);
*retval = mDataVal;
return NS_OK;
}
if (mHaveCachedJSVal) {
cc->SetReturnValueWasSet(PR_TRUE);
*retval = mCachedJSVal;
return NS_OK;
}
if (mHaveAttemptedConversion) {
// Don't try to convert again if the first time around we saw an error.
return NS_ERROR_FAILURE;
}
mHaveAttemptedConversion = PR_TRUE;
JSContext* cx;
rv = cc->GetJSContext(&cx);
NS_ENSURE_SUCCESS(rv, rv);
JSAutoRequest ar(cx);
JSBool ok = mCachedJSVal.Hold(cx);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
NS_ASSERTION(JSVAL_IS_STRING(mDataVal), "Bad jsval!");
JSString* str = JSVAL_TO_STRING(mDataVal);
JSONParser* parser = JS_BeginJSONParse(cx, mCachedJSVal.ToJSValPtr());
NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
// This is slightly sneaky, but now that JS_BeginJSONParse succeeded we always
// need call JS_FinishJSONParse even if JS_ConsumeJSONText fails. We'll report
// an error if either failed, though.
ok = JS_ConsumeJSONText(cx, parser, JS_GetStringChars(str),
JS_GetStringLength(str));
// Note the '&& ok' after the call here!
ok = JS_FinishJSONParse(cx, parser, JSVAL_NULL) && ok;
if (!ok) {
mCachedJSVal = JSVAL_NULL;
return NS_ERROR_UNEXPECTED;
}
NS_ASSERTION(mCachedJSVal.ToJSObject(), "Bad JSON result!");
if (mIsPrimitive) {
jsval primitive;
ok = JS_GetProperty(cx, mCachedJSVal.ToJSObject(), JSON_PRIMITIVE_PROPNAME,
&primitive);
if (!ok) {
mCachedJSVal = JSVAL_NULL;
return NS_ERROR_UNEXPECTED;
}
mCachedJSVal = primitive;
}
// We no longer need to hold this copy of the data around.
mDataVal.Release();
// Now that everything has succeeded we'll set this flag so that we return the
// cached jsval in the future.
mHaveCachedJSVal = PR_TRUE;
*retval = mCachedJSVal;
cc->SetReturnValueWasSet(PR_TRUE);
*retval = mDataVal;
return NS_OK;
}

Просмотреть файл

@ -210,26 +210,17 @@ public:
NS_DECL_NSIWORKERMESSAGEEVENT
NS_DECL_NSICLASSINFO_GETINTERFACES
nsDOMWorkerMessageEvent()
: mIsJSON(PR_FALSE), mIsPrimitive(PR_FALSE), mHaveCachedJSVal(PR_FALSE),
mHaveAttemptedConversion(PR_FALSE) { }
nsDOMWorkerMessageEvent() : mDataValWasReparented(PR_FALSE) { }
nsresult SetJSONData(JSContext* aCx,
jsval aData,
PRBool aIsJSON,
PRBool aIsPrimitive);
nsresult SetJSVal(JSContext* aCx,
jsval aData);
protected:
nsString mOrigin;
nsCOMPtr<nsISupports> mSource;
nsAutoJSValHolder mDataVal;
nsAutoJSValHolder mCachedJSVal;
PRPackedBool mIsJSON;
PRPackedBool mIsPrimitive;
PRPackedBool mHaveCachedJSVal;
PRPackedBool mHaveAttemptedConversion;
PRBool mDataValWasReparented;
};
class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent,

Просмотреть файл

@ -1,177 +1,212 @@
var cyclicalObject = {};
cyclicalObject.foo = cyclicalObject;
var cyclicalArray = [];
cyclicalArray.push(cyclicalArray);
function makeCrazyNested(obj, count) {
var innermostobj;
for (var i = 0; i < count; i++) {
obj.foo = { bar: 5 }
innermostobj = obj.foo;
obj = innermostobj;
}
return innermostobj;
}
var crazyNestedObject = {};
makeCrazyNested(crazyNestedObject, 100);
var crazyCyclicalObject = {};
var innermost = makeCrazyNested(crazyCyclicalObject, 1000);
innermost.baz = crazyCyclicalObject;
var objectWithSaneGetter = { };
objectWithSaneGetter.__defineGetter__("foo", function() { return 5; });
// We don't walk prototype chains for cloning so this won't actually do much...
function objectWithSaneGetter2() { }
objectWithSaneGetter2.prototype = {
get foo() {
return 5;
}
};
var objectWithThrowingGetter = { };
objectWithThrowingGetter.__defineGetter__("foo", function() { throw "bad"; });
var messages = [
{
type: "object",
array: false,
exception: false,
shouldCompare: false,
shouldEqual: false,
value: { foo: "bar" }
value: { },
jsonValue: '{}'
},
{
type: "object",
value: {foo: "bar"},
jsonValue: '{"foo":"bar"}'
},
{
type: "object",
value: {foo: "bar", foo2: {bee: "bop"}},
jsonValue: '{"foo":"bar","foo2":{"bee":"bop"}}'
},
{
type: "object",
value: {foo: "bar", foo2: {bee: "bop"}, foo3: "baz"},
jsonValue: '{"foo":"bar","foo2":{"bee":"bop"},"foo3":"baz"}'
},
{
type: "object",
value: {foo: "bar", foo2: [1,2,3]},
jsonValue: '{"foo":"bar","foo2":[1,2,3]}'
},
{
type: "object",
value: cyclicalObject,
exception: true
},
{
type: "object",
value: [null, 2, false, cyclicalObject],
exception: true
},
{
type: "object",
value: cyclicalArray,
exception: true
},
{
type: "object",
value: {foo: 1, bar: cyclicalArray},
exception: true
},
{
type: "object",
value: crazyNestedObject,
jsonValue: JSON.stringify(crazyNestedObject)
},
{
type: "object",
value: crazyCyclicalObject,
exception: true
},
{
type: "object",
value: objectWithSaneGetter,
jsonValue: '{"foo":5}'
},
{
type: "object",
value: new objectWithSaneGetter2(),
jsonValue: '{}'
},
{
type: "object",
value: objectWithThrowingGetter,
exception: true
},
{
type: "object",
array: true,
exception: false,
shouldCompare: false,
shouldEqual: false,
value: [9, 8, 7]
value: [9, 8, 7],
jsonValue: '[9,8,7]'
},
{
type: "object",
array: true,
value: [9, false, 10.5, {foo: "bar"}],
jsonValue: '[9,false,10.5,{"foo":"bar"}]'
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: null
},
{
type: "undefined",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: undefined,
compareValue: undefined
value: undefined
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: "Hello"
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: JSON.stringify({ foo: "bar" })
value: JSON.stringify({ foo: "bar" }),
compareValue: '{"foo":"bar"}'
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 1
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 0
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -1
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 238573459843702923492399923049
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -238573459843702923492399923049
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: 0.25
},
{
type: "number",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -0.25
},
{
type: "boolean",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: true
},
{
type: "boolean",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: false
},
/*
// Uncomment these once bug 465371 is fixed!
{
type: "function",
array: false,
exception: true,
shouldCompare: false,
shouldEqual: false,
value: function (foo) { return "Bad!"; }
},
{
type: "xml",
array: false,
exception: true,
shouldCompare: true,
shouldEqual: true,
value: <funtimes></funtimes>
},
*/
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: NaN,
value: function (foo) { return "Bad!"; },
compareValue: null
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: Infinity,
compareValue: null
type: "number",
isNaN: true,
value: NaN
},
{
type: "object",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: -Infinity,
compareValue: null
type: "number",
isInfinity: true,
value: Infinity
},
{
type: "number",
isNegativeInfinity: true,
value: -Infinity
},
{
type: "string",
array: false,
exception: false,
shouldCompare: true,
shouldEqual: true,
value: "testFinished"
}
@ -182,22 +217,26 @@ for (var index = 0; index < messages.length; index++) {
if (message.hasOwnProperty("compareValue")) {
continue;
}
message.compareValue = message.value;
if (message.hasOwnProperty("shouldEqual") ||
message.hasOwnProperty("shouldCompare")) {
message.compareValue = message.value;
}
}
var onmessage = function(event) {
function onmessage(event) {
for (var index = 0; index < messages.length; index++) {
var exception = false;
var exception = undefined;
try {
postMessage(messages[index].value);
}
catch (e) {
exception = true;
exception = e;
}
if (messages[index].exception != exception) {
throw "Exception inconsistency!";
if ((exception !== undefined && !messages[index].exception) ||
(exception === undefined && messages[index].exception)) {
throw "Exception inconsistency [" + index + "]: " + exception;
}
}
}

Просмотреть файл

@ -31,19 +31,38 @@ Tests of DOM Worker JSON messages
key = messages[index++];
}
is(typeof event.data, key.type,
"Bad type! " + messages.indexOf(key));
is(event.data instanceof Array, key.array,
"Array mismatch! " + messages.indexOf(key));
is(typeof event.data, key.type, "Bad type! " + messages.indexOf(key));
if (key.shouldCompare) {
if (key.array) {
is(event.data instanceof Array, key.array,
"Array mismatch! " + messages.indexOf(key));
}
if (key.isNaN) {
ok(isNaN(event.data), "Should be NaN!" + messages.indexOf(key));
}
if (key.isInfinity) {
is(event.data, Infinity, "Should be Infinity!" + messages.indexOf(key));
}
if (key.isNegativeInfinity) {
is(event.data, -Infinity, "Should be -Infinity!" + messages.indexOf(key));
}
if (key.shouldCompare || key.shouldEqual) {
ok(event.data == key.compareValue,
"Values don't compare! " + messages.indexOf(key));
}
if (key.shouldEqual) {
ok(event.data === key.compareValue,
"Values don't equal!" + messages.indexOf(key));
"Values don't equal! " + messages.indexOf(key));
}
if (key.jsonValue) {
is(JSON.stringify(event.data), key.jsonValue,
"Object stringification inconsistent!" + messages.indexOf(key));
}
if (event.data == "testFinished") {
@ -53,7 +72,7 @@ Tests of DOM Worker JSON messages
};
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.data);
ok(false, "Worker had an error: " + event.message);
SimpleTest.finish();
}