Bug 1075302 - Allow Necko to do main thread I/O on remote blobs, r=khuey.
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -110,16 +110,18 @@ skip-if = (buildapp == 'b2g' && toolkit
[test_bfcache.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_blob_archive.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_blob_simple.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_blob_worker_crash.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
+[test_blob_worker_xhr_post.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_blocked_order.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_bug937006.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_clear.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
[test_complex_keyPaths.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_blob_worker_xhr_post.html
@@ -0,0 +1,113 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Indexed Database Property Test</title>
+
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript;version=1.7">
+ function testSteps()
+ {
+ const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
+ const BLOB_TYPE = "text/plain";
+ const BLOB_SIZE = BLOB_DATA.join("").length;
+
+ info("Setting up");
+
+ let request = indexedDB.open(window.location.pathname, 1);
+ request.onerror = errorHandler;
+ request.onupgradeneeded = grabEventAndContinueHandler;
+ request.onsuccess = unexpectedSuccessHandler;
+ let event = yield undefined;
+
+ let db = event.target.result;
+ db.onerror = errorHandler;
+
+ ok(db, "Created database");
+
+ info("Creating objectStore");
+
+ let objectStore = db.createObjectStore("foo", { autoIncrement: true });
+
+ request.onupgradeneeded = unexpectedSuccessHandler;
+ request.onsuccess = grabEventAndContinueHandler;
+ event = yield undefined;
+
+ ok(true, "Opened database");
+
+ let blob = new Blob(BLOB_DATA, { type: BLOB_TYPE });
+
+ info("Adding blob to database");
+
+ objectStore = db.transaction("foo", "readwrite").objectStore("foo");
+ objectStore.add(blob).onsuccess = grabEventAndContinueHandler;
+ event = yield undefined;
+
+ let blobKey = event.target.result;
+ ok(blobKey, "Got a key for the blob");
+
+ info("Getting blob from the database");
+
+ objectStore = db.transaction("foo").objectStore("foo");
+ objectStore.get(blobKey).onsuccess = grabEventAndContinueHandler;
+ event = yield undefined;
+
+ blob = event.target.result;
+
+ ok(blob instanceof Blob, "Got a blob");
+ is(blob.size, BLOB_SIZE, "Correct size");
+ is(blob.type, BLOB_TYPE, "Correct type");
+
+ let slice = blob.slice(0, BLOB_DATA[0].length, BLOB_TYPE);
+
+ ok(slice instanceof Blob, "Slice returned a blob");
+ is(slice.size, BLOB_DATA[0].length, "Correct size for slice");
+ is(slice.type, BLOB_TYPE, "Correct type for slice");
+
+ info("Sending slice to a worker");
+
+ function workerScript() {
+ onmessage = function(event) {
+ var blob = event.data;
+ var xhr = new XMLHttpRequest();
+ // We just want to make sure the error case doesn't fire; it's fine for
+ // us to just want a 404.
+ xhr.open('POST', 'http://mochi.test:8888/does-not-exist', true);
+ xhr.onload = function() {
+ postMessage({ status: xhr.status });
+ };
+ xhr.onerror = function() {
+ postMessage({ status: 'error' });
+ }
+ xhr.send(blob);
+ }
+ }
+
+ let workerScriptUrl =
+ URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
+
+ let xhrWorker = new Worker(workerScriptUrl);
+ xhrWorker.postMessage(slice);
+ xhrWorker.onmessage = grabEventAndContinueHandler;
+ event = yield undefined;
+
+ is(event.data.status, 404, "XHR generated the expected 404");
+ xhrWorker.terminate();
+
+ URL.revokeObjectURL(workerScriptUrl);
+
+ finishTest();
+ yield undefined;
+ }
+ </script>
+ <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -354,27 +354,29 @@ public:
{
MOZ_ASSERT(IsOnOwningThread());
}
void
Serialize(InputStreamParams& aParams,
FileDescriptorArray& /* aFileDescriptors */)
{
+ MOZ_RELEASE_ASSERT(mBlobImpl);
+
nsCOMPtr<nsIRemoteBlob> remote = do_QueryInterface(mBlobImpl);
MOZ_ASSERT(remote);
- MOZ_ASSERT(remote->GetBlobChild());
-
- aParams = RemoteInputStreamParams(
- nullptr /* sourceParent */,
- remote->GetBlobChild() /* sourceChild */);
+
+ BlobChild* actor = remote->GetBlobChild();
+ MOZ_ASSERT(actor);
+
+ aParams = RemoteInputStreamParams(actor->ParentID());
}
bool
- Deserialize(const InputStreamParams& aParams,
+ Deserialize(const InputStreamParams& /* aParams */,
const FileDescriptorArray& /* aFileDescriptors */)
{
// See InputStreamUtils.cpp to see how deserialization of a
// RemoteInputStream is special-cased.
MOZ_CRASH("RemoteInputStream should never be deserialized");
}
void
@@ -414,25 +416,58 @@ public:
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHOD
Available(uint64_t* aAvailable) MOZ_OVERRIDE
{
- // See large comment in FileInputStreamWrapper::Available.
- if (IsOnOwningThread()) {
+ if (!IsOnOwningThread()) {
+ nsresult rv = BlockAndWaitForStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStream->Available(aAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ if (NS_IsMainThread()) {
+ NS_WARNING("Someone is trying to do main-thread I/O...");
+ }
+#endif
+
+ nsresult rv;
+
+ // See if we already have our real stream.
+ nsCOMPtr<nsIInputStream> inputStream;
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ inputStream = mStream;
+ }
+
+ // If we do then just call through.
+ if (inputStream) {
+ rv = inputStream->Available(aAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ // If the stream is already closed then we can't do anything.
+ if (!mBlobImpl) {
return NS_BASE_STREAM_CLOSED;
}
- nsresult rv = BlockAndWaitForStream();
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = mStream->Available(aAvailable);
+ // Otherwise fake it...
+ NS_WARNING("Available() called before real stream has been delivered, "
+ "guessing the amount of data available!");
+
+ rv = mBlobImpl->GetSize(aAvailable);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHOD
Read(char* aBuffer, uint32_t aCount, uint32_t* aResult) MOZ_OVERRIDE
{
@@ -685,43 +720,57 @@ public:
DebugOnly<bool> isMutable;
MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
MOZ_ASSERT(!isMutable);
return GetOrCreateInternal(aID,
aProcessID,
aBlobImpl,
/* aMayCreate */ true,
- /* aMayGet */ false);
+ /* aMayGet */ false,
+ /* aIgnoreProcessID */ false);
}
static already_AddRefed<IDTableEntry>
Get(const nsID& aID, intptr_t aProcessID)
{
return GetOrCreateInternal(aID,
aProcessID,
nullptr,
/* aMayCreate */ false,
- /* aMayGet */ true);
+ /* aMayGet */ true,
+ /* aIgnoreProcessID */ false);
+ }
+
+ static already_AddRefed<IDTableEntry>
+ Get(const nsID& aID)
+ {
+ return GetOrCreateInternal(aID,
+ 0,
+ nullptr,
+ /* aMayCreate */ false,
+ /* aMayGet */ true,
+ /* aIgnoreProcessID */ true);
}
static already_AddRefed<IDTableEntry>
GetOrCreate(const nsID& aID, intptr_t aProcessID, DOMFileImpl* aBlobImpl)
{
MOZ_ASSERT(aBlobImpl);
DebugOnly<bool> isMutable;
MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
MOZ_ASSERT(!isMutable);
return GetOrCreateInternal(aID,
aProcessID,
aBlobImpl,
/* aMayCreate */ true,
- /* aMayGet */ true);
+ /* aMayGet */ true,
+ /* aIgnoreProcessID */ false);
}
const nsID&
ID() const
{
return mID;
}
@@ -743,17 +792,18 @@ private:
IDTableEntry(const nsID& aID, intptr_t aProcessID, DOMFileImpl* aBlobImpl);
~IDTableEntry();
static already_AddRefed<IDTableEntry>
GetOrCreateInternal(const nsID& aID,
intptr_t aProcessID,
DOMFileImpl* aBlobImpl,
bool aMayCreate,
- bool aMayGet);
+ bool aMayGet,
+ bool aIgnoreProcessID);
};
// Each instance of this class will be dispatched to the network stream thread
// pool to run the first time where it will open the file input stream. It will
// then dispatch itself back to the owning thread to send the child process its
// response (assuming that the child has not crashed). The runnable will then
// dispatch itself to the thread pool again in order to close the file input
// stream.
@@ -2913,16 +2963,36 @@ BlobParent::Create(PBackgroundParent* aM
{
AssertCorrectThreadForManager(aManager);
MOZ_ASSERT(aManager);
return CreateFromParams(aManager, aParams);
}
// static
+already_AddRefed<DOMFileImpl>
+BlobParent::GetBlobImplForID(const nsID& aID)
+{
+ if (NS_WARN_IF(gProcessType != GeckoProcessType_Default)) {
+ ASSERT_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ nsRefPtr<IDTableEntry> idTableEntry = IDTableEntry::Get(aID);
+ if (NS_WARN_IF(!idTableEntry)) {
+ return nullptr;
+ }
+
+ nsRefPtr<DOMFileImpl> blobImpl = idTableEntry->BlobImpl();
+ MOZ_ASSERT(blobImpl);
+
+ return blobImpl.forget();
+}
+
+// static
template <class ParentManagerType>
BlobParent*
BlobParent::GetOrCreateFromImpl(ParentManagerType* aManager,
DOMFileImpl* aBlobImpl)
{
AssertCorrectThreadForManager(aManager);
MOZ_ASSERT(aManager);
MOZ_ASSERT(aBlobImpl);
@@ -3545,17 +3615,18 @@ IDTableEntry::~IDTableEntry()
// static
already_AddRefed<BlobParent::IDTableEntry>
BlobParent::
IDTableEntry::GetOrCreateInternal(const nsID& aID,
intptr_t aProcessID,
DOMFileImpl* aBlobImpl,
bool aMayCreate,
- bool aMayGet)
+ bool aMayGet,
+ bool aIgnoreProcessID)
{
MOZ_ASSERT(gProcessType == GeckoProcessType_Default);
MOZ_ASSERT(sIDTableMutex);
sIDTableMutex->AssertNotCurrentThreadOwns();
nsRefPtr<IDTableEntry> entry;
{
@@ -3573,17 +3644,17 @@ IDTableEntry::GetOrCreateInternal(const
if (entry) {
MOZ_ASSERT_IF(aBlobImpl, entry->BlobImpl() == aBlobImpl);
if (NS_WARN_IF(!aMayGet)) {
return nullptr;
}
- if (NS_WARN_IF(entry->mProcessID != aProcessID)) {
+ if (!aIgnoreProcessID && NS_WARN_IF(entry->mProcessID != aProcessID)) {
return nullptr;
}
} else {
if (NS_WARN_IF(!aMayCreate)) {
return nullptr;
}
MOZ_ASSERT(aBlobImpl);
--- a/dom/ipc/BlobParent.h
+++ b/dom/ipc/BlobParent.h
@@ -102,16 +102,19 @@ public:
const ParentBlobConstructorParams& aParams);
static void
Destroy(PBlobParent* aActor)
{
delete static_cast<BlobParent*>(aActor);
}
+ static already_AddRefed<DOMFileImpl>
+ GetBlobImplForID(const nsID& aID);
+
bool
HasManager() const
{
return mBackgroundManager || mContentManager;
}
PBackgroundParent*
GetBackgroundManager() const
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -2,19 +2,16 @@
/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PBlob;
include InputStreamParams;
-using struct nsID
- from "nsID.h";
-
using struct mozilla::void_t
from "ipc/IPCMessageUtils.h";
using struct mozilla::SerializedStructuredCloneBuffer
from "ipc/IPCMessageUtils.h";
namespace mozilla {
namespace dom {
--- a/ipc/glue/InputStreamParams.ipdlh
+++ b/ipc/glue/InputStreamParams.ipdlh
@@ -2,16 +2,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
include protocol PBlob;
include protocol PFileDescriptorSet;
+using struct nsID
+ from "nsID.h";
+
namespace mozilla {
namespace ipc {
struct StringInputStreamParams
{
nsCString data;
};
@@ -34,17 +37,17 @@ struct MultiplexInputStreamParams
InputStreamParams[] streams;
uint32_t currentStream;
nsresult status;
bool startedReadingCurrent;
};
struct RemoteInputStreamParams
{
- PBlob remoteBlob;
+ nsID id;
};
// XXX This may only be used for same-process inter-thread communication! The
// value should be reinterpret_cast'd to nsIInputStream. It carries a
// reference.
struct SameProcessInputStreamParams
{
intptr_t addRefedInputStream;
--- a/ipc/glue/InputStreamUtils.cpp
+++ b/ipc/glue/InputStreamUtils.cpp
@@ -102,27 +102,23 @@ DeserializeInputStream(const InputStream
case InputStreamParams::TMultiplexInputStreamParams:
serializable = do_CreateInstance(kMultiplexInputStreamCID);
break;
// When the input stream already exists in this process, all we need to do
// is retrieve the original instead of sending any data over the wire.
case InputStreamParams::TRemoteInputStreamParams: {
- const RemoteInputStreamParams& params =
- aParams.get_RemoteInputStreamParams();
+ if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
+ return nullptr;
+ }
- nsRefPtr<DOMFileImpl> blobImpl;
- if (params.remoteBlobParent()) {
- blobImpl =
- static_cast<BlobParent*>(params.remoteBlobParent())->GetBlobImpl();
- } else {
- blobImpl =
- static_cast<BlobChild*>(params.remoteBlobChild())->GetBlobImpl();
- }
+ const nsID& id = aParams.get_RemoteInputStreamParams().id();
+
+ nsRefPtr<DOMFileImpl> blobImpl = BlobParent::GetBlobImplForID(id);
MOZ_ASSERT(blobImpl, "Invalid blob contents");
// If fetching the internal stream fails, we ignore it and return a
// null stream.
nsCOMPtr<nsIInputStream> stream;
nsresult rv = blobImpl->GetInternalStream(getter_AddRefs(stream));
if (NS_FAILED(rv) || !stream) {